DEV Community

Cover image for Building BACnet from Raw Packets: Objects, Priority Arrays, BBMDs, and Event Services
404Saint
404Saint

Posted on

Building BACnet from Raw Packets: Objects, Priority Arrays, BBMDs, and Event Services

By RUGERO Tesla (@404saint)

Industrial communication protocols reveal their design philosophy through the way they represent operational state.

Modbus exposes memory through coils and registers.

EtherNet/IP builds on the Common Industrial Protocol (CIP), organizing devices into object classes and explicit messaging services.

BACnet approaches automation from an entirely different direction. Rather than treating field devices as collections of registers, it models an automation system as a distributed object database in which sensors, actuators, schedules, alarms, and configuration elements are represented as standardized objects with well-defined properties.

That architectural decision fundamentally changes how clients interact with a BACnet device. Reading process information no longer involves polling register offsets; instead, application services operate directly against object instances and individual properties. Command execution is governed by priority arbitration, network communication depends on the BACnet Virtual Link Layer (BVLL), and many state changes are distributed through event-driven notifications rather than continuous polling.

This article documents a packet-level analysis of those mechanisms using an isolated BACnet/IP laboratory built around the open-source bacnet-stack implementation. Instead of relying on high-level BACnet libraries, each experiment manually constructed BVLL, NPDU, and APDU structures in Python to observe how the target implementation processed protocol requests under controlled conditions.

The research was divided into four phases:

  • Object Model & Property Engine
  • Priority Array Arbitration
  • Routing, BBMD & Subnet Traversal
  • Change-of-Value (COV), Event Services & BACnet/SC

Research Environment

The target consisted of a locally compiled bacnet-stack server exposing a virtual BACnet/IP device over UDP port 47808 (0xBAC0). All experiments were executed from a separate Python research harness that manually assembled protocol frames before transmitting them to the server.

Unlike production assessments, the objective was not vulnerability discovery. Instead, the laboratory provided an instrumented environment for observing protocol behavior, validating packet construction, and documenting implementation responses to both expected and unusual requests.


Phase 1 — Inspecting the BACnet Object Database

BACnet devices organize operational state into standardized objects. Every physical or logical component of the automation system is represented as an object containing a collection of named properties.

Rather than beginning with write operations, the first phase focused on understanding the controller's exposed object model.

The initial experiment queried the Object_List property of the Device object. The response returned 76 object instances, including standard BACnet object classes such as Analog Inputs, Analog Outputs, Binary Inputs, Binary Outputs, and several vendor-defined object types.

Subsequent experiments exercised additional application services including:

  • ReadProperty
  • ReadPropertyMultiple
  • CreateObject
  • DeleteObject
  • Extended property identifiers

One observation recorded during testing was that the target accepted an unauthenticated CreateObject request while rejecting unsupported property identifiers and protected deletion attempts using standard BACnet Error-PDUs. The implementation remained stable throughout the assessment and returned protocol-compliant error responses instead of terminating the connection.

Figure 1. Phase 1 research harness demonstrating object enumeration, multi-property inspection, dynamic object creation, invalid property handling, and protected object deletion against the laboratory BACnet/IP target.

Figure 1. Phase 1 research harness demonstrating object enumeration, multi-property inspection, dynamic object creation, invalid property handling, and protected object deletion against the laboratory BACnet/IP target.

The experiments established the structure of the target's internal object database before moving to command execution behavior.


Phase 2 — Understanding Priority Array Arbitration

One of BACnet's most distinctive features is the Priority Array.

Unlike protocols where writing a value immediately replaces the previous state, commandable BACnet objects maintain sixteen independent priority slots. The object's effective value is determined by the highest-priority non-NULL entry within the array.

To examine this behavior, the research harness issued successive WriteProperty requests targeting different priority levels before reading back the resulting Priority Array.

The experiments included:

  • Writing at Schedule priority (Slot 16)
  • Writing at Manual Life Safety (Slot 1)
  • Writing at Manual Operator (Slot 8)
  • Relinquishing control using NULL
  • Dumping the complete Priority Array

The server acknowledged each write request with a SimpleACK, while subsequent reads demonstrated how the implementation stored and evaluated priority values.

During testing, one implementation-specific observation stood out. Although a write targeted Priority Slot 8, the value later appeared in Slot 12 when the Priority Array was retrieved. Because this behavior was observed only within the laboratory implementation, no broader conclusions were drawn regarding BACnet itself. The result is simply documented as an implementation observation requiring additional investigation.

Figure 2. Priority Array assessment showing writes across multiple priority levels, NULL relinquishment, and the resulting Priority_Array returned by the laboratory target.

Figure 2. Priority Array assessment showing writes across multiple priority levels, NULL relinquishment, and the resulting Priority_Array returned by the laboratory target.

Beyond illustrating BACnet's arbitration model, the experiments highlighted how command execution depends on protocol semantics rather than simply accepting the most recent write.


Phase 3 — Routing, BBMDs, and Network Infrastructure

BACnet/IP extends beyond application services by introducing the BACnet Virtual Link Layer (BVLL).

BVLL enables communication across routed IP networks through Broadcast Management Devices (BBMDs), Foreign Device Registration, and BACnet network-layer routing services.

Three infrastructure mechanisms were examined:

  • Foreign Device Registration
  • Who-Is-Router-To-Network
  • Global Who-Is broadcasts

The target acknowledged a Register-Foreign-Device request using a successful BVLL Result response, indicating acceptance of the registration message at the protocol layer.

Router discovery requests produced no I-Am-Router-To-Network responses during the observation period, and global broadcast discovery did not identify additional BACnet devices within the laboratory environment.

These observations are consistent with the isolated topology used during testing and should not be interpreted as representative of production BACnet deployments.

Figure 3. BBMD and network infrastructure assessment demonstrating Foreign Device Registration, router discovery, and broadcast-based topology observations.

Figure 3. BBMD and network infrastructure assessment demonstrating Foreign Device Registration, router discovery, and broadcast-based topology observations.

This phase reinforced the distinction between BACnet's application layer and the additional networking infrastructure required to operate across routed environments.


Phase 4 — Event Services, COV, and BACnet/SC

The final phase examined BACnet's event-driven communication model.

Unlike protocols that depend exclusively on polling, BACnet allows clients to subscribe for Change of Value (COV) notifications. Once subscribed, the server can transmit state updates whenever monitored values change.

The research harness manually constructed a SubscribeCOV request targeting an Analog Input object.

The target acknowledged the subscription using a SimpleACK before transmitting an Unconfirmed COV Notification, confirming that the subscription had been accepted and event delivery was operational.

The second experiment focused on the Event_Enable property.

Rather than issuing a write operation in isolation, the property was read, modified, and read again to verify that the server stored the updated Bit String exactly as transmitted. This confirmed successful property modification within the laboratory implementation.

The final experiment probed TCP port 47809, the standard transport used by BACnet Secure Connect (BACnet/SC), by attempting a WebSocket handshake. The connection was refused, indicating that the laboratory server exposed only traditional BACnet/IP services during testing.

Figure 4. Event-service analysis demonstrating SubscribeCOV acknowledgement, Event_Enable verification before and after modification, and BACnet/SC transport probing.

Figure 4. Event-service analysis demonstrating SubscribeCOV acknowledgement, Event_Enable verification before and after modification, and BACnet/SC transport probing.

Together, these experiments completed the examination of BACnet's application services, event mechanisms, and secure transport transition.


Key Observations

Across the four research phases, several implementation characteristics were consistently observed:

  • The target exposed its object database through standard property services.
  • Priority arbitration followed BACnet's multi-slot command model rather than simple overwrite semantics.
  • Foreign Device Registration requests were acknowledged at the BVLL layer.
  • Router discovery and global broadcast enumeration produced no observable responses within the isolated laboratory.
  • SubscribeCOV successfully generated event notifications.
  • Event_Enable property modifications were reflected in subsequent reads.
  • No BACnet/SC listener was present on the expected secure transport port.

None of these observations should be generalized beyond the laboratory implementation. They describe the behavior recorded during controlled testing and serve as a foundation for understanding BACnet's protocol mechanics rather than evaluating commercial products.


Repository

The complete research repository contains:

  • Protocol architecture primer
  • Four packet-level research phases
  • Laboratory reproduction guide
  • Python research harnesses
  • Network diagrams
  • Terminal captures
  • Detailed implementation observations

GitHub: https://github.com/404saint/industrial-protocol-labs/tree/main/bacnet-research


Closing Remarks

BACnet is often introduced as "the building automation protocol," but that description overlooks much of what makes it technically interesting. Its object-oriented data model, priority-based command arbitration, network-layer routing infrastructure, and event-driven services distinguish it from many traditional industrial protocols.

Constructing protocol frames manually made those design decisions significantly clearer than interacting through abstraction libraries alone. More importantly, documenting the observed behavior, whether expected or implementation-specific, provides a reproducible reference for engineers and security researchers studying BACnet at the packet level.


Disclaimer: All experiments described in this article were conducted within an isolated laboratory environment using controlled BACnet/IP implementations. The work is intended solely for protocol education, interoperability research, and defensive security analysis. Do not perform similar testing against systems without explicit authorization.

Top comments (0)