First commit

This commit is contained in:
Fabian Stamm 2020-04-29 15:33:35 +02:00
commit 65068b7c4d
7 changed files with 2313 additions and 0 deletions

10
.editorconfig Normal file
View File

@ -0,0 +1,10 @@
root=true
[*]
charset = utf-8
end_of_line = lf
indent_size = 3
indent_style = space
insert_final_newline = true
[*.yaml]
indent_size = 2

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
lib/
connectivity-exporter

14
example.config.yaml Normal file
View File

@ -0,0 +1,14 @@
targets:
- host: 127.0.0.1
icmp: true
tcp:
- 1234
- host: 1.1.1.1
traceroute: true
icmp: true
tcp:
- 53
- host: 8.8.8.8
icmp: true
tcp:
- 53

1983
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "@hibas123/connectivity-exporter",
"version": "1.0.0",
"description": "",
"main": "lib/index.js",
"bin": "lib/index.js",
"scripts": {
"build": "tsc && pkg -t node12-linux .",
"dev": "nodemon -e ts --exec ts-node src/index.ts -- --config example.config.yaml"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^13.13.4",
"@types/ping": "^0.2.0",
"nodemon": "^2.0.3",
"pkg": "^4.4.8",
"ts-node": "^8.9.1",
"typescript": "^3.8.3"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"argparse": "^1.0.10",
"nodejs-traceroute": "^1.2.0",
"ping": "^0.2.3",
"prom-client": "^12.0.0",
"yaml": "^1.9.2"
}
}

264
src/index.ts Normal file
View File

@ -0,0 +1,264 @@
import * as client from "prom-client";
import * as yaml from "yaml";
import { readFileSync, watchFile } from "fs";
import * as net from "net";
import * as ping from "ping";
import * as Traceroute from "nodejs-traceroute";
import * as argparse from "argparse";
import { strict as assert } from "assert";
const argp = new argparse.ArgumentParser();
argp.addArgument(["-c", "--config"], {
help: "Path of the config file",
type: String,
required: true,
dest: "config",
});
argp.addArgument(["-p", "--port"], {
help: "The port to listen at",
type: Number,
defaultValue: 9285,
dest: "port",
});
const args: {
config: string;
port: number;
} = argp.parseArgs();
async function testPing(host: string) {
let probe = await ping.promise.probe(host, {
timeout: 5,
});
if (!probe.alive) return -1;
return Number(probe.avg);
}
async function testTraceroute(host: string) {
const tracer = new Traceroute();
return new Promise((yes, no) => {
console.log("Tracepath to:", host);
tracer
.on("pid", (pid) => {
console.log(`pid: ${pid}`);
})
.on("destination", (destination) => {
console.log(`destination: ${destination}`);
})
.on("hop", (hop) => {
console.log(`hop: ${JSON.stringify(hop)}`);
})
.on("close", (code) => {
console.log(`close: code ${code}`);
})
.on("error", no);
tracer.trace("github.com");
});
}
async function testTCP(host: string, port: number) {
return new Promise((yes, no) => {
console.log("Checking TCP", host, port);
let socket = net.connect(port, host);
socket.on("error", () => {
yes(false);
socket.destroy();
});
socket.on("connect", () => {
yes(true);
socket.destroy();
});
});
}
let getMetrics: () => Promise<string>;
type IConfig = {
targets: {
host: string;
icmp: boolean;
traceroute: boolean;
tcp: (number | { port: number })[];
}[];
};
function load() {
let config: IConfig;
try {
console.log("Loading: ", args.config);
config = yaml.parse(readFileSync(args.config, "utf8"));
// Verify config
assert(typeof config === "object", "Config is invalid!");
assert(typeof config.targets !== "undefined", "Field targets required!");
assert(Array.isArray(config.targets), "Field targets must be an array");
config.targets.forEach((target, idx) => {
assert(typeof target === "object", "Each target must be an object!");
assert(
typeof target.host === "string",
`The host must be a string in the ${idx} target`
);
assert(
typeof target.icmp === "undefined" ||
typeof target.icmp === "boolean",
`Field icmp must be undefined or boolean in target ${target.host}`
);
assert(
typeof target.traceroute === "undefined" ||
typeof target.traceroute === "boolean",
`Field traceroute must be undefined or boolean in target ${target.host}`
);
assert(
typeof target.tcp === "undefined" || Array.isArray(target.tcp),
`Field tcp must be undefined or array in target ${target.host}`
);
if (target.tcp) {
target.tcp.forEach((tcp) => {
assert(
typeof tcp === "number" || typeof tcp === "object",
"Each entry in TCP has to be a number or an object"
);
if (typeof tcp === "object") {
assert(
typeof tcp.port === "number",
"The port in TCP is required!"
);
}
});
}
});
} catch (err) {
console.log(err);
return;
}
console.log("Config verified. Putting in production");
// Enable config
const reg = new client.Registry();
const reachable = new client.Gauge({
name: "connectivity_ping_reachable",
help: "Checks if the target is pingable by this host",
labelNames: ["target"],
registers: [reg],
});
const pingTimes = new client.Gauge({
name: "connectivity_ping_time",
help: "Contains the current ping time from this host to target",
labelNames: ["target"],
registers: [reg],
});
const hopCount = new client.Gauge({
name: "connectivity_hops_count",
help: "The number of hops between this host and the target",
labelNames: ["target"],
registers: [reg],
});
const tcpCheck = new client.Gauge({
name: "connectivity_tcp_available",
help: "Checks if connectivity to specified tcp port is available",
labelNames: ["target", "port"],
registers: [reg],
});
getMetrics = async () => {
console.log("Running");
await Promise.all(
config.targets.map(async (target) => {
let jobs = [];
if (target.icmp) {
jobs.push(
testPing(target.host).then((ping) => {
reachable.set(
{
target: target.host,
},
ping >= 0 ? 1 : 0
);
pingTimes.set(
{
target: target.host,
},
ping as any
);
})
);
}
if (target.traceroute && false) {
//Currently not working
jobs.push(testTraceroute(target.host).then(() => {}));
}
if (target.tcp) {
jobs.push(
...target.tcp.map((check) =>
testTCP(
target.host,
typeof check === "number" ? check : check.port
).then((avail) => {
tcpCheck.set(
{
target: target.host,
port:
typeof check === "number" ? check : check.port,
},
avail ? 1 : 0
);
})
)
);
}
return Promise.all(jobs);
})
);
return reg.metrics();
};
}
process.on("SIGHUP", () => load());
watchFile(args.config, load);
load();
import * as http from "http";
import { URL } from "url";
http
.createServer(async (req, res) => {
const url = new URL(req.url, "http://127.0.0.1");
if (url.pathname == "/metrics") {
if (getMetrics)
getMetrics()
.then((data) => res.end(data))
.catch((err) => {
console.log("Error", err);
res.statusCode = 400;
res.end("Something went wrong!");
});
else {
res.statusCode = 500;
res.end("Not ready!");
}
} else {
res.end("<a href='/metrics'>Metrics</a>");
}
})
.listen(args.port, () => {
console.log("Listening at:", args.port);
});

9
tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"target": "ES2020",
"outDir": "lib/"
},
"include": ["src/"]
}