What is this post about?
When you want a smart bulb to join your Bluetooth Mesh network, something has to happen first — the bulb needs to announce itself, the network needs to verify its identity, and then the provisioner needs to hand it the right keys. That whole process starts with beacons.
In this post we will look at two core beacon types used in Bluetooth Mesh — the Unprovisioned Device Beacon (used before a device joins a network) and the Secure Network Beacon (used after it joins). We will also cover how a mesh network is created from scratch and how guest access works.
We use BlueZ mesh tools for code examples so you can try things on a real Linux system.
Keywords covered in this post
Think of a Bluetooth Mesh network like a secured office building. Before you get a keycard (network key), you have to stand outside and wave — that is the Unprovisioned Device Beacon. Once you have your keycard and are inside, you keep checking in at each floor with a badge scanner — that is the Secure Network Beacon.
Beacons serve two purposes:
- Discovery — let the provisioner know a new device is waiting to be added.
- Health checks — let existing nodes know about security updates (key refresh, IV index changes).
When a device has never been provisioned into any mesh network, it broadcasts an Unprovisioned Device Beacon as a non-connectable BLE advertising packet. The provisioner scans for this beacon to discover new devices.
Packet structure (byte layout):
| Beacon Type 1 byte 0x00 |
Device UUID 16 bytes |
OOB Information 2 bytes |
URI Hash 4 bytes (optional) |
| Identifies beacon type as “unprovisioned” | Unique 128-bit ID of this device | Bitmask: where to find OOB data | Links beacon to a URI with extra info (e.g. public key URL) |
2.1 What is the OOB Information field?
OOB stands for Out-Of-Band. The provisioner needs to verify the device’s identity securely. The OOB Information field is a 16-bit bitmask telling the provisioner where it can find authentication data for this device — for example, printed on a sticker, available via NFC, or encoded in a QR code.
| Bit | Meaning | Practical example |
| 0 | Other | Some other OOB method |
| 1 | Electronic / URI | Public key URL in a web page |
| 2 | 2D machine-readable code | QR code on the device |
| 3 | Bar code | Barcode sticker on the box |
| 4 | NFC | Tap phone to device |
| 5 | Number | PIN printed on device |
| 6 | String | Passphrase printed on label |
| 11 | On box | Info is on the outer packaging |
| 12 | Inside box | Quick-start card inside the box |
| 13 | On piece of paper | Separate paper slip |
| 14 | Inside manual | Printed in the user manual |
| 15 | On device | Engraved or printed on the body |
2.2 URI Hash — linking a beacon to a web resource
Sometimes a device needs to point to a URL that has more information — like its public key or a data sheet. It broadcasts that URL in a separate BLE advertising packet using the URI AD type. But how does the provisioner know which beacon belongs to which URL?
The answer is the URI Hash: a 4-byte fingerprint of the URI data, computed using the mesh s1 hash function. When the provisioner sees both the beacon and the URI packet with matching hashes, it knows they belong to the same device.
2.3 BlueZ: scanning for unprovisioned devices
With BlueZ, you can use mesh-cfgclient to scan for unprovisioned devices. Here is how the flow looks in practice on Linux:
# Start the BlueZ mesh daemon
sudo bluetooth-meshd --config /etc/bluetooth/mesh --nodetach
# In another terminal, open mesh-cfgclient
mesh-cfgclient
# Inside mesh-cfgclient: discover unprovisioned devices
# The tool listens for Unprovisioned Device Beacons (beacon type 0x00)
discover-unprovisioned on
# You will see output like:
# Unprovisioned device:
# UUID: aabbccdd-eeff-0011-2233-445566778899
# OOB info: 0x0020 (bit 5 set → Number / PIN)
# URI hash: 0x1a2b3c4d
The OOB info: 0x0020 means bit 5 is set, which corresponds to a numeric PIN. The provisioner now knows to ask for a PIN before provisioning this device.
Once a device has been provisioned, it stops sending the Unprovisioned Device Beacon. Instead, nodes in the mesh periodically send Secure Network Beacons to keep everyone on the same page about the network’s security state.
Secure Network Beacon byte layout:
| Beacon Type 1 byte 0x01 |
Flags 1 byte |
Network ID 8 bytes |
IV Index 4 bytes |
Authentication Value 8 bytes |
| Identifies as secure beacon | Key refresh + IV update bits | Derived from network key | Replay attack counter | AES-CMAC signature |
3.1 The Flags byte — only 2 bits actually matter
The Flags field is a single byte but only the two lowest bits are used right now. Bits 2–7 are reserved.
| Bit position | Flag name | Value 0 | Value 1 | What it means in practice |
| Bit 0 | Key Refresh Flag | False — normal operation | True — key refresh in progress | When set, nodes know to start using the new network key |
| Bit 1 | IV Update Flag | Normal operation | IV Update active | IV Index is being updated across the network (anti-replay protection) |
| Bits 2–7 | Reserved | Must be 0 — reserved for future use | ||
3.2 What is the IV Index and why does it matter?
Every message in a Bluetooth Mesh network has a sequence number. When the sequence number space is getting exhausted (after millions of messages), the network needs to reset it. The IV Index (Initialization Vector Index) is a 4-byte counter that gets incremented to signal this reset. It acts as a safeguard against replay attacks.
|
IV Index = 0
Fresh network — SEQ 0 to 0xFFFFFF available
|
→ |
SEQ almost full
Beacon sets IV Update Flag = 1
|
→ |
IV Index = 1
All nodes accept new IV, SEQ resets
|
3.3 Authentication Value — how the beacon is signed
The Authentication Value prevents a rogue node from forging a beacon. It is computed using AES-CMAC — a standard message authentication code algorithm — over the Flags, Network ID, and IV Index, keyed with the Beacon Key (derived from the network key).
|
Input
Flags (1B) + Network ID (8B) + IV Index (4B)
|
+
keyed with
|
Beacon Key
Derived from Network Key using k1 function
|
→ |
AES-CMAC
|
→ |
Auth Value
First 8 bytes of output
|
Any node receiving a Secure Network Beacon recomputes this value independently using its own copy of the Beacon Key. If the values match, the beacon is genuine.
3.4 Beacon Interval — how often should nodes send beacons?
You don’t want every node flooding the air with beacons every second. The spec defines a self-adjusting algorithm. Each node watches how many beacons it sees over an observation window and adjusts its own sending rate accordingly.
The formula is:
Beacon Interval = Observation Period
× (Observed Beacons + 1)
/ Expected Number of Beacons
Expected Beacons = Observation Period / 10 seconds
Min interval = 10 seconds
Max interval = 600 seconds
| Scenario | Observed Beacons | Observation Period | Expected Beacons | Resulting Interval |
| Quiet network — few nodes | 1 | 20s | 2 | 20s → clamped to 10s |
| Normal operation | 5 | 60s | 6 | 60s |
| Dense network — many nodes | 20 | 200s | 20 | 210s (back-off applied) |
The logic is simple: if you are seeing a lot of beacons from other nodes, wait longer before sending your own. This prevents beacon storms in large networks.
3.5 BlueZ: observing secure network beacons with btmon
You can capture raw mesh beacon packets on Linux using btmon and then cross-reference with your network’s IV index:
# Capture HCI traffic — mesh beacons will appear as HCI LE meta events
sudo btmon -w /tmp/mesh_capture.log
# In another terminal start the mesh daemon and join a network
sudo bluetooth-meshd --config /etc/bluetooth/mesh --nodetach
# Back in btmon output, look for AD type 0x2B (Mesh Beacon)
# A Secure Network Beacon (type 0x01) raw bytes example:
# 01 ← Beacon Type: Secure Network beacon
# 02 ← Flags: 0b00000010 → IV Update active, Key Refresh = false
# A1B2C3D4E5F60718 ← Network ID (8 bytes)
# 00000001 ← IV Index = 1 (IV update just happened)
# DEADBEEF12345678 ← Authentication Value (AES-CMAC output, first 8 bytes)
A mesh network does not exist until a Provisioner creates it. The provisioner is the trusted entity that hands out network keys and addresses — like a network administrator. Typically this is a smartphone app or a dedicated gateway device.
Steps to create a mesh network:
| Step | What the Provisioner does | Details |
| 1 | Generate Network Key | Uses a TRNG (True Random Number Generator) compliant with BT Core Spec Vol 2 Part H §2 |
| 2 | Set IV Index to 0x00000000 | All fresh networks start with IV Index = 0. It only increments when sequence numbers are exhausted. |
| 3 | Allocate Unicast Address | The Provisioner’s own primary element gets the first address. Other elements get sequential addresses. |
| 4 | Scan for unprovisioned devices | Uses active or passive BLE scanning to find Unprovisioned Device Beacons |
| 5 | Provision devices | Sends network key + unicast address to each new device using the PB-ADV or PB-GATT bearer |
| 6 | Configure via Config Client | Distributes app keys, sets publish/subscribe addresses so nodes can talk to each other |
4.1 BlueZ: provisioning a device with mesh-cfgclient
# Start mesh-cfgclient as a provisioner
mesh-cfgclient
# Discover unprovisioned devices
discover-unprovisioned on
# After seeing a device UUID, provision it
# Replace UUID with the one shown in discovery output
provision aabbccdd-eeff-0011-2233-445566778899
# The tool will:
# 1. Run the provisioning protocol (exchange keys via OOB)
# 2. Assign a unicast address (e.g., 0x0002)
# 3. Send the network key to the device
# After provisioning succeeds:
# Configure a node — add an application key
appkey-add 0x0002
# Set a publish address on the device's Generic OnOff model
publish-set 0x0002 0 0xC000
# 0x0002 = unicast address of the node
# 0 = element index
# 0xC000 = group address to publish messages to
Imagine you run a hotel. Guests should be able to control the lights in their own room, but not the master switch for the whole building. Bluetooth Mesh handles this elegantly with temporary guest access.
A guest is given its own separate network key — not the main network key. Only the nodes the guest is allowed to reach also get this guest network key. Additionally, only specific application keys are shared with the guest, restricting which models (features) they can interact with.
|
⟷ |
|
||||||||||||||||
Key design decisions behind this:
- The guest never sees NK-Main, so it cannot decrypt or inject messages on the main subnet.
- App keys are also guest-specific, restricting which model operations (like Lock vs Unlock) the guest can perform.
- Guests cannot trigger IV Index updates on the primary subnet — this protects the network-wide sequence number counter from a potentially compromised guest device.
- Multiple guests can exist simultaneously, each in their own isolated subnet.
5.1 BlueZ: setting up a guest subnet
# Inside mesh-cfgclient, add a second network key for the guest subnet
# NetKey index 1 will be the guest subnet key
netkey-add 0x0001
# Provision the guest device with the guest network key (index 1)
provision aabbccdd-1111-2222-3333-445566778800
# After provisioning the guest node (address 0x0010):
# Add the guest app key (app key index 2, bound to net key index 1)
appkey-add 0x0010 2
# Set the guest node's publish address to a guest-only group
publish-set 0x0010 0 0xC100
# 0xC100 = guest group address — main network nodes won't subscribe to this
# For nodes that should be reachable by the guest (e.g. room lights at 0x0005):
# Also give them the guest network key
netkey-add 0x0005 0x0001
appkey-add 0x0005 2
# Now 0x0005 listens on both the main subnet AND the guest subnet
Here is how all the pieces we covered fit into a single device’s journey from factory to fully participating mesh node:
|
① Power On
Device has no network key
|
→ |
② Broadcasts
Unprovisioned Device Beacon (UUID + OOB info)
|
→ |
③ Provisioner sees beacon
Scans UUID, reads OOB location bit
|
→ |
④ Provisioning
Keys + unicast address delivered securely
|
→ |
⑤ Node active
Now sends Secure Network Beacons with AES-CMAC auth
|
| Property | Unprovisioned Device Beacon | Secure Network Beacon |
| Beacon Type byte | 0x00 | 0x01 |
| When sent | Before provisioning | After provisioning, periodically |
| Total size | 23 bytes (19 + optional 4 URI Hash) | 22 bytes (fixed) |
| Authenticated? | No — open broadcast | Yes — AES-CMAC with Beacon Key |
| Key content | Device UUID + OOB Info | Network ID + IV Index + Flags |
| Who sends it | Unprovisioned device only | Relay and Friend nodes (others may) |
| Typical interval | Continuous until provisioned | ~10 seconds (adaptive 10–600s) |
| Purpose | Device discovery | IV Index sync + Key Refresh notification |
Continue the Bluetooth Mesh Series
This post covered the beacon layer and network bootstrap. Next we go deeper into IV Index updates, Key Refresh procedures, and how messages actually flow through the mesh.
