This repository provides a BACnet-only benchmarking testbed to compare plaintext BACnet traffic, in-network AES on P4 switches, and BACnet/SC over TLS.
The testbed sends sensor values from the NIST Zero Net House HVAC dataset through two P4 software switches and measures latency/processing metrics across five scenarios.
The benchmark supports 5 scenarios:
bacnet-plainaes-128aes-192aes-256bacnet-sc-tls
RTT(all 5 scenarios)PPTpacket processing time (AES scenarios only)DEQegress dequeuing time (AES scenarios only)
.
├── Dockerfile/
│ └── bacnet.Dockerfile
├── protocols/
│ └── bacnet/
│ ├── server-client/ # BACnet WriteProperty benchmark client (CSV batch sender)
│ └── bin/ # Built benchmark client + BACnet server binary
├── bacnet-sc-reference-stack-code/
│ ├── config/kathara/ # BACnet/SC benchmark configs
│ ├── scripts/ # BACnet/SC benchmark client script
│ └── dev/src/java/ # Reference stack sources + local service changes
├── testbed/
│ ├── benchmark_bacnet.sh # Main benchmark orchestrator
│ ├── build_bacnet_image.sh # Docker build helper
│ ├── lab.conf # Kathara topology
│ ├── HVAC-minute.csv # NIST dataset used by benchmark
│ ├── s1/ # Switch 1 assets (P4, keys, metrics scripts)
│ ├── s2/ # Switch 2 assets (P4, keys, metrics scripts)
│ ├── scripts/
│ │ ├── summarize_metrics.py # Per-run metric summary (CSV/JSON)
│ │ └── plots/ # Batch run plot generator
│ └── shared/ # Runtime outputs (RTT/PPT/DEQ/summary)
└── README.md
Input dataset: testbed/HVAC-minute.csv (NIST Zero Net House).
- The benchmark reads the full file by default.
- CSV columns 4-9 are used as numeric HVAC values.
- Columns 10-13 are empty in the source dataset and are ignored.
- No one-minute pacing is applied; packets are sent as fast as possible for faster experiments.
- Docker
- Kathara
- Permission to run Docker/Kathara commands on your host
From repository root:
./testbed/build_bacnet_image.shThis builds bacnet-local:latest, used by bacnetclient and bacnetserver in testbed/lab.conf.
From testbed/:
kathara lclean || true
./benchmark_bacnet.sh --all --ppt --deq --noterminals--plain--aes-128--aes-192--aes-256--bacnet-sc--all
--rtt(always enabled logically)--ppt(AES only)--deq(AES only)
--no-egress-metrics(AES only, keeps in-network AES but skips egress register writes)--noterminals(recommended for non-interactive runs)
Run only plaintext BACnet:
./benchmark_bacnet.sh --plain --noterminalsRun only one AES scenario:
./benchmark_bacnet.sh --aes-128 --ppt --deq --noterminalsRun AES scenarios without egress register writes:
./benchmark_bacnet.sh --aes-128 --aes-192 --aes-256 --no-egress-metrics --noterminalsGenerate charts from a completed batch folder:
python3 testbed/scripts/plots/generate_batch_plots.py \
--batch-dir testbed/batch_runs/20260330_182156By default, plots are saved to testbed/batch_runs/<timestamp>/plots/.
Shortcut for latest batch:
./testbed/scripts/plots/generate_batch_plots.shAll outputs are written to testbed/shared/.
- RTT:
results_rtt_<scenario>.txt - Switch PPT (AES):
results_s1_*packet_processing_time*.txt,results_s2_*packet_processing_time*.txt - Switch DEQ (AES):
results_s1_*packet_dequeuing_timedelta*.txt,results_s2_*packet_dequeuing_timedelta*.txt
benchmark_summary.csvbenchmark_summary.json
Each summary row reports:
countminmaxmeanmedianp95p99
The P4 programs are BACnet-focused (testbed/s1/bacnet_secure_switch.p4, testbed/s2/bacnet_secure_switch.p4).
- BACnet UDP traffic (port
47808) is parsed and eligible for in-network encryption/decryption. plainmode clearsbacnet_sectable entries, so traffic is forwarded without in-network crypto.- AES modes (
128/192/256) installcipher/decipheractions inbacnet_sec. - With
--no-egress-metrics, AES modes installcipher_no_metrics/decipher_no_metrics, so crypto remains active while egress metric registers are not written.
bacnet-plainandaes-128/192/256use BACnet/IP confirmedWritePropertyrequests.- Each valid CSV row is sent as one
WritePropertyto aCharacterString Valueobject, carrying six HVAC values in one payload string. bacnet-sc-tlsuses BACnet/SC (WriteProperty) over TLS in the reference stack.
To simulate plants where secure channels occasionally drop and reconnect, set these in bacnet-sc-reference-stack-code/config/kathara/BenchmarkClient.properties:
app.reconnectEveryRows(0 disables, e.g.5000)app.reconnectJitterRows(random interval jitter in rows)app.reconnectPauseMs(delay before reconnect)app.reconnectSeed(-1for random, fixed value for reproducible runs)
- Switch programs are compiled at lab startup by
s1.startupands2.startup. - If a run is interrupted, execute
kathara lcleanbefore rerunning. - BACnet/SC scenario uses local reference stack integration inside
bacnet-local:latest.
If you use this repository in academic work, please cite our paper.
@inproceedings{bacnet_in_network_p4,
title={In-Network Security for Smart Buildings BACnet Communications},
author={Rinieri, Lorenzo and Iacobelli, Antonio and Melis, Andrea and Girau, Roberto and Callegati, Franco and Prandini, Marco},
booktitle={},
pages={},
year={},
organization={}
}- NIST Zero Net House HVAC dataset (
HVAC-minute.csv) bacnet-stackproject (C BACnet stack used by benchmark client/server container image)- BACnet/SC reference stack included in
bacnet-sc-reference-stack-code/