Compare commits
21 Commits
f_influx-c
...
f_docker
| Author | SHA1 | Date | |
|---|---|---|---|
| 720fbbe75b | |||
| a681bbd2d2 | |||
| d9ee804c3b | |||
| e320d8670b | |||
| 2d824543d1 | |||
| 6e080907d1 | |||
| 91c3aca9e2 | |||
| 31ab10c3e1 | |||
| 8ff7211f0f | |||
| 2ae85ababb | |||
| 227ba127f8 | |||
| 8417de5756 | |||
| 54eadf3bc3 | |||
| e39ebaac23 | |||
| 54d627d469 | |||
| ca3c37be0f | |||
| 96b52e63a0 | |||
| 3c5e941cba | |||
| a468d7a57b | |||
| 4ad5eba7e0 | |||
| 2646c9787e |
@@ -7,7 +7,11 @@ WORKDIR /usr/src/app
|
|||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get -y install \
|
||||||
|
tcpdump
|
||||||
|
|
||||||
# Bundle app source
|
# Bundle app source
|
||||||
COPY ./src/ .
|
COPY ./src/ .
|
||||||
|
|
||||||
CMD ["npm", "run"]
|
CMD ["npm", "run", "start"]
|
||||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
rfmon:
|
||||||
|
container_name: rfmon
|
||||||
|
image: # TODO: Add image
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
- LOGLEVEL=DEBUG
|
||||||
|
- WIFI_INTERFACE=wlan0
|
||||||
|
- INFLUX_URL=http://influxdb:8086/
|
||||||
|
- INFLUX_TOKEN=
|
||||||
|
- INFLUX_ORG=
|
||||||
|
- INFLUX_BUCKET=
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
"name": "rfmon-to-influx",
|
"name": "rfmon-to-influx",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Writing (mostly meta-) data received in Wireless-Monitor-Mode into an InfluxDB",
|
"description": "Writing (mostly meta-) data received in Wireless-Monitor-Mode into an InfluxDB",
|
||||||
"main": "src/main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "node src/main.js"
|
"start": "node main.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
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,
|
||||||
|
};
|
||||||
38
src/main.js
38
src/main.js
@@ -1,10 +1,18 @@
|
|||||||
|
"use strict";
|
||||||
const logger = require("./helper/logger.js")("main");
|
const logger = require("./helper/logger.js")("main");
|
||||||
|
|
||||||
const { requireEnvVars } = require("./helper/env.js");
|
const { requireEnvVars } = require("./helper/env.js");
|
||||||
const { exit } = require("process");
|
const { exit } = require("process");
|
||||||
|
const { exec } = require("./helper/exec.js");
|
||||||
|
|
||||||
const { InfluxDB } = require('@influxdata/influxdb-client');
|
const { InfluxDB } = require('@influxdata/influxdb-client');
|
||||||
const InfluxChecks = require('./helper/influx-checks.js');
|
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");
|
||||||
|
|
||||||
/// Setup ENVs
|
/// Setup ENVs
|
||||||
const env = process.env;
|
const env = process.env;
|
||||||
// Defaults
|
// Defaults
|
||||||
@@ -46,4 +54,34 @@ if(errorMsg){
|
|||||||
|
|
||||||
logger.info("Influx ok");
|
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..");
|
||||||
|
proc.stdout
|
||||||
|
.setEncoding("utf8")
|
||||||
|
.pipe(new RegexBlockStream(/^\d{2}:\d{2}:\d{2}.\d{6}.*(\n( {4,8}|\t\t?).*)+\n/gm))
|
||||||
|
.pipe(new PacketStreamFactory())
|
||||||
|
.pipe(new PacketInfluxPointFactory())
|
||||||
|
.pipe(new InfluxPointWriter(influxDb, env.INFLUX_ORG, env.INFLUX_BUCKET));
|
||||||
|
|
||||||
|
logger.debug("Attaching error-logger..");
|
||||||
|
proc.stderr.setEncoding("utf8").on("data", (data) => {
|
||||||
|
logger.error(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("Attaching exit-handler..");
|
||||||
|
proc.on("exit", (code) => {
|
||||||
|
logger.info(`tcpdump exited code: ${code}`);
|
||||||
|
if (code) {
|
||||||
|
logger.fatal(`tcpdump exited with non-zero code: ${code}`);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
logger.info("Shutdown");
|
||||||
|
exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("Startup complete");
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -37,13 +37,16 @@ class PacketInfluxPointFactory extends Transform{
|
|||||||
_transform(packet, encoding, next){
|
_transform(packet, encoding, next){
|
||||||
// Create measurements
|
// Create measurements
|
||||||
MEASUREMENT_MAP.forEach((objKey, measurement) => {
|
MEASUREMENT_MAP.forEach((objKey, measurement) => {
|
||||||
if(!Object.keys(packet).includes(objKey)) return;
|
if(packet[objKey] == null) return;
|
||||||
|
|
||||||
let point = new Point(measurement); // Create point
|
let point = new Point(measurement); // Create point
|
||||||
|
|
||||||
// Set tags
|
// Set tags
|
||||||
TAG_LIST.filter(tag => Object.keys(packet).includes(tag))
|
TAG_LIST.filter(tag => Object.keys(packet).includes(tag)) // Filter tags available on object
|
||||||
.forEach(tag => point.tag(tag, packet[tag]));
|
.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
|
||||||
|
|
||||||
@@ -54,6 +57,15 @@ 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 */
|
/** Mapping for type -> field-method */
|
||||||
const POINT_FIELD_TYPE = new Map([
|
const POINT_FIELD_TYPE = new Map([
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const { Transform } = require('stream');
|
|||||||
const { DateTime } = require("luxon");
|
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 hexConv = require.main.require("./helper/hexConverter.js");
|
||||||
|
const wifiStateAnalyser = require.main.require("./helper/wifiStateAnalyzer.js");
|
||||||
|
|
||||||
const PACKET_TYPE_MAP = {
|
const PACKET_TYPE_MAP = {
|
||||||
"Beacon": PacketType.Beacon,
|
"Beacon": PacketType.Beacon,
|
||||||
@@ -65,17 +66,22 @@ class PacketStreamFactory extends Transform{
|
|||||||
?.forEach(match => packet.flags[FLAG_TYPE_MAP[match]] = true) // Set them to true in flags
|
?.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.dataRate = Number(data.match(/(?<=^|\s)\d+(\.\d+)?(?=\sMb\/?s($|\s))/i)?.[0]) || null;
|
||||||
packet.frequency = Number(data.match(/(?<=^|\s)[0-9]{4}(?=\sMHz($|\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];
|
let packetTypeStr = data.match(new RegExp('(?<=^|\\s)('+ PACKET_TYPES_REGEX +')(?=$|\\s)', 'i'))?.[0];
|
||||||
packet.packetType = packetTypeStr? PACKET_TYPE_MAP[packetTypeStr]:
|
if(packetTypeStr)
|
||||||
data.match(/(SA|TA|DA|RA|BSSID):.{17}\s*$/i)? PacketType.NoData:
|
packet.packetType = PACKET_TYPE_MAP[packetTypeStr];
|
||||||
PacketType.Unknown;
|
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;
|
packet.srcMac = data.match(/(?<=(^|\s)(SA|TA):).{17}(?=$|\s)/i)?.[0] ?? null;
|
||||||
|
|
||||||
@@ -129,25 +135,9 @@ class PacketStreamFactory extends Transform{
|
|||||||
|
|
||||||
// Read key-information
|
// Read key-information
|
||||||
const keyInfoRaw = (packet.payloadData[0x5]<<0x8) + packet.payloadData[0x6];
|
const keyInfoRaw = (packet.payloadData[0x5]<<0x8) + packet.payloadData[0x6];
|
||||||
const keyInfo = {
|
const keyInfo = wifiStateAnalyser.keyInfoFromRaw(keyInfoRaw); // Convert
|
||||||
"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,
|
|
||||||
};
|
|
||||||
|
|
||||||
newPacket.handshakeStage = (!keyInfo.Install && keyInfo.KeyACK && !keyInfo.KeyMIC && !keyInfo.Secure)? HandshakeStage[1] :
|
newPacket.handshakeStage = wifiStateAnalyser.handshakeStageFromKeyInfo(keyInfo); // Get stage
|
||||||
(!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;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(newPacket) packet = Object.assign(newPacket, packet);
|
if(newPacket) packet = Object.assign(newPacket, packet);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class RegexBlockStream extends Transform{
|
|||||||
* @param {RegExp} matcher Block-match
|
* @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} 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
|
* @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){
|
constructor(matcher, withholdLastBlock = true, matchAllOnFlush = false){
|
||||||
super({
|
super({
|
||||||
|
|||||||
Reference in New Issue
Block a user