Compare commits
53 Commits
f_influx-c
...
v2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| c3cd6393d4 | |||
| 8eef17fd4c | |||
| c97137f4a7 | |||
| 39350932a4 | |||
| 1e37f35e38 | |||
| d0be44c1af | |||
| a13d81e9c0 | |||
| 059c02e243 | |||
| 57cf6fb0a7 | |||
| 298a96bf16 | |||
| b98dff947d | |||
| a610f209d5 | |||
| 0f6c5b6b0e | |||
| cc9e4c7258 | |||
| 56ac283544 | |||
| 9095e21e6f | |||
| 6e05a0b45c | |||
| d14e469ef4 | |||
| 99a3e13d77 | |||
| b5c895674e | |||
| 86d2b8c1cf | |||
| 7ff6556d51 | |||
| 16388c73e5 | |||
| 8211f55b89 | |||
| c28bbaaada | |||
| 4ddbe3f06f | |||
| 0d84079ce1 | |||
| 1bf761970f | |||
| 5a0118aedd | |||
| 0709db0ddf | |||
| c27761322c | |||
| 45a11753de | |||
| d482001cdc | |||
| a681bbd2d2 | |||
| d9ee804c3b | |||
| e320d8670b | |||
| 2d824543d1 | |||
| 6e080907d1 | |||
| 91c3aca9e2 | |||
| 31ab10c3e1 | |||
| 8ff7211f0f | |||
| 2ae85ababb | |||
| 227ba127f8 | |||
| 8417de5756 | |||
| 54eadf3bc3 | |||
| e39ebaac23 | |||
| 54d627d469 | |||
| ca3c37be0f | |||
| 96b52e63a0 | |||
| 3c5e941cba | |||
| a468d7a57b | |||
| 4ad5eba7e0 | |||
| 2646c9787e |
10
Dockerfile
10
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:16
|
||||
FROM node:16-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
@@ -7,7 +7,13 @@ WORKDIR /usr/src/app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# remove development dependencies
|
||||
RUN npm prune --production
|
||||
|
||||
# Install required apk-packages & delete cache
|
||||
RUN apk update && apk add tcpdump && rm -rf /var/cache/apk/*
|
||||
|
||||
# Bundle app source
|
||||
COPY ./src/ .
|
||||
|
||||
CMD ["npm", "run"]
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -2,10 +2,10 @@
|
||||
"name": "rfmon-to-influx",
|
||||
"version": "1.0.0",
|
||||
"description": "Writing (mostly meta-) data received in Wireless-Monitor-Mode into an InfluxDB",
|
||||
"main": "src/main.js",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node src/main.js"
|
||||
"start": "node main.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
const PacketType = {
|
||||
Beacon: 'Beacon',
|
||||
ProbeRequest: 'ProbeRequest',
|
||||
ProbeResponse: 'ProbeResponse',
|
||||
Data: 'Data',
|
||||
RequestToSend: 'RequestToSend',
|
||||
ClearToSend: 'ClearToSend',
|
||||
Acknowledgment: 'Acknowledgment',
|
||||
BlockAcknowledgment: 'BlockAcknowledgment',
|
||||
NoData: 'NoData',
|
||||
Authentication: 'Authentication',
|
||||
AssociationRequest: 'AssociationRequest',
|
||||
AssociationResponse: 'AssociationResponse',
|
||||
Disassociation: 'Disassociation',
|
||||
Handshake: 'Handshake',
|
||||
Unknown: 'Unknown'
|
||||
}
|
||||
Beacon: "Beacon",
|
||||
ProbeRequest: "ProbeRequest",
|
||||
ProbeResponse: "ProbeResponse",
|
||||
Data: "Data",
|
||||
RequestToSend: "RequestToSend",
|
||||
ClearToSend: "ClearToSend",
|
||||
Acknowledgment: "Acknowledgment",
|
||||
BlockAcknowledgment: "BlockAcknowledgment",
|
||||
NoData: "NoData",
|
||||
Authentication: "Authentication",
|
||||
AssociationRequest: "AssociationRequest",
|
||||
AssociationResponse: "AssociationResponse",
|
||||
Disassociation: "Disassociation",
|
||||
Handshake: "Handshake",
|
||||
Unknown: "Unknown"
|
||||
};
|
||||
|
||||
const FlagType = {
|
||||
MoreFragments: "MoreFragments",
|
||||
@@ -23,7 +23,7 @@ const FlagType = {
|
||||
MoreData: "MoreData",
|
||||
Protected: "Protected",
|
||||
Order: "Order"
|
||||
}
|
||||
};
|
||||
|
||||
class Packet{
|
||||
timestampMicros;
|
||||
@@ -58,10 +58,10 @@ class ProbeRequestPacket extends PacketWithSSID{}
|
||||
class ProbeResponsePacket extends PacketWithSSID{}
|
||||
|
||||
const AuthenticationType = {
|
||||
OpenSystem_1: 'OpenSystem_1',
|
||||
OpenSystem_2: 'OpenSystem_2',
|
||||
Unknown: 'Unknown',
|
||||
}
|
||||
OpenSystem_1: "OpenSystem_1",
|
||||
OpenSystem_2: "OpenSystem_2",
|
||||
Unknown: "Unknown",
|
||||
};
|
||||
class AuthenticationPacket extends Packet{
|
||||
authenticationType;
|
||||
}
|
||||
@@ -77,11 +77,11 @@ class DisassociationPacket extends Packet{
|
||||
|
||||
|
||||
const HandshakeStage = {
|
||||
1: '1',
|
||||
2: '2',
|
||||
3: '3',
|
||||
4: '4'
|
||||
}
|
||||
1: "1",
|
||||
2: "2",
|
||||
3: "3",
|
||||
4: "4"
|
||||
};
|
||||
class HandshakePacket extends Packet{
|
||||
handshakeStage;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
function requireEnvVars(requiredEnv){
|
||||
// Ensure required ENV vars are set
|
||||
let unsetEnv = requiredEnv.filter((env) => !(typeof process.env[env] !== 'undefined'));
|
||||
let unsetEnv = requiredEnv.filter((env) => (typeof process.env[env] === "undefined"));
|
||||
|
||||
if (unsetEnv.length > 0) {
|
||||
return "Required ENV variables are not set: [" + unsetEnv.join(', ') + "]";
|
||||
return "Required ENV variables are not set: [" + unsetEnv.join(", ") + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const logger = require("./logger.js")("exec");
|
||||
|
||||
const { spawn } = require("child_process");
|
||||
const { parseArgsStringToArgv } = require('string-argv');
|
||||
const { parseArgsStringToArgv } = require("string-argv");
|
||||
|
||||
|
||||
function exec(cmd, options){
|
||||
|
||||
@@ -21,4 +21,4 @@ function bytesToHex(bytes) {
|
||||
module.exports = {
|
||||
hexToBytes,
|
||||
bytesToHex
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
const logger = require.main.require("./helper/logger.js")("influx-checks");
|
||||
|
||||
const Os = require("os");
|
||||
const { InfluxDB, Point } = require('@influxdata/influxdb-client')
|
||||
const Influx = require('@influxdata/influxdb-client-apis');
|
||||
const { InfluxDB, Point } = require("@influxdata/influxdb-client")
|
||||
const Influx = require("@influxdata/influxdb-client-apis");
|
||||
|
||||
|
||||
function checkHealth(influxDb){
|
||||
@@ -39,7 +39,7 @@ function checkBucket(influxDb, options){
|
||||
function checkWriteApi(influxDb, options){
|
||||
return new Promise((resolve, reject) => {
|
||||
const writeApi = influxDb.getWriteApi(options.org, options.bucket); // Get WriteAPI
|
||||
writeApi.writePoint(new Point("worker_connectionTest").tag("hostname", Os.hostname())) // Write point
|
||||
writeApi.writePoint(new Point("worker_connectionTest").tag("hostname", Os.hostname())); // Write point
|
||||
writeApi.close()
|
||||
.catch((err) => {
|
||||
logger.error("Could not get writeApi:");
|
||||
|
||||
46
src/helper/userHelper.js
Normal file
46
src/helper/userHelper.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// This file specifies functions to help a user with e.g. configuration-errors
|
||||
|
||||
function detectStreamData(stream, timeout = 5000){
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeoutHandler;
|
||||
if(timeout){
|
||||
timeoutHandler = setTimeout(() => {
|
||||
reject("timeout");
|
||||
remListeners();
|
||||
},
|
||||
timeout);
|
||||
}
|
||||
|
||||
function remListeners(){
|
||||
stream.removeListener("error", errorHandler);
|
||||
stream.removeListener("data", dataHandler);
|
||||
if(timeoutHandler) clearTimeout(timeoutHandler);
|
||||
}
|
||||
|
||||
function errorHandler(err) {
|
||||
remListeners();
|
||||
}
|
||||
function dataHandler(data) {
|
||||
resolve(data);
|
||||
remListeners();
|
||||
}
|
||||
|
||||
stream.on("error", errorHandler);
|
||||
stream.on("data", dataHandler);
|
||||
});
|
||||
}
|
||||
|
||||
function detectStreamsData(streams, timeout = 5000){
|
||||
let promises = [];
|
||||
streams.forEach((stream) => {
|
||||
promises.push(detectStreamData(stream, timeout));
|
||||
});
|
||||
return promises;
|
||||
}
|
||||
|
||||
|
||||
// Specify exports
|
||||
module.exports = {
|
||||
detectStreamData,
|
||||
detectStreamsData,
|
||||
};
|
||||
43
src/helper/wifiStateAnalyzer.js
Normal file
43
src/helper/wifiStateAnalyzer.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { HandshakeStage } = require.main.require("./dto/Packet.js");
|
||||
|
||||
function keyInfoFromRaw(keyInfoRaw) {
|
||||
return {
|
||||
"KeyDescriptorVersion": keyInfoRaw>>0 & 0b111,
|
||||
"KeyType": keyInfoRaw>>3 & 0b1,
|
||||
"KeyIndex": keyInfoRaw>>4 & 0b11,
|
||||
"Install": keyInfoRaw>>6 & 0b1,
|
||||
"KeyACK": keyInfoRaw>>7 & 0b1,
|
||||
"KeyMIC": keyInfoRaw>>8 & 0b1,
|
||||
"Secure": keyInfoRaw>>9 & 0b1,
|
||||
"Error": keyInfoRaw>>10 & 0b1,
|
||||
"Request": keyInfoRaw>>11 & 0b1,
|
||||
"EncryptedKeyData": keyInfoRaw>>12 & 0b1,
|
||||
"SMKMessage": keyInfoRaw>>13 & 0b1,
|
||||
};
|
||||
}
|
||||
|
||||
const HANDSHAKE_STAGE_KEYINFO = {
|
||||
"keys": ["Install", "KeyACK", "KeyMIC", "Secure"],
|
||||
"0100": HandshakeStage[1],
|
||||
"0010": HandshakeStage[2],
|
||||
"1111": HandshakeStage[3],
|
||||
"0011": HandshakeStage[4],
|
||||
};
|
||||
function handshakeStageFromKeyInfo(keyInfo){
|
||||
|
||||
// Extract compare-keys
|
||||
let keyData = "";
|
||||
for (const key of HANDSHAKE_STAGE_KEYINFO["keys"]) {
|
||||
keyData += keyInfo[key].toString();
|
||||
}
|
||||
|
||||
// Get and return stage
|
||||
return HANDSHAKE_STAGE_KEYINFO[keyData];
|
||||
}
|
||||
|
||||
|
||||
// Specify exports
|
||||
module.exports = {
|
||||
keyInfoFromRaw,
|
||||
handshakeStageFromKeyInfo,
|
||||
};
|
||||
97
src/main.js
97
src/main.js
@@ -1,9 +1,22 @@
|
||||
const logger = require("./helper/logger.js")("main");
|
||||
"use strict";
|
||||
const logFactory = require("./helper/logger.js");
|
||||
const logger = logFactory("main");
|
||||
|
||||
const { requireEnvVars } = require("./helper/env.js");
|
||||
const { exit } = require("process");
|
||||
const { InfluxDB } = require('@influxdata/influxdb-client');
|
||||
const InfluxChecks = require('./helper/influx-checks.js');
|
||||
const { exec } = require("./helper/exec.js");
|
||||
const Os = require("os");
|
||||
|
||||
const { InfluxDB } = require("@influxdata/influxdb-client");
|
||||
const InfluxChecks = require("./helper/influx-checks.js");
|
||||
|
||||
const { RegexBlockStream } = require("./streamHandler/RegexBlockStream.js");
|
||||
const { PacketStreamFactory } = require("./streamHandler/PacketStreamFactory.js");
|
||||
const { PacketInfluxPointFactory } = require("./streamHandler/PacketInfluxPointFactory.js");
|
||||
const { InfluxPointWriter } = require("./streamHandler/InfluxPointWriter.js");
|
||||
|
||||
const userHelper = require("./helper/userHelper.js");
|
||||
|
||||
|
||||
/// Setup ENVs
|
||||
const env = process.env;
|
||||
@@ -11,6 +24,7 @@ const env = process.env;
|
||||
{
|
||||
env.LOGLEVEL ??= "INFO";
|
||||
env.WIFI_INTERFACE ??= "wlan0";
|
||||
env.HOSTNAME ??= Os.hostname();
|
||||
}
|
||||
// Required vars
|
||||
let errorMsg = requireEnvVars([
|
||||
@@ -30,11 +44,11 @@ if(errorMsg){
|
||||
.then((res) => {return InfluxChecks.checkBucket(influxDb, {
|
||||
org: env.INFLUX_ORG,
|
||||
name: env.INFLUX_BUCKET
|
||||
})})
|
||||
});})
|
||||
.then((res) => {return InfluxChecks.checkWriteApi(influxDb, {
|
||||
org: env.INFLUX_ORG,
|
||||
bucket: env.INFLUX_BUCKET
|
||||
})})
|
||||
});})
|
||||
.catch((err) => {
|
||||
if(err) {
|
||||
logger.error("Error whilst checking influx:");
|
||||
@@ -44,6 +58,79 @@ if(errorMsg){
|
||||
exit(1);
|
||||
});
|
||||
|
||||
logger.debug("Get WriteApi & set default-hostname to", `'${env.HOSTNAME}'`);
|
||||
const influxWriteApi = influxDb.getWriteApi(env.INFLUX_ORG, env.INFLUX_BUCKET, "us");
|
||||
influxWriteApi.useDefaultTags({"hostname": env.HOSTNAME});
|
||||
logger.info("Influx ok");
|
||||
|
||||
logger.info("Starting tcpdump..");
|
||||
const TCPDUMP_BASECMD = "tcpdump -vvv -e -n -X -s0 -i";
|
||||
let cmd = `${TCPDUMP_BASECMD} ${env.WIFI_INTERFACE}`;
|
||||
|
||||
let proc = exec(cmd);
|
||||
logger.debug("Creating & Attaching streams..");
|
||||
let regexBlockStream = new RegexBlockStream(/^\d{2}:\d{2}:\d{2}.\d{6}.*(\n( {4,8}|\t\t?).*)+\n/gm);
|
||||
let packetStreamFactory = new PacketStreamFactory();
|
||||
let packetInfluxPointFactory = new PacketInfluxPointFactory();
|
||||
let influxPointWriter = new InfluxPointWriter(influxWriteApi);
|
||||
proc.stdout
|
||||
.setEncoding("utf8")
|
||||
.pipe(regexBlockStream)
|
||||
.pipe(packetStreamFactory)
|
||||
.pipe(packetInfluxPointFactory)
|
||||
.pipe(influxPointWriter);
|
||||
|
||||
logger.debug("Attaching error-logger..");
|
||||
const loggerTcpdump = logFactory("tcpdump");
|
||||
proc.stderr.setEncoding("utf8").on("data", (data) => {
|
||||
if(!data.match(/^(tcpdump: )?listening on /i) || !data.match(/^\d+ packets captured/i)) { // Catch start-error
|
||||
loggerTcpdump.debug(data);
|
||||
}
|
||||
else loggerTcpdump.error(data);
|
||||
});
|
||||
|
||||
// FIXME: This is a hacky workaround to not let errors from subprocess bubble up and terminate our process
|
||||
regexBlockStream.on("error", (err) => {});
|
||||
|
||||
proc.on("error", (err) => {
|
||||
loggerTcpdump.error(err);
|
||||
});
|
||||
|
||||
const loggerPacketStream = logFactory("PacketStreamFactory");
|
||||
userHelper.detectStreamData(proc.stdout, 10000) // Expect tcpdump-logs to have data after max. 10s
|
||||
.then(() => {
|
||||
loggerTcpdump.debug("Got first data");
|
||||
userHelper.detectStreamData(packetStreamFactory, 10000) // Expect then to have packets after further 10s
|
||||
.then(() => {
|
||||
loggerPacketStream.debug("Got first packet");
|
||||
})
|
||||
.catch((err) => {
|
||||
if(err == "timeout") loggerPacketStream.warn("No packets");
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if(err == "timeout") loggerTcpdump.warn("No data after 10s! Wrong configuration?");
|
||||
});
|
||||
|
||||
logger.debug("Attaching exit-handler..");
|
||||
proc.on("exit", (code) => {
|
||||
loggerTcpdump.debug(`tcpdump exited code: ${code}`);
|
||||
if (code) {
|
||||
loggerTcpdump.fatal(`tcpdump exited with non-zero code: ${code}`);
|
||||
exit(1);
|
||||
}
|
||||
logger.info("Shutdown");
|
||||
exit(0);
|
||||
});
|
||||
|
||||
// Handle stop-signals for graceful shutdown
|
||||
function shutdownReq() {
|
||||
logger.info("Shutdown request received..");
|
||||
logger.debug("Stopping subprocess tcpdump, then exiting myself..");
|
||||
proc.kill(); // Kill process (send SIGTERM), then upper event-handler will stop self
|
||||
}
|
||||
process.on("SIGTERM", shutdownReq);
|
||||
process.on("SIGINT", shutdownReq);
|
||||
|
||||
logger.info("Startup complete");
|
||||
})();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const logger = require.main.require("./helper/logger.js")("InfluxPointWriter");
|
||||
const { Writable } = require('stream');
|
||||
const {InfluxDB, Point, HttpError} = require('@influxdata/influxdb-client')
|
||||
const { Writable } = require("stream");
|
||||
const { WriteApi } = require("@influxdata/influxdb-client");
|
||||
|
||||
/**
|
||||
* Get points and write them into influx
|
||||
@@ -8,16 +8,13 @@ const {InfluxDB, Point, HttpError} = require('@influxdata/influxdb-client')
|
||||
class InfluxPointWriter extends Writable{
|
||||
/**
|
||||
*
|
||||
* @param {InfluxDB} influxDb InfluxDb
|
||||
* @param {string} org Organization to use
|
||||
* @param {string} bucket Bucket to use
|
||||
* @param {Partial<WriteOptions>} options Options for WriteApi
|
||||
* @param {WriteApi} writeApi WriteAPI from InfluxDB instance
|
||||
*/
|
||||
constructor(influxDb, org, bucket, options){
|
||||
constructor(writeApi){
|
||||
super({
|
||||
objectMode: true
|
||||
});
|
||||
this._api = influxDb.getWriteApi(org, bucket, 'us', options);
|
||||
this._api = writeApi;
|
||||
}
|
||||
|
||||
_write(point, encoding, next){
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
const logger = require.main.require("./helper/logger.js")("PacketStreamFactory");
|
||||
const { Transform } = require('stream');
|
||||
const {Point} = require('@influxdata/influxdb-client')
|
||||
const { Transform } = require("stream");
|
||||
const {Point} = require("@influxdata/influxdb-client");
|
||||
|
||||
/** Keys to always use as tags */
|
||||
const TAG_LIST = [
|
||||
"srcMac",
|
||||
"dstMac",
|
||||
"srcmac",
|
||||
"dstmac",
|
||||
"bssid",
|
||||
"frequency",
|
||||
"flags",
|
||||
"packettype",
|
||||
];
|
||||
|
||||
/** Measurement-name and corresponding field-key */
|
||||
const MEASUREMENT_MAP = new Map([
|
||||
["Signal", "signal"],
|
||||
["PayloadSize", "payloadSize"],
|
||||
["DataRate", "dataRate"],
|
||||
["SSID", "ssid"],
|
||||
["AuthenticationType", "authenticationType"],
|
||||
["AssociationSuccess", "associationIsSuccessful"],
|
||||
["DisassociationReason", "disassociationReason"],
|
||||
["rfmon_signal_dbm", "signal"],
|
||||
["rfmon_payloadsize_bytes", "payloadSize"],
|
||||
["rfmon_datarate_bytes", "dataRate"],
|
||||
["rfmon_ssid_names", "ssid"],
|
||||
["rfmon_authenticationtype_info", "authenticationType"],
|
||||
["rfmon_associationsuccess_bools", "associationIsSuccessful"],
|
||||
["rfmon_disassociationreason_info", "disassociationReason"],
|
||||
["rfmon_handshakestage_info", "handshakeStage"],
|
||||
]);
|
||||
|
||||
|
||||
@@ -37,15 +39,18 @@ class PacketInfluxPointFactory extends Transform{
|
||||
_transform(packet, encoding, next){
|
||||
// Create measurements
|
||||
MEASUREMENT_MAP.forEach((objKey, measurement) => {
|
||||
if(!Object.keys(packet).includes(objKey)) return;
|
||||
if(packet[objKey] == null) return;
|
||||
|
||||
let point = new Point(measurement); // Create point
|
||||
|
||||
// Set tags
|
||||
TAG_LIST.filter(tag => Object.keys(packet).includes(tag))
|
||||
.forEach(tag => point.tag(tag, packet[tag]));
|
||||
TAG_LIST.filter(tag => Object.keys(packet).includes(tag)) // Filter tags available on object
|
||||
.filter(tag => packet[tag] != null) // Filter tags not falsy on object
|
||||
.forEach(tag => {
|
||||
tagObjectRecursively(point, tag, packet[tag]);
|
||||
});
|
||||
|
||||
point.setField('value', packet[objKey]); // Set field
|
||||
point.setField("value", packet[objKey]); // Set field
|
||||
|
||||
this.push(point); // Push point into stream
|
||||
});
|
||||
@@ -54,17 +59,26 @@ class PacketInfluxPointFactory extends Transform{
|
||||
}
|
||||
}
|
||||
|
||||
function tagObjectRecursively(point, tag, field, suffix = ""){
|
||||
if(typeof(field) == "object"){
|
||||
// TODO: Convert boolean-arrays like "packet.flags" to key: value
|
||||
Object.entries(field).map(([key, value]) => {
|
||||
tagObjectRecursively(point, tag, value, `_${key}${suffix}`);
|
||||
});
|
||||
}
|
||||
else point.tag(tag+suffix, field);
|
||||
}
|
||||
|
||||
/** Mapping for type -> field-method */
|
||||
const POINT_FIELD_TYPE = new Map([
|
||||
['boolean', function(key, value){ return this.booleanField(key, value); }],
|
||||
['number', function(key, value){ return this.intField(key, value); }],
|
||||
['string', function(key, value){ return this.stringField(key, value); }],
|
||||
["boolean", function(key, value){ return this.booleanField(key, value); }],
|
||||
["number", function(key, value){ return this.intField(key, value); }],
|
||||
["string", function(key, value){ return this.stringField(key, value); }],
|
||||
]);
|
||||
Point.prototype.setField = function(key, value){
|
||||
let setField = POINT_FIELD_TYPE.get(typeof value);
|
||||
return setField.apply(this, [key, value]);
|
||||
}
|
||||
};
|
||||
|
||||
// Specify exports
|
||||
module.exports = {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const logger = require.main.require("./helper/logger.js")("PacketStreamFactory");
|
||||
const { Transform } = require('stream');
|
||||
const { Transform } = require("stream");
|
||||
const { DateTime } = require("luxon");
|
||||
const { PacketType, FlagType, Packet, PacketWithSSID, BeaconPacket, ProbeRequestPacket, ProbeResponsePacket, AuthenticationPacket, AuthenticationType, AssociationResponsePacket, DisassociationPacket, HandshakePacket, HandshakeStage } = require.main.require('./dto/Packet.js');
|
||||
const { PacketType, FlagType, Packet, PacketWithSSID, BeaconPacket, ProbeRequestPacket, ProbeResponsePacket, AuthenticationPacket, AuthenticationType, AssociationResponsePacket, DisassociationPacket, HandshakePacket, HandshakeStage } = require.main.require("./dto/Packet.js");
|
||||
const hexConv = require.main.require("./helper/hexConverter.js");
|
||||
const wifiStateAnalyser = require.main.require("./helper/wifiStateAnalyzer.js");
|
||||
|
||||
const PACKET_TYPE_MAP = {
|
||||
"Beacon": PacketType.Beacon,
|
||||
@@ -19,20 +20,20 @@ const PACKET_TYPE_MAP = {
|
||||
"Disassociation:": PacketType.Disassociation,
|
||||
"EAPOL": PacketType.Handshake,
|
||||
};
|
||||
const PACKET_TYPES_REGEX = Object.keys(PACKET_TYPE_MAP).join('|');
|
||||
const PACKET_TYPES_REGEX = Object.keys(PACKET_TYPE_MAP).join("|");
|
||||
|
||||
const AUTHENTICATION_TYPE_MAP = {
|
||||
"(Open System)-1": AuthenticationType.OpenSystem_1,
|
||||
"(Open System)-2": AuthenticationType.OpenSystem_2,
|
||||
}
|
||||
};
|
||||
|
||||
const FLAG_TYPE_MAP = {
|
||||
"Retry": FlagType.Retry,
|
||||
"Pwr Mgmt": FlagType.PwrMgt,
|
||||
"More Data": FlagType.MoreData,
|
||||
"Protected": FlagType.Protected,
|
||||
}
|
||||
const FLAG_TYPE_MAPS_REGEX = Object.keys(FLAG_TYPE_MAP).join('|');
|
||||
};
|
||||
const FLAG_TYPE_MAPS_REGEX = Object.keys(FLAG_TYPE_MAP).join("|");
|
||||
|
||||
/**
|
||||
* Read data from text-blocks and convert them to Packet
|
||||
@@ -48,8 +49,8 @@ class PacketStreamFactory extends Transform{
|
||||
_transform(chunk, encoding, next){
|
||||
let packet = new Packet();
|
||||
|
||||
const lines = chunk.split('\n');
|
||||
const header = lines.splice(0, 1)[0]; // Grab first line, 'lines' is now the payload
|
||||
const lines = chunk.split("\n");
|
||||
const header = lines.splice(0, 1)[0]; // Grab first line, "lines" is now the payload
|
||||
packet = this._handleHeader(packet, header);
|
||||
packet = this._handlePayload(packet, lines);
|
||||
|
||||
@@ -61,21 +62,26 @@ class PacketStreamFactory extends Transform{
|
||||
packet.timestampMicros = DateTime.fromISO(data.slice(0, 12)).toSeconds() + data.slice(12, 15)/1000000;
|
||||
|
||||
// Find flags
|
||||
data.match(data.match(new RegExp('(?<=^|\\s)('+ FLAG_TYPE_MAPS_REGEX +')(?=$|\\s)', 'ig'))
|
||||
data.match(data.match(new RegExp("(?<=^|\\s)("+ FLAG_TYPE_MAPS_REGEX +")(?=$|\\s)", "ig"))
|
||||
?.forEach(match => packet.flags[FLAG_TYPE_MAP[match]] = true) // Set them to true in flags
|
||||
);
|
||||
|
||||
packet.dataRate = Number(data.match(/(?<=^|\s)[0-9]+(\.[0-9]+)?(?=\sMb\/?s($|\s))/i)?.[0]) || null;
|
||||
packet.frequency = Number(data.match(/(?<=^|\s)[0-9]{4}(?=\sMHz($|\s))/i)?.[0]) || null;
|
||||
packet.dataRate = Number(data.match(/(?<=^|\s)\d+(\.\d+)?(?=\sMb\/?s($|\s))/i)?.[0]) || null;
|
||||
packet.frequency = Number(data.match(/(?<=^|\s)\d{4}(?=\sMHz($|\s))/i)?.[0]) || null;
|
||||
|
||||
packet.durationMicros = Number(data.match(/(?<=^|\s)[0-9]{1,4}(?=us($|\s))/i)?.[0]) || null;
|
||||
packet.durationMicros = Number(data.match(/(?<=^|\s)\d{1,4}(?=us($|\s))/i)?.[0]) || null;
|
||||
|
||||
packet.signal = Number(data.match(/(?<=^|\s)-[0-9]{2,3}(?=dBm\sSignal($|\s))/i)?.[0]) || null;
|
||||
packet.signal = Number(data.match(/(?<=^|\s)-\d{2,3}(?=dBm\sSignal($|\s))/i)?.[0]) || null;
|
||||
|
||||
let packetTypeStr = data.match(new RegExp('(?<=^|\\s)('+ PACKET_TYPES_REGEX +')(?=$|\\s)', 'i'))?.[0];
|
||||
packet.packetType = packetTypeStr? PACKET_TYPE_MAP[packetTypeStr]:
|
||||
data.match(/(SA|TA|DA|RA|BSSID):.{17}\s*$/i)? PacketType.NoData:
|
||||
PacketType.Unknown;
|
||||
let packetTypeStr = data.match(new RegExp("(?<=^|\\s)("+ PACKET_TYPES_REGEX +")(?=$|\\s)", "i"))?.[0];
|
||||
if(packetTypeStr)
|
||||
packet.packetType = PACKET_TYPE_MAP[packetTypeStr];
|
||||
else if(data.match(/(SA|TA|DA|RA|BSSID):.{17}\s*$/i)){
|
||||
packet.packetType = PacketType.NoData;
|
||||
}
|
||||
else {
|
||||
packet.packetType = PacketType.Unknown;
|
||||
}
|
||||
|
||||
packet.srcMac = data.match(/(?<=(^|\s)(SA|TA):).{17}(?=$|\s)/i)?.[0] ?? null;
|
||||
|
||||
@@ -91,12 +97,12 @@ class PacketStreamFactory extends Transform{
|
||||
case PacketType.ProbeResponse:
|
||||
case PacketType.AssociationRequest:
|
||||
newPacket = new PacketWithSSID();
|
||||
newPacket.ssid = data.match(new RegExp('(?<=(^|\\s)'+ packetTypeStr +'\\s\\().{0,32}(?=\\)($|\\s))', 'i'))?.[0] ?? null;
|
||||
newPacket.ssid = data.match(new RegExp("(?<=(^|\\s)"+ packetTypeStr +"\\s\\().{0,32}(?=\\)($|\\s))", "i"))?.[0] ?? null;
|
||||
break;
|
||||
|
||||
case PacketType.Authentication:
|
||||
newPacket = new AuthenticationPacket();
|
||||
newPacket.authenticationType = AUTHENTICATION_TYPE_MAP[data.match(/(?<=(^|\s)Authentication\s).{3,}(?=\:(\s|$))/i)[0]] ?? AuthenticationType.Unknown;
|
||||
newPacket.authenticationType = AUTHENTICATION_TYPE_MAP[data.match(/(?<=(^|\s)Authentication\s).{3,}(?=:(\s|$))/i)[0]] ?? AuthenticationType.Unknown;
|
||||
break;
|
||||
|
||||
case PacketType.AssociationResponse:
|
||||
@@ -115,41 +121,26 @@ class PacketStreamFactory extends Transform{
|
||||
}
|
||||
|
||||
_handlePayload(packet, data){
|
||||
data = data.join('');
|
||||
data = data.join("");
|
||||
|
||||
// Get payload-Hex-Data. If there is no data: empty
|
||||
packet.payloadData = hexConv.hexToBytes(data.match(/(?<=\s)([A-F0-9]{1,4}(?=\s))/igm)?.join('') ?? '');
|
||||
packet.payloadData = hexConv.hexToBytes(data.match(/(?<=\s)([A-F0-9]{1,4}(?=\s))/igm)?.join("") ?? "");
|
||||
packet.payloadData.splice(packet.payloadData.length-4, 4); // Remove FrameCheck sequence
|
||||
|
||||
// Cover special cases with more data
|
||||
let newPacket;
|
||||
switch(packet.packetType){
|
||||
case PacketType.Handshake:
|
||||
case PacketType.Handshake: {
|
||||
newPacket = new HandshakePacket();
|
||||
|
||||
// Read key-information
|
||||
const keyInfoRaw = (packet.payloadData[0x5]<<0x8) + packet.payloadData[0x6];
|
||||
const keyInfo = {
|
||||
"KeyDescriptorVersion": keyInfoRaw>>0 & 0b111,
|
||||
"KeyType": keyInfoRaw>>3 & 0b1,
|
||||
"KeyIndex": keyInfoRaw>>4 & 0b11,
|
||||
"Install": keyInfoRaw>>6 & 0b1,
|
||||
"KeyACK": keyInfoRaw>>7 & 0b1,
|
||||
"KeyMIC": keyInfoRaw>>8 & 0b1,
|
||||
"Secure": keyInfoRaw>>9 & 0b1,
|
||||
"Error": keyInfoRaw>>10 & 0b1,
|
||||
"Request": keyInfoRaw>>11 & 0b1,
|
||||
"EncryptedKeyData": keyInfoRaw>>12 & 0b1,
|
||||
"SMKMessage": keyInfoRaw>>13 & 0b1,
|
||||
};
|
||||
const keyInfo = wifiStateAnalyser.keyInfoFromRaw(keyInfoRaw); // Convert
|
||||
|
||||
newPacket.handshakeStage = (!keyInfo.Install && keyInfo.KeyACK && !keyInfo.KeyMIC && !keyInfo.Secure)? HandshakeStage[1] :
|
||||
(!keyInfo.Install && !keyInfo.KeyACK && keyInfo.KeyMIC && !keyInfo.Secure)? HandshakeStage[2] :
|
||||
( keyInfo.Install && keyInfo.KeyACK && keyInfo.KeyMIC && keyInfo.Secure)? HandshakeStage[3] :
|
||||
(!keyInfo.Install && !keyInfo.KeyACK && keyInfo.KeyMIC && keyInfo.Secure)? HandshakeStage[4] :
|
||||
null;
|
||||
newPacket.handshakeStage = wifiStateAnalyser.handshakeStageFromKeyInfo(keyInfo); // Get stage
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(newPacket) packet = Object.assign(newPacket, packet);
|
||||
|
||||
return packet;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const logger = require.main.require("./helper/logger.js")("RegexBlockStream");
|
||||
const { Transform } = require('stream')
|
||||
const { Transform } = require("stream");
|
||||
|
||||
/**
|
||||
* Matches whole blocks as regex and passes them on
|
||||
@@ -13,7 +13,7 @@ class RegexBlockStream extends Transform{
|
||||
* @param {RegExp} matcher Block-match
|
||||
* @param {boolean} withholdLastBlock When true, the last matches block will not be submitted to prevent submitting incomplete blocks.
|
||||
* @param {boolean} matchAllOnFlush (Only in combination with withholdLastBlock) When enabled, the buffer will be matched on last time on _flush (stream deconstruction) and write any, also incomplete, blocks
|
||||
* @remarks WARNING: It should match a clean-block (including e.g. newline)! Otherwise buffer will get dirty and use more and more ressources.
|
||||
* @remarks WARNING: It should match a clean-block (including e.g. newline)! Otherwise buffer will get dirty and use more and more resources.
|
||||
*/
|
||||
constructor(matcher, withholdLastBlock = true, matchAllOnFlush = false){
|
||||
super({
|
||||
@@ -27,7 +27,7 @@ class RegexBlockStream extends Transform{
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, next){
|
||||
chunk = this.readableBuffer.length? this.readableBuffer.join('') + chunk: chunk; // Add previous buffer to current chunk
|
||||
chunk = this.readableBuffer.length? this.readableBuffer.join("") + chunk: chunk; // Add previous buffer to current chunk
|
||||
this.readableBuffer.length && this.readableBuffer.clear(); // Clear buffer once we read it
|
||||
|
||||
let matches = chunk.match(this.matcher); // Match
|
||||
@@ -44,17 +44,17 @@ class RegexBlockStream extends Transform{
|
||||
if(matches){
|
||||
matches.forEach((match) => {
|
||||
this.push(match); // Write match to stream
|
||||
if(chunk) chunk = chunk.replace(match, ''); // Remove match from chunks
|
||||
if(chunk) chunk = chunk.replace(match, ""); // Remove match from chunks
|
||||
});
|
||||
}
|
||||
if(chunk) return chunk;
|
||||
}
|
||||
|
||||
_flush(next){
|
||||
if(matchAllOnFlush){ // When requested, we'll match one last time over the remaining buffer
|
||||
let chunk = this.readableBuffer.join('');
|
||||
if(this.matchAllOnFlush){ // When requested, we'll match one last time over the remaining buffer
|
||||
let chunk = this.readableBuffer.join("");
|
||||
let matches = chunk.match(this.matcher); // Match remaining buffer
|
||||
_writeMatches(matches); // Write matches including last element
|
||||
this._writeMatches(matches); // Write matches including last element
|
||||
}
|
||||
|
||||
next(); // Tell system we are done
|
||||
|
||||
Reference in New Issue
Block a user