Compare commits
80 Commits
code-smell
...
master
@ -1,13 +1,29 @@
|
|||||||
FROM node:16
|
# ---- Base ----
|
||||||
|
FROM alpine:3 AS base
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy project file
|
||||||
|
COPY package.json .
|
||||||
|
|
||||||
|
# Install required apk-packages
|
||||||
|
RUN apk add --no-cache nodejs npm tcpdump
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Dependencies ----
|
||||||
|
FROM base AS dependencies
|
||||||
|
|
||||||
# Install app dependencies
|
# Install app dependencies
|
||||||
COPY package*.json ./
|
RUN npm install --only=production
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
|
# ---- Release ----
|
||||||
|
FROM base AS release
|
||||||
|
|
||||||
|
# copy from build image
|
||||||
|
COPY --from=dependencies /usr/src/app/ ./
|
||||||
# Bundle app source
|
# Bundle app source
|
||||||
COPY ./src/ .
|
COPY ./src/ .
|
||||||
|
|
||||||
CMD ["npm", "run"]
|
CMD ["npm", "run", "start"]
|
||||||
|
@ -1,3 +1,419 @@
|
|||||||
# rfmon-to-influx
|
rfmon-to-influx
|
||||||
|
=================
|
||||||
|
|
||||||
|
![](docs/img/header0.png)
|
||||||
|
*Successful Associations, grouped by AP within 24h*
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
Writing (mostly meta-) data received in Wireless-Monitor-Mode into an InfluxDB.
|
Writing (mostly meta-) data received in Wireless-Monitor-Mode into an InfluxDB.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Table of contents
|
||||||
|
=================
|
||||||
|
<!-- TOC -->
|
||||||
|
- [1. Description](#1-description)
|
||||||
|
- [1.1. What kind of data](#11-what-kind-of-data)
|
||||||
|
- [1.2. Data-Usage](#12-data-usage)
|
||||||
|
- [1.3. Tools used](#13-tools-used)
|
||||||
|
- [1.4. Architecture](#14-architecture)
|
||||||
|
- [2. Usage/Installation](#2-usageinstallation)
|
||||||
|
- [2.1. Prerequisites](#21-prerequisites)
|
||||||
|
- [2.2. Choosing an Export-Method](#22-choosing-an-export-method)
|
||||||
|
- [2.3. Running with Docker](#23-running-with-docker)
|
||||||
|
- [2.4. Environment-Variables](#24-environment-variables)
|
||||||
|
- [3. Data collected](#3-data-collected)
|
||||||
|
- [3.1. Data-Types](#31-data-types)
|
||||||
|
- [3.2. Metric-Overview](#32-metric-overview)
|
||||||
|
- [3.3. Metric-Details](#33-metric-details)
|
||||||
|
- [3.4. Tag-Overview](#34-tag-overview)
|
||||||
|
- [3.5. Tag-Details](#35-tag-details)
|
||||||
|
- [4. Potential Issues](#4-potential-issues)
|
||||||
|
- [4.1. Channel/Frequency](#41-channelfrequency)
|
||||||
|
- [4.2. Technology](#42-technology)
|
||||||
|
- [4.3. Data protection](#43-data-protection)
|
||||||
|
- [4.4. Ethical](#44-ethical)
|
||||||
|
<!-- /TOC -->
|
||||||
|
<br>
|
||||||
|
|
||||||
|
# 1. Description
|
||||||
|
|
||||||
|
This Program listens on an Wifi-Interface in Monitor-Mode (rfmon) and logs most actions made into an influx or influx-like time-database.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 1.1. What kind of data
|
||||||
|
|
||||||
|
**Any** packet sent by a router or station nearby is received and its metadata is collected and categorised.
|
||||||
|
|
||||||
|
The host does **not** have to be part of that network.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 1.2. Data-Usage
|
||||||
|
|
||||||
|
The data can be used to identify problems with the wifi-communication nearby
|
||||||
|
e.g.
|
||||||
|
- Wifi-Congestion at certain times of the day
|
||||||
|
- occurring signal-issues
|
||||||
|
- e.g. due to broken Microwave-Ovens disrupting communications
|
||||||
|
- or moving big Objects (e.g. Machines) causing signal-reduction.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Aswell as gaining knowledge about installed routers and user interaction with them
|
||||||
|
e.g.
|
||||||
|
- in a company environment
|
||||||
|
- Logging presense and activity of interconnected machines
|
||||||
|
- Finding other Access-Points not allowed due to potential disruption of Production-Lines
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Other usages might be threat-detection at Wifi-Level
|
||||||
|
e.g.
|
||||||
|
- Deauthentication-Attacks
|
||||||
|
- Bruteforce-Attempts
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 1.3. Tools used
|
||||||
|
|
||||||
|
The program uses `tcpdump` for listening in a subProcess and then extract the metadata when packets arrive.
|
||||||
|
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 1.4. Architecture
|
||||||
|
|
||||||
|
![](docs/img/1.4.architecture.png)
|
||||||
|
|
||||||
|
The system heavily uses NodeJS-Streams to read, transform and pass data around.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
# 2. Usage/Installation
|
||||||
|
|
||||||
|
## 2.1. Prerequisites
|
||||||
|
|
||||||
|
The Wifi-Interface cannot be used elsewhere at the same time e.g. Network-Manager.
|
||||||
|
(Packet-capture e.g. tcpdump or Wireshark is ok)
|
||||||
|
|
||||||
|
As of this version, the program does **not** set the interface into monitor mode or changes to channels.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### 2.1.1. Interface into Monitor-Mode (rfmon)
|
||||||
|
|
||||||
|
You can change into Monitor-mode beforehand with the packages `net-tools` and `wireless-tools`:
|
||||||
|
```sh
|
||||||
|
ifconfig <interface> down
|
||||||
|
iwconfig <interface> mode Monitor
|
||||||
|
ifconfig <interface> up
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### 2.1.2. Set/Change channels
|
||||||
|
|
||||||
|
You can set the channel of the interface (if the interface allows this) with the package `wireless-tools`:
|
||||||
|
```sh
|
||||||
|
iw dev <interface> set channel <channelNumber>
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 2.2. Choosing an Export-Method
|
||||||
|
|
||||||
|
The system allows exporting directly into [InfluxDB](https://docs.influxdata.com/influxdb) version >= 2.0 or into any system using the [InfluxDb-Line-Protocol](https://docs.influxdata.com/influxdb/v2.1/reference/syntax/line-protocol/) e.g. [QuestDB](https://questdb.io/) over TCP.
|
||||||
|
|
||||||
|
As of writing (using InfluxDB v2.1 and using the *flux*-language), the data written by this system was a bit too much for InfluxDB and it struggled very quickly on a fairly beefy machine.
|
||||||
|
|
||||||
|
Thats why the additional LineProtocol-Export-Method was added. Freedom of choice of the Time-Database.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
If you want to use the InfluxDB-Line-Protocol, simply set the environment variable `USE_INFLUXDB_LINEPROTOCOL` to `true` along with the-other necessary Host and Port-variables.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 2.3. Running with Docker
|
||||||
|
|
||||||
|
### 2.3.1. Permissions
|
||||||
|
|
||||||
|
The container must run as **root**, to have permission to listen on the wifi-interface.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### 2.3.2. docker run
|
||||||
|
|
||||||
|
Either run with docker directly.
|
||||||
|
|
||||||
|
<details><summary>for InfluxDB</summary>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run
|
||||||
|
-d
|
||||||
|
--restart unless-stopped
|
||||||
|
--network host
|
||||||
|
-e WIFI_INTERFACE="<yourInterfaceName or leave empty for wlan0>"
|
||||||
|
-e INFLUX_URL="http://influxdb:8086/"
|
||||||
|
-e INFLUX_TOKEN="<yourToken>"
|
||||||
|
-e INFLUX_ORG="<yourOrganisation>"
|
||||||
|
ruakij/rfmon-to-influx:2
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>for InfluxDB-Line-Protocol</summary>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run
|
||||||
|
-d
|
||||||
|
--restart unless-stopped
|
||||||
|
--network host
|
||||||
|
-e WIFI_INTERFACE="<yourInterfaceName or leave empty for wlan0>"
|
||||||
|
-e USE_INFLUXDB_LINEPROTOCOL="true"
|
||||||
|
-e INFLUXDB_LINEPROTOCOL_HOST="<host>"
|
||||||
|
-e INFLUXDB_LINEPROTOCOL_PORT="<port>"
|
||||||
|
ruakij/rfmon-to-influx:2
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### 2.3.3. docker-compose
|
||||||
|
|
||||||
|
Or use the more preferred way with docker-compose.
|
||||||
|
|
||||||
|
`docker-compose.yml`
|
||||||
|
|
||||||
|
<details><summary>for InfluxDB</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
rfmon:
|
||||||
|
container_name: rfmon
|
||||||
|
image: ruakij/rfmon-to-influx:2
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
- WIFI_INTERFACE="<yourInterfaceName or leave empty for wlan0>"
|
||||||
|
- INFLUX_URL="http://influxdb:8086/"
|
||||||
|
- INFLUX_TOKEN="<yourToken>"
|
||||||
|
- INFLUX_ORG="<yourOrganisation>"
|
||||||
|
- INFLUX_BUCKET="<yourBucket>"
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>for InfluxDB-Line-Protocol</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
rfmon:
|
||||||
|
container_name: rfmon
|
||||||
|
image: ruakij/rfmon-to-influx:2
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: "host"
|
||||||
|
environment:
|
||||||
|
- WIFI_INTERFACE="<yourInterfaceName or leave empty for wlan0>"
|
||||||
|
- USE_INFLUXDB_LINEPROTOCOL="true"
|
||||||
|
- INFLUXDB_LINEPROTOCOL_HOST="<host>"
|
||||||
|
- INFLUXDB_LINEPROTOCOL_PORT="<port>"
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
And then pull&start the container:
|
||||||
|
```sh
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 2.4. Environment-Variables
|
||||||
|
|
||||||
|
### 2.4.1. Necessary
|
||||||
|
|
||||||
|
<details><summary>for InfluxDB</summary>
|
||||||
|
|
||||||
|
Variable|Description
|
||||||
|
---|---
|
||||||
|
`INFLUX_URL` | Url of influx-server
|
||||||
|
`INFLUX_TOKEN` | Token with write-access
|
||||||
|
`INFLUX_ORG` | Organisation and..
|
||||||
|
`INFLUX_BUCKET` | Bucket to write into
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>for InfluxDB-Line-Protocol</summary>
|
||||||
|
|
||||||
|
Variable|Description
|
||||||
|
---|---
|
||||||
|
`USE_INFLUXDB_LINEPROTOCOL` | Enable LineProtocol
|
||||||
|
`INFLUXDB_LINEPROTOCOL_HOST` | Host and..
|
||||||
|
`INFLUXDB_LINEPROTOCOL_PORT` | Port of your server
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
### 2.4.2. Optional
|
||||||
|
|
||||||
|
Variable|Default|Description
|
||||||
|
---|---|---
|
||||||
|
`LOGLEVEL` | INFO | Loglevel
|
||||||
|
`WIFI_INTERFACE` | wlan0 | Wifi-Interface name in Monitor-Mode
|
||||||
|
~~`HOSTNAME`~~ | ~~Device's Hostname~~ | ~~Hostname to use as global hostname-tag~~ *(Unused)*
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
# 3. Data collected
|
||||||
|
|
||||||
|
8 Metrics are constructed with 6-10 tags identifying them.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 3.1. Data-Types
|
||||||
|
|
||||||
|
Type|Example|Description
|
||||||
|
---|---|---
|
||||||
|
`String` | Wlan | -
|
||||||
|
`Number` | 0 | Any normal number, positive and negative
|
||||||
|
`Boolean` | true | true or false values
|
||||||
|
`MAC` | 12:34:56:78:9A:BC | Address for L2-networks
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 3.2. Metric-Overview
|
||||||
|
---
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Name|Type|Description
|
||||||
|
---|---|---
|
||||||
|
rfmon_signal_dbm | `Number` (-95 <> -20) | Signal-Level of every Packet in dBm
|
||||||
|
rfmon_datarate_bytes | `Number` (1 <> 144) | Data-Rate of every Packet in MBit/s
|
||||||
|
rfmon_ssid_names | `String` (Length: 0-32) | SSIDs of any Packet containing it
|
||||||
|
rfmon_authenticationtype_info | `String` | Authentication-Type used by Sender
|
||||||
|
rfmon_associationsuccess_bools | `Boolean` | Result of an Association
|
||||||
|
rfmon_disassociationreason_info | `String` | Disconnect-Reason from a ST (not always sent)
|
||||||
|
rfmon_handshakestage_info | `Number` (1 <> 4) | Stage of a handshake (1 and 3 from ST, 2 and 4 from AP)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 3.3. Metric-Details
|
||||||
|
|
||||||
|
### 3.3.1. rfmon_ssid_names
|
||||||
|
`String` (Length: 0-32)
|
||||||
|
|
||||||
|
SSIDs from ProbeRequest might be empty (probe for any) or in case of Beacon-Frames could be hidden.
|
||||||
|
|
||||||
|
### 3.3.2. rfmon_authenticationtype_info
|
||||||
|
`String` {OpenSystem_1, OpenSystem_2, Unknown}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 3.4. Tag-Overview
|
||||||
|
---
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Name |Type |Description
|
||||||
|
---|---|---
|
||||||
|
srcmac | `MAC` | Sender's MAC-Address (not present in ClearToSend-Packet)
|
||||||
|
dstmac | `MAC` | Destination's MAC-Address (not present in RequestToSend-Packet)
|
||||||
|
bssid | `MAC` | AP's MAC-Address
|
||||||
|
frequency | `Number` | Frequency the packet was captured on in MHz
|
||||||
|
packetType | `String` | Type of packet
|
||||||
|
flags_MoreFragments | `Boolean` | Packet is incomplete
|
||||||
|
flags_Retry | " | Packet is being retried
|
||||||
|
flags_PwrMgt | " | Sender will not sleep
|
||||||
|
flags_MoreData | " | More data in send-buffer to be expected
|
||||||
|
flags_Protected | " | Packet is protected
|
||||||
|
flags_Order | " | Packet is strictly ordered
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 3.5. Tag-Details
|
||||||
|
|
||||||
|
### 3.5.1. frequency
|
||||||
|
`Number` (2412 <> 2484)
|
||||||
|
|
||||||
|
The frequency corresponds to following wifi-channels:
|
||||||
|
|
||||||
|
Channel|Frequency
|
||||||
|
---|---
|
||||||
|
1 | 2412
|
||||||
|
2 | 2417
|
||||||
|
3 | 2422
|
||||||
|
4 | 2427
|
||||||
|
5 | 2432
|
||||||
|
6 | 2437
|
||||||
|
7 | 2442
|
||||||
|
8 | 2447
|
||||||
|
9 | 2452
|
||||||
|
10 | 2457
|
||||||
|
11 | 2462
|
||||||
|
12 | 2467
|
||||||
|
13 | 2472
|
||||||
|
14 | 2484
|
||||||
|
|
||||||
|
See [Wikipedia - List of WLAN channels - 2.4GHz](https://en.wikipedia.org/wiki/List_of_WLAN_channels#2.4_GHz_(802.11b/g/n/ax)) for more Information.
|
||||||
|
|
||||||
|
### 3.5.2. packettype
|
||||||
|
`String`
|
||||||
|
|
||||||
|
Type|Sender|Description
|
||||||
|
---|---|---
|
||||||
|
Beacon | AP | Signal its presence and provide synchronisation for Stations
|
||||||
|
ProbeRequest | ST | Ask if certain RA/SSID is available
|
||||||
|
ProbeResponse | AP | Directly respond to Request and Signal own presence
|
||||||
|
Data | Both | Data-packets
|
||||||
|
RequestToSend | ST | Ask for transmission-time
|
||||||
|
ClearToSend | RA | Ack transmission-time
|
||||||
|
Acknowledgment | Both | Ack Data-Packets
|
||||||
|
BlockAcknowledgment | Both | Ack alot of Data-Packets at once
|
||||||
|
NoData | Both | Packet without content, typically used to transmit QoS-States
|
||||||
|
Authentication | Both | Authentication-process to establish identity and set states
|
||||||
|
AssociationRequest | ST | Register to AP
|
||||||
|
AssociationResponse | AP | Respond to registering
|
||||||
|
Disassociation | ST | Actively unregister e.g. to associate with different AP
|
||||||
|
Handshake | Both | 4-Way-EAPOL-Handshake to generate encryption-keys between participants
|
||||||
|
Unknown | - | Unknown packets not identified into above types
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
# 4. Potential Issues
|
||||||
|
|
||||||
|
## 4.1. Channel/Frequency
|
||||||
|
|
||||||
|
The System can only monitor one channel at a time which might not be enough cover,
|
||||||
|
to combat this, more Interfaces and Systems can be deployed.
|
||||||
|
|
||||||
|
This is not entirely unproblematic, as the system cannot currently prevent packages from being inserted more than once.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 4.2. Technology
|
||||||
|
|
||||||
|
Mismatches between sender and receiver-technologies (e.g. MIMO or HT) can cause packets not being logged at all.
|
||||||
|
Though this should only be a problem for data-packets.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 4.3. Data protection
|
||||||
|
|
||||||
|
Because the system collects any data, this can be problematic, specially in countries with strong data-protection laws.
|
||||||
|
|
||||||
|
A wifi MAC address is likely to be considered as information of an identifiable natural person, e.g. under GDPR Art.4 (1) and its processing may only be done with prior consent or has to be anonymised.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 4.4. Ethical
|
||||||
|
|
||||||
|
The large-scale collection of data for behavioural or movement analysis, especially without consent of the data subject, is highly controversial.
|
||||||
|
|
||||||
|
Metadata that can be used to track precise activities, such as wifi data, is very powerful and should only be collected and used when necessary.
|
||||||
|
|
||||||
|
If this data falls into the hands of a malicious actor, more precise attacks on the targets could be carried out, such as break-insv, behaviour-based discrimination or more successful phishing.
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
TAG="ruakij/rfmon-to-influx"
|
||||||
|
PLATFORM="linux/amd64,linux/arm64/v8,linux/arm/v7"
|
||||||
|
EXTRA_ARGS="$@"
|
||||||
|
|
||||||
|
docker buildx build \
|
||||||
|
--platform $PLATFORM \
|
||||||
|
--tag $TAG \
|
||||||
|
$EXTRA_ARGS \
|
||||||
|
.
|
@ -0,0 +1,7 @@
|
|||||||
|
TAG="ruakij/rfmon-to-influx"
|
||||||
|
EXTRA_ARGS="$@"
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--tag $TAG \
|
||||||
|
$EXTRA_ARGS \
|
||||||
|
.
|
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -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,
|
||||||
|
};
|
@ -1,87 +1,201 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
const logger = require("./helper/logger.js")("main");
|
const logFactory = require("./helper/logger.js");
|
||||||
|
const logger = logFactory("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 { exec } = require("./helper/exec.js");
|
||||||
|
const Os = require("os");
|
||||||
|
|
||||||
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 { RegexBlockStream } = require("./streamHandler/RegexBlockStream.js");
|
||||||
const { PacketStreamFactory } = require("./streamHandler/PacketStreamFactory.js");
|
const { PacketStreamFactory } = require("./streamHandler/PacketStreamFactory.js");
|
||||||
const { PacketInfluxPointFactory } = require("./streamHandler/PacketInfluxPointFactory.js");
|
const { PacketInfluxPointFactory } = require("./streamHandler/PacketInfluxPointFactory.js");
|
||||||
const { InfluxPointWriter } = require("./streamHandler/InfluxPointWriter.js");
|
const { InfluxPointWriter } = require("./streamHandler/InfluxPointWriter.js");
|
||||||
|
const { InfluxDbLineProtocolWriter } = require("./streamHandler/InfluxDbLineProtocolWriter.js");
|
||||||
|
const { InfluxPointToLineProtoStream } = require("./streamHandler/InfluxPointToLineProtoStream.js");
|
||||||
|
|
||||||
|
const userHelper = require("./helper/userHelper.js");
|
||||||
|
|
||||||
|
|
||||||
/// Setup ENVs
|
/// Setup ENVs
|
||||||
const env = process.env;
|
const env = process.env;
|
||||||
// Defaults
|
// Defaults
|
||||||
{
|
{
|
||||||
env.LOGLEVEL ??= "INFO";
|
env.LOGLEVEL ??= "INFO";
|
||||||
env.WIFI_INTERFACE ??= "wlan0";
|
env.WIFI_INTERFACE ??= "wlan0";
|
||||||
|
env.HOSTNAME ??= Os.hostname();
|
||||||
|
|
||||||
|
env.USE_INFLUXDB_LINEPROTOCOL ??= false;
|
||||||
}
|
}
|
||||||
// Required vars
|
// Required vars
|
||||||
let errorMsg = requireEnvVars([
|
let errorMsg = requireEnvVars(
|
||||||
"INFLUX_URL", "INFLUX_TOKEN",
|
env.USE_INFLUXDB_LINEPROTOCOL? [ // When lineprotocol is enabled, we need host and port
|
||||||
"INFLUX_ORG", "INFLUX_BUCKET"
|
"INFLUXDB_LINEPROTOCOL_HOST", "INFLUXDB_LINEPROTOCOL_PORT",
|
||||||
]);
|
] : [ // When its disabled, influxdb-data
|
||||||
|
"INFLUX_URL", "INFLUX_TOKEN",
|
||||||
|
"INFLUX_ORG", "INFLUX_BUCKET"
|
||||||
|
]);
|
||||||
if(errorMsg){
|
if(errorMsg){
|
||||||
logger.fatal(errorMsg);
|
logger.fatal(errorMsg);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
(async function() {
|
(async function() {
|
||||||
logger.info("Setup Influx..");
|
let pointWriter;
|
||||||
const influxDb = new InfluxDB({url: env.INFLUX_URL, token: env.INFLUX_TOKEN});
|
if(!env.USE_INFLUXDB_LINEPROTOCOL){
|
||||||
|
logger.info("Setup Influx..");
|
||||||
await InfluxChecks.checkHealth(influxDb)
|
const influxDb = new InfluxDB({url: env.INFLUX_URL, token: env.INFLUX_TOKEN});
|
||||||
.then((res) => {return InfluxChecks.checkBucket(influxDb, {
|
|
||||||
org: env.INFLUX_ORG,
|
await InfluxChecks.checkHealth(influxDb)
|
||||||
name: env.INFLUX_BUCKET
|
.then((res) => {return InfluxChecks.checkBucket(influxDb, {
|
||||||
})})
|
org: env.INFLUX_ORG,
|
||||||
.then((res) => {return InfluxChecks.checkWriteApi(influxDb, {
|
name: env.INFLUX_BUCKET
|
||||||
org: env.INFLUX_ORG,
|
});})
|
||||||
bucket: env.INFLUX_BUCKET
|
.then((res) => {return InfluxChecks.checkWriteApi(influxDb, {
|
||||||
})})
|
org: env.INFLUX_ORG,
|
||||||
.catch((err) => {
|
bucket: env.INFLUX_BUCKET
|
||||||
if(err) {
|
});})
|
||||||
logger.error("Error whilst checking influx:");
|
.catch((err) => {
|
||||||
logger.error(err);
|
if(err) {
|
||||||
}
|
logger.error("Error whilst checking influx:");
|
||||||
logger.fatal("Setup influx failed!");
|
logger.error(err);
|
||||||
exit(1);
|
}
|
||||||
|
logger.fatal("Setup influx failed!");
|
||||||
|
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});
|
||||||
|
|
||||||
|
pointWriter = new InfluxPointWriter(influxWriteApi);
|
||||||
|
|
||||||
|
logger.info("Influx ok");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info("Setup Influxdb-LineProtocol..");
|
||||||
|
|
||||||
|
let lineProtocolWriter = new InfluxDbLineProtocolWriter(env.INFLUXDB_LINEPROTOCOL_HOST, env.INFLUXDB_LINEPROTOCOL_PORT);
|
||||||
|
|
||||||
|
logger.debug("Create PointToLineProto and pipe to LineProtocolWriter");
|
||||||
|
pointWriter = new InfluxPointToLineProtoStream();
|
||||||
|
pointWriter
|
||||||
|
.setEncoding("utf8")
|
||||||
|
.pipe(lineProtocolWriter);
|
||||||
|
|
||||||
|
logger.debug("Waiting for connection..");
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
lineProtocolWriter.once("connect", () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
lineProtocolWriter.once("error", (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
setTimeout(() => { // After timeout, reject promise
|
||||||
|
reject("Timeout whilst waiting to connect");
|
||||||
|
}, 6500);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info("Influxdb-LineProtocol ok");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if(err) {
|
||||||
|
logger.error("Error whilst checking Influxdb-LineProtocol:");
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
logger.fatal("Setup Influxdb-LineProtocol failed!");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
proc.stdout
|
||||||
|
.setEncoding("utf8")
|
||||||
|
.pipe(regexBlockStream)
|
||||||
|
.pipe(packetStreamFactory)
|
||||||
|
.pipe(packetInfluxPointFactory)
|
||||||
|
.pipe(pointWriter);
|
||||||
|
|
||||||
|
logger.debug("Attaching error-logger..");
|
||||||
|
const loggerTcpdump = logFactory("tcpdump");
|
||||||
|
let linkTypeId;
|
||||||
|
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);
|
||||||
|
|
||||||
|
if(!linkTypeId && data.match(/^(tcpdump: )?listening on/i)){ // Grab first data containing listen-info if proper header was found
|
||||||
|
const linkType = data.match(/((?<=link-type ))([a-z].*?) \(.*?\)(?=,)/i)[0];
|
||||||
|
const linkTypeData = linkType.match(/(\S*) (.*)/i);
|
||||||
|
linkTypeId = linkTypeData[1];
|
||||||
|
const linkTypeDetail = linkTypeData[2];
|
||||||
|
|
||||||
|
if(linkTypeId !== "IEEE802_11_RADIO"){
|
||||||
|
logger.error(`Interface not in Monitor-mode! (Expected 'IEEE802_11_RADIO', but got '${linkTypeId}')`);
|
||||||
|
shutdown(1, "SIGKILL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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}`);
|
||||||
|
if(!exitCode) exitCode = 1; // When exitCode is 0, set to 1
|
||||||
|
}
|
||||||
|
logger.info("Shutdown");
|
||||||
|
exit(exitCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info("Influx ok");
|
// Handle stop-signals for graceful shutdown
|
||||||
|
var exitCode = 0;
|
||||||
logger.info("Starting tcpdump..");
|
function shutdownReq() {
|
||||||
const TCPDUMP_BASECMD = "tcpdump -vvv -e -n -X -s0 -i"
|
logger.info("Shutdown request received..");
|
||||||
let cmd = `sudo ${TCPDUMP_BASECMD} ${env.WIFI_INTERFACE}`;
|
shutdown();
|
||||||
|
}
|
||||||
let proc = exec(cmd);
|
function shutdown(code, signal = "SIGTERM"){
|
||||||
logger.debug("Creating & Attaching streams..");
|
if(code) exitCode = code;
|
||||||
proc.stdout
|
logger.debug("Stopping subprocess tcpdump, then exiting myself..");
|
||||||
.setEncoding("utf8")
|
proc.kill(signal); // Kill process, then upper event-handler will stop self
|
||||||
.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");
|
process.on("SIGTERM", shutdownReq);
|
||||||
exit(0);
|
process.on("SIGINT", shutdownReq);
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("Startup complete");
|
logger.info("Startup complete");
|
||||||
})();
|
})();
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
const logger = require.main.require("./helper/logger.js")("InfluxDbLineProtocolWriter");
|
||||||
|
const net = require("net");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get points and write them into influx
|
||||||
|
*/
|
||||||
|
class InfluxDbLineProtocolWriter extends net.Socket{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} host Host of line-server
|
||||||
|
* @param {string} port Port of line-server
|
||||||
|
* @param {object} options Options for further configuration
|
||||||
|
*/
|
||||||
|
constructor(host, port, options = {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._host = host;
|
||||||
|
this._port = port;
|
||||||
|
|
||||||
|
// options defaults
|
||||||
|
options.autoConnect ??= true;
|
||||||
|
options.timeout ??= 5000;
|
||||||
|
options.autoReconnect ??= true;
|
||||||
|
options.autoReconnectBackoffTime ??= 3000;
|
||||||
|
this._options = options;
|
||||||
|
|
||||||
|
this._isConnected = false;
|
||||||
|
|
||||||
|
super.setKeepAlive(true, 5000);
|
||||||
|
|
||||||
|
// Register auto-Reconnect if enabled
|
||||||
|
if(this._options.autoReconnect){
|
||||||
|
this.on("connect", () => {
|
||||||
|
logger.debug("Connection established!");
|
||||||
|
this._isConnected = true;
|
||||||
|
|
||||||
|
if(this._autoReconnectTimeout)
|
||||||
|
clearInterval(this._autoReconnectTimeout);
|
||||||
|
this._autoReconnectTimeout = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("error", (err) => {
|
||||||
|
logger.error(err.code, "TCP ERROR");
|
||||||
|
this._isConnected = false;
|
||||||
|
|
||||||
|
if(!this._autoReconnectTimeout)
|
||||||
|
this._autoReconnectTimeout = setInterval(() => {
|
||||||
|
this.connect();
|
||||||
|
},
|
||||||
|
this._options.autoReconnectBackoffTime);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autoconnect if requested
|
||||||
|
if(this._options.autoConnect) this.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
get host(){ return this._host; }
|
||||||
|
get port(){ return this._port; }
|
||||||
|
|
||||||
|
get isConnected(){ return this._isConnected; }
|
||||||
|
|
||||||
|
connect(){
|
||||||
|
logger.debug("Connecting..");
|
||||||
|
super.connect(this._port, this._host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify exports
|
||||||
|
module.exports = {
|
||||||
|
InfluxDbLineProtocolWriter
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
const logger = require.main.require("./helper/logger.js")("InfluxPointToLineProtoStream");
|
||||||
|
const { Transform } = require("stream");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get points and converts them to Line-protocol
|
||||||
|
*/
|
||||||
|
class InfluxPointToLineProtoStream extends Transform{
|
||||||
|
constructor(){
|
||||||
|
super({
|
||||||
|
writableObjectMode: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(point, encoding, next){
|
||||||
|
next(null, point.toLineProtocol() +"\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify exports
|
||||||
|
module.exports = {
|
||||||
|
InfluxPointToLineProtoStream
|
||||||
|
};
|
Loading…
Reference in New Issue