A local Linux testbed for behavioural network anomaly detection and resilient SDN response
CPS-NetLab studies whether lightweight network monitoring can identify unusual behaviour and temporarily isolate an affected host without interrupting an unaffected host. It runs entirely inside a local Mininet network. Open vSwitch provides programmable forwarding, Python provides traffic generation and analysis, and a concurrent Rust server handles the application protocol.
The testbed contains two sensor clients and one server. h1 can generate controlled anomalous or benign but unusual traffic. h2 remains a control host. Packet metadata is captured on the server-facing switch port and converted into one-second host windows. An Isolation Forest learns a normal baseline. A per-host 4-of-5 rule confirms sustained anomalies before Open vSwitch applies a temporary ingress quarantine.
flowchart TD
H1[h1 sensor client] --> S1[Open vSwitch s1]
H2[h2 control client] --> S1
S1 --> H3[h3 Rust TCP server]
S1 --> T[Packet telemetry]
T --> F[Feature extraction]
F --> D[Isolation Forest]
D --> R[4-of-5 decision rule]
R --> Q[Temporary OVS quarantine]
Q --> S1
The supplied scenarios are bounded and restricted to the private Mininet topology. They are not exploit tools. CPS-NetLab must not be used against public, shared, production, or third-party systems.
The model was trained on one 300-second normal run containing 604 host windows. A separate 300-second run was used as a normal holdout. Each attack scenario and each benign novelty scenario was then repeated five times. The figures and plain-text evidence are stored in reports/.
| Scenario | Runs detected | Precision | Recall | F1 | False-positive rate | Median latency |
|---|---|---|---|---|---|---|
| Connection spike | 5 of 5 | 0.967 | 0.936 | 0.951 | 1.56% | 2 s |
| Volume spike | 5 of 5 | 0.972 | 0.892 | 0.930 | 1.26% | 3 s |
| Port spread | 5 of 5 | 0.966 | 0.906 | 0.935 | 1.57% | 4 s |
| Timing burst | 1 of 5 | 0.667 | 0.0127 | 0.0248 | 0.31% | 0 s for the one detected run |
The timing result is a failure case. Four of five runs were missed. The zero-second latency only describes the single detected run.
| Normal or benign scenario | Runs | Point false-positive rate | Sustained false-positive rate |
|---|---|---|---|
| Normal holdout | 1 | 2.32% | 0.00% |
| Benign rate change | 5 | 38.94% | 31.28% |
| Benign reconnect | 5 | 34.66% | 27.52% |
| Benign volume increase | 5 | 38.70% | 30.75% |
The detector identifies behavioural novelty rather than malicious intent. The high benign false-positive rates show why network context and safer response policies would be required in a real system.
A separate live run applied one exact-cookie quarantine to h1 for the full configured 30 seconds.
| Metric | Result |
|---|---|
| Sustained decision to OVS action | 0.0256 s |
h1 recovery after expiry |
0.797 s |
h2 acceptance during the labelled event |
100% (16 of 16) |
h2 observed interruptions |
0 |
h2 mean latency change |
+0.0128 ms (+1.65%) |
These results describe a small synthetic topology. They do not establish production safety or broad statistical generalisation.
Python 3.12 or newer, Rust 1.75 or newer, Mininet, Open vSwitch, TShark, and Dumpcap are required. Run commands from the repository root.
Install the official packages:
sudo pacman -S --needed \
python rust wireshark-cli openvswitch iproute2 shellcheck \
python-joblib python-matplotlib python-numpy python-pandas \
python-yaml python-scikit-learn python-pytest mypy ruffInstall Mininet from a reviewed AUR package or from upstream. Confirm that mn and python -c 'import mininet' use the same Python installation. Start Open vSwitch:
sudo systemctl enable --now ovs-vswitchd.service
sudo ovs-vsctl showCheck the environment and build the Rust server:
scripts/setup.sh --build-serversudo apt update
sudo apt install \
python3 python3-venv python3-pip python-is-python3 \
cargo rustc mininet openvswitch-switch tshark wireshark-common \
iproute2 shellcheck
python3 -m venv --system-site-packages .venv
.venv/bin/pip install -e '.[dev]'
cargo build --release --manifest-path server/Cargo.tomlVerify the topology:
sudo python -m topology --testCollect a five-minute training baseline:
sudo scripts/run_baseline.sh --run 1Train the detector with the features= path printed by the baseline command:
scripts/train.sh experiments/processed/features-baseline-run1-UTC-TOKEN.csvRun one controlled scenario:
sudo scripts/experiment.sh connection_spike --run 1Run the live response path after training:
sudo scripts/experiment.sh connection_spike --run 1 --online-defenceRecreate the aggregate analysis from the committed evidence:
scripts/train.sh \
reports/data/features-baseline-run1-20260831T024541.170762Z-d9a93333.csv
python -m evaluation.aggregate \
--config config.yaml \
--processed-directory reports/data \
--output-json /tmp/cps-netlab-aggregate.json \
--output-csv /tmp/cps-netlab-aggregate.csv \
--plot-directory /tmp/cps-netlab-plotsGenerated captures, models, logs, and build output are ignored by Git. The MIT Licence in LICENSE covers the source code and committed report material.

