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
│ ├── 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
testbed/lab.conf uses the published multi-architecture images by default:
loriringhio97/p4loriringhio97/bacnet
To refresh them locally:
docker pull loriringhio97/p4:latest
docker pull loriringhio97/bacnet:latestFor local-only development you can still build a private tag and point bacnetclient/bacnetserver and s1 / s2 in testbed/lab.conf at it:
docker build -t bacnet:latest -f Dockerfile/bacnet.Dockerfile .
docker build -t p4:latest -f Dockerfile/p4.Dockerfile .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 --noterminalsRun BACnet/SC with the default TLS 1.3 path:
./benchmark_bacnet.sh --bacnet-sc --noterminalsForce TLS 1.2 if you need to compare against older runs:
BACNET_SC_TLS_VERSION=TLSv1.2 ./benchmark_bacnet.sh --bacnet-sc --noterminalsAll 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. TLS 1.3 is the default; setBACNET_SC_TLS_VERSION=TLSv1.2to force TLS 1.2.
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)
The vendored BACnet/SC WebSocket stack had a few TLS 1.3-sensitive bugs: the client socket factory was setting client-auth mode on SSLSocket, the client WebSocket path relied on lazy TLS handshaking, and SSLSocketChannel2 could process TLS unwraps on write readiness while also discarding decrypted bytes when TLS 1.3 recreated session buffers. The local fix leaves client authentication to the server-side TLS request, starts the TLS handshake explicitly before the WebSocket handshake, and preserves pending TLS/plaintext bytes while separating read-side and write-side handshake progress.
- Switch programs are compiled at lab startup by
s1.startupands2.startup. - If a run is interrupted, execute
kathara lcleanbefore rerunning. - BACnet/SC scenario uses the reference stack integration inside the BACnet container image.
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={2026 IEEE 11th International Conference on Network Softwarization (NetSoft)},
pages={},
year={2026},
organization={IEEE}
}- NIST Net-Zero Energy Residential Test Facility dataset, using the HVAC minute readings (
HVAC-minute.csv) bacnet-stack, the C BACnet stack used by the benchmark client/server container image- BACnet/SC Reference Stack, included locally in
bacnet-sc-reference-stack-code/