@gibme/mikrotik 1.0.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2016-2023 Brandon Lehmann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Simple Mikrotik helper/wrapper
2
+
3
+ ## Documentation
4
+
5
+ [https://gibme-npm.github.io/mikrotik/](https://gibme-npm.github.io/mikrotik/)
@@ -0,0 +1,64 @@
1
+ import SSH from '@gibme/ssh';
2
+ import { BandwidthTest, Response } from './types';
3
+ import Cache from '@gibme/cache/memory';
4
+ export { BandwidthTest };
5
+ export default class Mikrotik extends SSH {
6
+ protected static cache: Cache;
7
+ /**
8
+ * Retrieves the routes in the routing table
9
+ *
10
+ * @param min_distance
11
+ * @param vrf
12
+ */
13
+ get_ip_routes(min_distance?: number, vrf?: string): Promise<Response.Route[]>;
14
+ /**
15
+ * Retrieves the IP addresses active on the system
16
+ */
17
+ get_ip_addresses(): Promise<Response.Address[]>;
18
+ /**
19
+ * Retrieves the interfaces active on the system
20
+ */
21
+ get_interfaces(): Promise<Response.Interface[]>;
22
+ /**
23
+ * For all device IP addresses, counts the routes that exit (either directly or tunneled)
24
+ * the device using each IP address
25
+ *
26
+ * @param min_distance
27
+ * @param vrf
28
+ */
29
+ get_route_counts(min_distance?: number, vrf?: string): Promise<Record<string, Response.RouteCount>>;
30
+ /**
31
+ * Executes a bandwidth test
32
+ *
33
+ * Note: you must supply a callback in the options to get the intermittent results
34
+ * of the bandwidth test; otherwise, you will only receive the final result back
35
+ * from the promise
36
+ *
37
+ * @param target
38
+ * @param username
39
+ * @param password
40
+ * @param options
41
+ */
42
+ bandwidth_test(target: string, username: string, password: string, options?: Partial<BandwidthTest.Options>): Promise<BandwidthTest.Update>;
43
+ /**
44
+ * Pings the target IP address from the source IP address (if supplied)
45
+ *
46
+ * @param target
47
+ * @param source
48
+ */
49
+ ping(target: string, source?: string): Promise<any>;
50
+ /**
51
+ * Performs a traceroute to the target IP address from the source IP address (if supplied)
52
+ *
53
+ * @param target
54
+ * @param source
55
+ */
56
+ traceroute(target: string, source?: string): Promise<Response.Traceroute[]>;
57
+ /**
58
+ * Executes a command with expected terse response and parses it accordingly
59
+ *
60
+ * @param command
61
+ * @protected
62
+ */
63
+ protected terse<Type extends object = any>(command: string): Promise<Type[]>;
64
+ }
package/dist/index.js ADDED
@@ -0,0 +1,491 @@
1
+ "use strict";
2
+ // Copyright (c) 2024, Brandon Lehmann <brandonlehmann@gmail.com>
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ var __importDefault = (this && this.__importDefault) || function (mod) {
31
+ return (mod && mod.__esModule) ? mod : { "default": mod };
32
+ };
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.BandwidthTest = void 0;
35
+ const ssh_1 = __importDefault(require("@gibme/ssh"));
36
+ const types_1 = require("./types");
37
+ Object.defineProperty(exports, "BandwidthTest", { enumerable: true, get: function () { return types_1.BandwidthTest; } });
38
+ const ip_1 = __importDefault(require("ip"));
39
+ const memory_1 = __importDefault(require("@gibme/cache/memory"));
40
+ const dns_1 = require("dns");
41
+ class Mikrotik extends ssh_1.default {
42
+ /**
43
+ * Retrieves the routes in the routing table
44
+ *
45
+ * @param min_distance
46
+ * @param vrf
47
+ */
48
+ get_ip_routes() {
49
+ return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf) {
50
+ const ifaces = yield this.get_interfaces();
51
+ const ips = yield this.get_ip_addresses();
52
+ const routes = yield this.terse('/ip route print terse without-paging where active');
53
+ return routes.map(route => {
54
+ var _a;
55
+ const _network = route['dst-address'].split('/');
56
+ const address = _network[0];
57
+ const cidr = parseInt(_network[1]);
58
+ const [gateway, iface] = (() => {
59
+ if (route.gateway.includes('%')) {
60
+ return route.gateway.split('%', 2);
61
+ }
62
+ else if (ip_1.default.isV4Format(route.gateway)) {
63
+ const ip = ips.filter(ip => ip.isLocal(route.gateway)).shift();
64
+ return [route.gateway, ip === null || ip === void 0 ? void 0 : ip.iface];
65
+ }
66
+ else {
67
+ const ip = ips.filter(ip => ip.iface === route.gateway).shift();
68
+ return [ip === null || ip === void 0 ? void 0 : ip.ipaddress, route.gateway];
69
+ }
70
+ })();
71
+ const distance = parseInt(route.distance);
72
+ const scope = parseInt(route.scope);
73
+ const target_scope = parseInt(route.target_scope) || undefined;
74
+ // this does not account for if a device has multiple addresses in the same network
75
+ const preferred_source = (_a = ips.filter(ip => gateway ? ip.isLocal(gateway) : false).shift()) === null || _a === void 0 ? void 0 : _a.ipaddress;
76
+ const _vrf = route['routing-mark'] || 'main';
77
+ const tunnel = (() => {
78
+ const tunnel = ifaces.filter(_iface => _iface.name === iface).shift();
79
+ if (!tunnel)
80
+ return undefined;
81
+ const root = ips.filter(ip => ip.ipaddress === tunnel.local_address).shift();
82
+ if (!root)
83
+ return undefined;
84
+ return Object.assign(Object.assign({}, tunnel), { root });
85
+ })();
86
+ return {
87
+ network: {
88
+ address,
89
+ cidr
90
+ },
91
+ preferred_source,
92
+ gateway,
93
+ iface,
94
+ tunnel,
95
+ distance,
96
+ scope,
97
+ target_scope,
98
+ vrf: _vrf
99
+ };
100
+ })
101
+ .filter(route => route.distance >= min_distance)
102
+ .filter(route => vrf ? route.vrf === vrf : true);
103
+ });
104
+ }
105
+ /**
106
+ * Retrieves the IP addresses active on the system
107
+ */
108
+ get_ip_addresses() {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const addresses = yield this.terse('/ip address print terse without-paging where !disabled');
111
+ return addresses.map(line => {
112
+ const _address = line.address.split('/');
113
+ const ipaddress = _address[0];
114
+ const cidr = parseInt(_address[1]);
115
+ return {
116
+ ipaddress,
117
+ network: line.network,
118
+ cidr,
119
+ iface: line.interface,
120
+ isLocal: (ipaddress) => ip_1.default.cidrSubnet(`${line.network}/${cidr}`).contains(ipaddress)
121
+ };
122
+ });
123
+ });
124
+ }
125
+ /**
126
+ * Retrieves the interfaces active on the system
127
+ */
128
+ get_interfaces() {
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ const ifaces = yield this.terse('/interface print terse without-paging where !disabled');
131
+ const gre = yield this.terse('/interface gre print terse without-paging where !disabled');
132
+ const ipip = yield this.terse('/interface ipip print terse without-paging where !disabled');
133
+ const eoip = yield this.terse('/interface eoip print terse without-paging where !disabled');
134
+ return ifaces.map(line => {
135
+ const _type = (() => {
136
+ switch (line.type) {
137
+ case 'gre-tunnel':
138
+ return 'gre';
139
+ case 'ipip-tunnel':
140
+ return 'ipip';
141
+ case 'eoip-tunnel':
142
+ return 'eoip';
143
+ default:
144
+ return line.type;
145
+ }
146
+ })();
147
+ const local_address = (() => {
148
+ switch (line.type) {
149
+ case 'gre-tunnel':
150
+ return gre.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
151
+ case 'ipip':
152
+ return ipip.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
153
+ case 'eoip':
154
+ return eoip.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
155
+ default:
156
+ return undefined;
157
+ }
158
+ })();
159
+ const remote_address = (() => {
160
+ switch (line.type) {
161
+ case 'gre-tunnel':
162
+ return gre.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
163
+ case 'ipip':
164
+ return ipip.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
165
+ case 'eoip':
166
+ return eoip.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
167
+ default:
168
+ return undefined;
169
+ }
170
+ })();
171
+ return {
172
+ name: line.name,
173
+ type: _type,
174
+ local_address,
175
+ remote_address
176
+ };
177
+ });
178
+ });
179
+ }
180
+ /**
181
+ * For all device IP addresses, counts the routes that exit (either directly or tunneled)
182
+ * the device using each IP address
183
+ *
184
+ * @param min_distance
185
+ * @param vrf
186
+ */
187
+ get_route_counts() {
188
+ return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf) {
189
+ const routes = yield this.get_ip_routes(min_distance, vrf);
190
+ const ips = yield this.get_ip_addresses();
191
+ const results = {};
192
+ ips.forEach(ip => {
193
+ results[ip.ipaddress] = {
194
+ interface: ip.iface,
195
+ count: routes.filter(route => { var _a; return route.preferred_source === ip.ipaddress || ((_a = route.tunnel) === null || _a === void 0 ? void 0 : _a.local_address) === ip.ipaddress; }).length,
196
+ active: false
197
+ };
198
+ });
199
+ const best = {
200
+ ip: '',
201
+ count: 0
202
+ };
203
+ for (const key of Object.keys(results)) {
204
+ if (results[key].count > best.count) {
205
+ best.count = results[key].count;
206
+ best.ip = key;
207
+ }
208
+ }
209
+ results[best.ip].active = true;
210
+ return results;
211
+ });
212
+ }
213
+ /**
214
+ * Executes a bandwidth test
215
+ *
216
+ * Note: you must supply a callback in the options to get the intermittent results
217
+ * of the bandwidth test; otherwise, you will only receive the final result back
218
+ * from the promise
219
+ *
220
+ * @param target
221
+ * @param username
222
+ * @param password
223
+ * @param options
224
+ */
225
+ bandwidth_test(target_1, username_1, password_1) {
226
+ return __awaiter(this, arguments, void 0, function* (target, username, password, options = {
227
+ duration: 15,
228
+ direction: 'both',
229
+ protocol: 'udp',
230
+ random_data: false,
231
+ callback: () => {
232
+ }
233
+ }) {
234
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
235
+ const $ = this;
236
+ const sleep = (timeout) => __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, timeout)); });
237
+ // eslint-disable-next-line no-async-promise-executor
238
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
239
+ var _a, _b, _c, _d, _e;
240
+ (_a = options.duration) !== null && _a !== void 0 ? _a : (options.duration = 15);
241
+ (_b = options.direction) !== null && _b !== void 0 ? _b : (options.direction = 'both');
242
+ (_c = options.protocol) !== null && _c !== void 0 ? _c : (options.protocol = 'udp');
243
+ (_d = options.random_data) !== null && _d !== void 0 ? _d : (options.random_data = false);
244
+ (_e = options.callback) !== null && _e !== void 0 ? _e : (options.callback = () => {
245
+ });
246
+ /**
247
+ * This serves as a lazy "mutex" that prevents us from running
248
+ * a bandwidth test for against a target host that is already
249
+ * in the process of running a bandwidth test
250
+ */
251
+ while (yield Mikrotik.cache.includes(target)) {
252
+ if (options.callback) {
253
+ options.callback({
254
+ status: 'queued',
255
+ duration: 0,
256
+ randomData: options.random_data,
257
+ direction: options.direction,
258
+ connectionCount: 0,
259
+ localCPULoad: 0,
260
+ remoteCPULoad: 0
261
+ });
262
+ }
263
+ yield sleep(1000);
264
+ }
265
+ const toBPS = (value) => {
266
+ if (!value) {
267
+ return 0;
268
+ }
269
+ value = value.toLowerCase();
270
+ if (value.includes('tbps')) {
271
+ const tmp = parseFloat(value) || 0;
272
+ return Math.round(tmp * 1000 * 1000 * 1000 * 1000);
273
+ }
274
+ else if (value.includes('gbps')) {
275
+ const tmp = parseFloat(value) || 0;
276
+ return Math.round(tmp * 1000 * 1000 * 1000);
277
+ }
278
+ else if (value.includes('mbps')) {
279
+ const tmp = parseFloat(value) || 0;
280
+ return Math.round(tmp * 1000 * 1000);
281
+ }
282
+ else if (value.includes('kbps')) {
283
+ const tmp = parseFloat(value) || 0;
284
+ return Math.round(tmp * 1000);
285
+ }
286
+ else {
287
+ return Math.round(parseFloat(value) || 0);
288
+ }
289
+ };
290
+ /**
291
+ * Performs further processing of the stream of data returned
292
+ * during the bandwidth testing
293
+ *
294
+ * @param buffer
295
+ */
296
+ function handleStream(buffer) {
297
+ return __awaiter(this, void 0, void 0, function* () {
298
+ const frame = new types_1.BandwidthTest.Frame(buffer).parse();
299
+ const result = {
300
+ status: frame.status,
301
+ duration: parseInt(frame.duration) || 0,
302
+ randomData: frame['random-data'] === 'yes',
303
+ direction: frame.direction,
304
+ connectionCount: parseInt(frame['connection-count']) || 0,
305
+ localCPULoad: (parseInt(frame['local-cpu-load']) || 0) / 100,
306
+ remoteCPULoad: (parseInt(frame['remote-cpu-load']) || 0) / 100
307
+ };
308
+ if (frame['lost-packets'])
309
+ result.lostPackets = parseInt(frame['lost-packets']) || 0;
310
+ if (frame['tx-current']) {
311
+ result.transmit = {
312
+ current: toBPS(frame['tx-current']),
313
+ shortAverage: toBPS(frame['tx-10-second-average']),
314
+ totalAverage: toBPS(frame['tx-total-average']),
315
+ size: toBPS(frame['tx-size'])
316
+ };
317
+ }
318
+ if (frame['rx-current']) {
319
+ result.receive = {
320
+ current: toBPS(frame['rx-current']),
321
+ shortAverage: toBPS(frame['rx-10-second-average']),
322
+ totalAverage: toBPS(frame['rx-total-average']),
323
+ size: toBPS(frame['rx-size'])
324
+ };
325
+ }
326
+ switch (result.status) {
327
+ case 'done testing':
328
+ if (options.callback)
329
+ options.callback(result);
330
+ $.off('stream', handleStream);
331
+ return resolve(result);
332
+ case 'authentication_failed':
333
+ $.off('stream', handleStream);
334
+ return reject(new Error('Authentication Failed'));
335
+ case 'connecting':
336
+ case 'running':
337
+ yield Mikrotik.cache.ttl(target, options.duration); // bump our mutex
338
+ if (options.callback)
339
+ options.callback(result);
340
+ break;
341
+ }
342
+ });
343
+ }
344
+ this.on('stream', handleStream);
345
+ // set our mutex
346
+ yield Mikrotik.cache.set(target, target, options.duration);
347
+ yield this.stream(`/tool bandwidth-test protocol=${options.protocol} ` +
348
+ `user=${username} password=${password} ` +
349
+ `duration=${options.duration}s direction=${options.direction} ` +
350
+ `address=${target} random-data=${options.random_data ? 'yes' : 'no'} interval=1s`, {
351
+ separator: '\r\n\r\n'
352
+ });
353
+ yield Mikrotik.cache.del(target); // release our mutex
354
+ }));
355
+ });
356
+ }
357
+ /**
358
+ * Pings the target IP address from the source IP address (if supplied)
359
+ *
360
+ * @param target
361
+ * @param source
362
+ */
363
+ ping(target, source) {
364
+ return __awaiter(this, void 0, void 0, function* () {
365
+ const result = {
366
+ target,
367
+ latency: 2000
368
+ };
369
+ if (source)
370
+ result.source = source;
371
+ let command = `/ping address=${target} count=1`;
372
+ if (source) {
373
+ command += ` src-address=${source}`;
374
+ }
375
+ try {
376
+ const response = (yield this.exec(command))
377
+ .toString()
378
+ .trim()
379
+ .split('\n')
380
+ .map(line => line.trim());
381
+ for (const line of response) {
382
+ const parts = line.split(/\s+/)
383
+ .map(part => part.trim());
384
+ if (isNaN(parseInt(parts[0])))
385
+ continue;
386
+ result.latency = parseInt(parts[4]) || 2000;
387
+ }
388
+ return result;
389
+ }
390
+ catch (_a) {
391
+ return result;
392
+ }
393
+ });
394
+ }
395
+ /**
396
+ * Performs a traceroute to the target IP address from the source IP address (if supplied)
397
+ *
398
+ * @param target
399
+ * @param source
400
+ */
401
+ traceroute(target, source) {
402
+ return __awaiter(this, void 0, void 0, function* () {
403
+ let command = `/tool traceroute address=${target} count=1`;
404
+ if (source) {
405
+ command += ` src-address=${source}`;
406
+ }
407
+ const response = (yield this.exec(command))
408
+ .toString()
409
+ .split('\r\n\r\n')
410
+ .map(frame => frame.trim())
411
+ .filter(frame => frame.length !== 0)
412
+ .map(frame => frame.split('\r\n')
413
+ .map(line => line.trim())
414
+ .map(line => line.split(/\s+/)
415
+ .map(col => col.trim()))
416
+ .filter(col => !isNaN(parseInt(col[0]))))
417
+ .pop();
418
+ if (!response)
419
+ throw new Error(`Could not perform traceroute to ${target}`);
420
+ const results = [];
421
+ const resolve = (ip) => __awaiter(this, void 0, void 0, function* () {
422
+ return new Promise(resolve => {
423
+ (0, dns_1.reverse)(ip, (error, addresses) => {
424
+ if (error)
425
+ return resolve([ip, undefined]);
426
+ return resolve([ip, addresses.shift()]);
427
+ });
428
+ });
429
+ });
430
+ for (const [hop, address, loss, sent, last, avg, best, worst] of response) {
431
+ const result = {
432
+ hop: parseInt(hop),
433
+ loss: (sent === 'timeout' ? parseInt(address) : parseInt(loss)) / 100,
434
+ sent: sent === 'timeout' ? parseInt(loss) : parseInt(sent),
435
+ last: sent === 'timeout' ? 2000 : parseInt(last),
436
+ average: sent === 'timeout' ? 2000 : parseInt(avg),
437
+ best: sent === 'timeout' ? 2000 : parseInt(best),
438
+ worst: sent === 'timeout' ? 2000 : parseInt(worst),
439
+ timeout: sent === 'timeout'
440
+ };
441
+ if (sent !== 'timeout')
442
+ result.address = address;
443
+ results.push(result);
444
+ }
445
+ (yield Promise.all(results.filter(result => result.address)
446
+ .map(result => resolve(result.address || ''))))
447
+ .forEach(result => {
448
+ for (let i = 0; i < results.length; i++) {
449
+ if (result[0] === results[i].address && result[1]) {
450
+ results[i].hostname = result[1];
451
+ }
452
+ }
453
+ });
454
+ return results;
455
+ });
456
+ }
457
+ /**
458
+ * Executes a command with expected terse response and parses it accordingly
459
+ *
460
+ * @param command
461
+ * @protected
462
+ */
463
+ terse(command) {
464
+ return __awaiter(this, void 0, void 0, function* () {
465
+ const results = [];
466
+ const lines = (yield this.exec(command))
467
+ .toString()
468
+ .split('\r\n')
469
+ .map(line => line.trim())
470
+ .filter(line => line.split(/\s+/)
471
+ .length !== 0 && line.length !== 0);
472
+ lines.forEach(line => {
473
+ const result = {};
474
+ line.split(/\s+/)
475
+ .filter(col => col.includes('='))
476
+ .forEach(col => {
477
+ const [key, value] = col.split('=', 2);
478
+ result[key] = value;
479
+ });
480
+ if (Object.keys(result).length !== 0)
481
+ results.push(result);
482
+ });
483
+ return results;
484
+ });
485
+ }
486
+ }
487
+ Mikrotik.cache = new memory_1.default({
488
+ stdTTL: 15,
489
+ checkperiod: 17
490
+ });
491
+ exports.default = Mikrotik;
@@ -0,0 +1,94 @@
1
+ export declare namespace BandwidthTest {
2
+ export type Direction = 'both' | 'transmit' | 'receive';
3
+ export type Protocol = 'udp' | 'tcp';
4
+ export type Status = 'running' | 'connecting' | 'done testing' | 'authentication_failed' | 'queued';
5
+ /**
6
+ * Class that helps with processing a "frame" of the Bandwidth Test response
7
+ */
8
+ export class Frame {
9
+ readonly buffer: Buffer;
10
+ private parsed?;
11
+ constructor(buffer: Buffer);
12
+ parse(): Record<string, string>;
13
+ }
14
+ interface DirectionStats {
15
+ current: number;
16
+ shortAverage: number;
17
+ totalAverage: number;
18
+ size: number;
19
+ }
20
+ export interface Update {
21
+ status: Status;
22
+ duration: number;
23
+ transmit?: DirectionStats;
24
+ receive?: DirectionStats;
25
+ lostPackets?: number;
26
+ randomData: boolean;
27
+ direction: Direction;
28
+ connectionCount: number;
29
+ localCPULoad: number;
30
+ remoteCPULoad: number;
31
+ }
32
+ export interface Options {
33
+ duration: number;
34
+ direction: Direction;
35
+ protocol: Protocol;
36
+ random_data: boolean;
37
+ callback: (frame: Update) => void;
38
+ }
39
+ export {};
40
+ }
41
+ export declare namespace Response {
42
+ interface Address {
43
+ ipaddress: string;
44
+ network: string;
45
+ cidr: number;
46
+ iface: string;
47
+ isLocal(ipaddress: string): boolean;
48
+ }
49
+ interface Interface {
50
+ name: string;
51
+ type: string;
52
+ local_address?: string;
53
+ remote_address?: string;
54
+ }
55
+ interface Tunnel extends Interface {
56
+ root: Address;
57
+ }
58
+ interface Route {
59
+ network: {
60
+ address: string;
61
+ cidr: number;
62
+ };
63
+ preferred_source?: string;
64
+ gateway?: string;
65
+ iface?: string;
66
+ tunnel?: Tunnel;
67
+ distance: number;
68
+ scope: number;
69
+ target_scope?: number;
70
+ vrf: string;
71
+ }
72
+ interface RouteCount {
73
+ interface: string;
74
+ count: number;
75
+ active: boolean;
76
+ }
77
+ interface Ping {
78
+ source?: string;
79
+ target: string;
80
+ latency: number;
81
+ }
82
+ interface Traceroute {
83
+ hop: number;
84
+ address?: string;
85
+ hostname?: string;
86
+ loss: number;
87
+ sent: number;
88
+ last: number;
89
+ average: number;
90
+ best: number;
91
+ worst: number;
92
+ timeout: boolean;
93
+ }
94
+ }
package/dist/types.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ // Copyright (c) 2024, Brandon Lehmann <brandonlehmann@gmail.com>
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.BandwidthTest = void 0;
23
+ var BandwidthTest;
24
+ (function (BandwidthTest) {
25
+ /**
26
+ * Class that helps with processing a "frame" of the Bandwidth Test response
27
+ */
28
+ class Frame {
29
+ // eslint-disable-next-line no-useless-constructor
30
+ constructor(buffer) {
31
+ this.buffer = buffer;
32
+ }
33
+ parse() {
34
+ if (!this.parsed) {
35
+ this.parsed = {};
36
+ const records = this.buffer.toString('utf8').trim()
37
+ .split('\n')
38
+ .map(line => line.trim())
39
+ .map(line => line.split(':', 2)
40
+ .map(elem => elem.trim()));
41
+ for (const [key, value] of records) {
42
+ if (key.length === 0)
43
+ continue;
44
+ this.parsed[key.toLowerCase()] = value;
45
+ }
46
+ }
47
+ return this.parsed;
48
+ }
49
+ }
50
+ BandwidthTest.Frame = Frame;
51
+ })(BandwidthTest || (exports.BandwidthTest = BandwidthTest = {}));
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@gibme/mikrotik",
3
+ "version": "1.0.0",
4
+ "description": "A simple mikrotik helper/wrapper",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/*"
9
+ ],
10
+ "license": "MIT",
11
+ "scripts": {
12
+ "build": "yarn build:typescript",
13
+ "build:docs": "./node_modules/.bin/typedoc",
14
+ "build:typescript": "./node_modules/.bin/tsc",
15
+ "test": "yarn test:style && yarn test:typecheck && yarn test:mocha",
16
+ "test:typecheck": "./node_modules/.bin/tsc --noEmit",
17
+ "test:style": "yarn style",
18
+ "test:mocha": "./node_modules/.bin/mocha --exit --timeout 30000 --require ts-node/register test/test.ts",
19
+ "style": "./node_modules/.bin/eslint src/**/*.ts test/**/*.ts",
20
+ "fix-style": "./node_modules/.bin/eslint --fix src/**/*.ts test/**/*.ts",
21
+ "fix:style": "yarn fix-style",
22
+ "prepublishOnly": "yarn build"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/gibme-npm/mikrotik.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/gibme-npm/mikrotik/issues"
30
+ },
31
+ "homepage": "https://gibme-npm.github.io/mikrotik/",
32
+ "engines": {
33
+ "node": ">=16"
34
+ },
35
+ "engineStrict": true,
36
+ "author": {
37
+ "name": "Brandon Lehmann",
38
+ "email": "brandonlehmann@gmail.com"
39
+ },
40
+ "devDependencies": {
41
+ "@types/mocha": "^10.0.6",
42
+ "@types/node": "^20.11.7",
43
+ "@typescript-eslint/eslint-plugin": "^6.19.1",
44
+ "@typescript-eslint/parser": "^6.19.1",
45
+ "dotenv": "^16.4.5",
46
+ "eslint": "^8.56.0",
47
+ "eslint-config-standard": "^17.1.0",
48
+ "eslint-plugin-import": "^2.29.1",
49
+ "eslint-plugin-n": "^16.6.2",
50
+ "eslint-plugin-node": "^11.1.0",
51
+ "eslint-plugin-promise": "^6.1.1",
52
+ "mocha": "^10.2.0",
53
+ "ts-node": "^10.9.2",
54
+ "typedoc": "^0.25.7",
55
+ "typescript": "^5.3.3"
56
+ },
57
+ "dependencies": {
58
+ "@gibme/cache": "^1.1.5",
59
+ "@gibme/ssh": "^1.0.0",
60
+ "@types/ip": "^1.1.3",
61
+ "ip": "^2.0.1"
62
+ }
63
+ }