From 77563fc85e6a35cc88ca300e42fd0df0128d24d7 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Mon, 21 Oct 2019 22:51:36 -0700 Subject: [PATCH] Move ping code to own file --- lib/nbi.ts | 11 ++--- lib/ping.ts | 89 +++++++++++++++++++++++++++++++++++++++++ lib/ui/api-functions.ts | 34 ---------------- lib/ui/api.ts | 19 +++++---- 4 files changed, 104 insertions(+), 49 deletions(-) create mode 100644 lib/ping.ts diff --git a/lib/nbi.ts b/lib/nbi.ts index 4189f84..c3bbd44 100644 --- a/lib/nbi.ts +++ b/lib/nbi.ts @@ -21,14 +21,13 @@ import * as url from "url"; import * as mongodb from "mongodb"; import * as querystring from "querystring"; import * as vm from "vm"; -import { exec } from "child_process"; import * as config from "./config"; import * as db from "./db"; import * as query from "./query"; import * as apiFunctions from "./api-functions"; import * as cache from "./cache"; import { version as VERSION } from "../package.json"; -import * as os from "os"; +import { ping } from "./ping"; const DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-_%]+)\/tasks\/?$/; const TASKS_REGEX = /^\/tasks\/([a-zA-Z0-9\-_%]+)(\/[a-zA-Z_]*)?$/; @@ -43,10 +42,6 @@ const PROVISIONS_REGEX = /^\/provisions\/([a-zA-Z0-9\-_%]+)\/?$/; const VIRTUAL_PARAMETERS_REGEX = /^\/virtual_parameters\/([a-zA-Z0-9\-_%]+)\/?$/; const FAULTS_REGEX = /^\/faults\/([a-zA-Z0-9\-_%:]+)\/?$/; -function pingVariant(): string { - return os.platform() === "freebsd" ? "-t 1 -c 3" : "-w 1 -i 0.2 -c 3"; -} - function throwError(err, httpResponse?): never { if (httpResponse) { httpResponse.writeHead(500, { Connection: "close" }); @@ -596,9 +591,9 @@ export function listener(request, response): void { } } else if (PING_REGEX.test(urlParts.pathname)) { const host = querystring.unescape(PING_REGEX.exec(urlParts.pathname)[1]); - const pingArgs = pingVariant(); - exec(`ping ${pingArgs} ${host}`, (err, stdout) => { + ping(host, (err, res, stdout) => { if (err) { + if (!res) throwError(err, response); response.writeHead(404, { "Cache-Control": "no-cache" }); response.end(`${err.name}: ${err.message}`); return; diff --git a/lib/ping.ts b/lib/ping.ts new file mode 100644 index 0000000..216cecc --- /dev/null +++ b/lib/ping.ts @@ -0,0 +1,89 @@ +/** + * Copyright 2013-2019 GenieACS Inc. + * + * This file is part of GenieACS. + * + * GenieACS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * GenieACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GenieACS. If not, see . + */ + +import { platform } from "os"; +import { exec } from "child_process"; + +interface Ping { + packetsTransmitted: number; + packetsReceived: number; + packetLoss: number; + min: number; + avg: number; + max: number; + mdev: number; +} + +export function ping( + host: string, + callback: (err, res?, stdout?) => void +): void { + let cmd: string, parseRegExp1: RegExp, parseRegExp2: RegExp; + switch (platform()) { + case "linux": + cmd = `ping -w 1 -i 0.2 -c 3 ${host}`; + parseRegExp1 = /(\d+) packets transmitted, (\d+) received, ([\d.]+)% packet loss[^]*([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+)/; + parseRegExp2 = /(\d+) packets transmitted, (\d+) received, ([\d.]+)% packet loss/; + break; + + case "freebsd": + // Send a single packet because on FreeBSD only superuser can send + // packets that are only 200 ms apart. + cmd = `ping -t 1 -c 3 ${host}`; + parseRegExp1 = /(\d+) packets transmitted, (\d+) packets received, ([\d.]+)% packet loss\nround-trip min\/avg\/max\/stddev = ([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+) ms/; + parseRegExp2 = /(\d+) packets transmitted, (\d+) packets received, ([\d.]+)% packet loss/; + break; + + default: + return callback(new Error("Platform not supported")); + } + + exec(cmd, (err, stdout) => { + let parsed: Ping; + if (stdout) { + const m1 = stdout.match(parseRegExp1); + if (m1) { + parsed = { + packetsTransmitted: +m1[1], + packetsReceived: +m1[2], + packetLoss: +m1[3], + min: +m1[4], + avg: +m1[5], + max: +m1[6], + mdev: +m1[7] + }; + } else { + const m2 = stdout.match(parseRegExp2); + if (m2) { + parsed = { + packetsTransmitted: +m2[1], + packetsReceived: +m2[2], + packetLoss: +m2[3], + min: null, + avg: null, + max: null, + mdev: null + }; + } + } + } + + callback(err, parsed, stdout); + }); +} diff --git a/lib/ui/api-functions.ts b/lib/ui/api-functions.ts index 1383f44..28b8dd6 100644 --- a/lib/ui/api-functions.ts +++ b/lib/ui/api-functions.ts @@ -17,7 +17,6 @@ * along with GenieACS. If not, see . */ -import { exec } from "child_process"; import { ObjectID } from "mongodb"; import * as db from "./db"; import { del } from "../cache"; @@ -29,17 +28,6 @@ import { connectionRequest, deleteDevice } from "../api-functions"; -import * as os from "os"; - -function pingVariant(): string { - return os.platform() === "freebsd" ? "-t 1 -c 3" : "-w 1 -i 0.2 -c 3"; -} - -function pingOutRegex(): RegExp { - return os.platform() === "freebsd" - ? /(\d) packets transmitted, (\d) packets received, ([\d.%]+) packet loss\nround-trip min\/avg\/max\/stddev = ([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+) ms/ - : /(\d) packets transmitted, (\d) received, ([\d.%]+) packet loss[^]*([\d.]+)\/([\d.]+)\/([\d.]+)\/([\d.]+)/; -} async function deleteFault(id): Promise { const deviceId = id.split(":", 1)[0]; @@ -160,28 +148,6 @@ interface PingResponse { mdev: number; } -export function ping(host): Promise { - return new Promise((resolve, reject) => { - const pingArgs = pingVariant(); - exec(`ping ${pingArgs} ${host}`, (err, stdout) => { - if (err) return void reject(err); - - const m = stdout.match(pingOutRegex()); - if (!m) return void reject(new Error("Could not parse ping response")); - - resolve({ - packetsTransmitted: +m[1], - packetsReceived: +m[2], - packetLoss: m[3], - min: +m[4], - avg: +m[5], - max: +m[6], - mdev: +m[7] - }); - }); - }); -} - export async function putResource(resource, id, data): Promise { if (resource === "presets") { await db.putPreset(id, data); diff --git a/lib/ui/api.ts b/lib/ui/api.ts index 3afd16c..b78fd38 100644 --- a/lib/ui/api.ts +++ b/lib/ui/api.ts @@ -29,6 +29,7 @@ import { QueryOptions, Expression } from "../types"; import { generateSalt, hashPassword } from "../auth"; import { del } from "../cache"; import Authorizer from "../common/authorizer"; +import { ping } from "../ping"; const router = new Router(); export default router; @@ -551,13 +552,17 @@ router.post("/devices/:id/tags", async (ctx, next) => { }); router.get("/ping/:host", async ctx => { - try { - ctx.body = await apiFunctions.ping(ctx.params.host); - } catch (err) { - ctx.status = 404; - ctx.response.headers["Cache-Control"] = "no-cache"; - ctx.body = `${err.name}: ${err.message}`; - } + return new Promise(resolve => { + ping(ctx.params.host, (err, parsed) => { + if (parsed) { + ctx.body = parsed; + } else { + ctx.status = 500; + ctx.body = `${err.name}: ${err.message}`; + } + resolve(); + }); + }); }); router.put("/users/:id/password", async (ctx, next) => {