bluez tutorial – BLE Link Layer Privacy whitelist in ble

 

BLE Link Layer Privacy 1.2 & White List
Chapter 8 Sections 8.14–8.15 — IRK, Resolving List, Private Addresses & Device Filtering with Filter Policies
RPA Rotation
Every 15 minutes
Key
IRK (128-bit)
Adv Filter
4 modes
Scan Filter
2 + 2 new (4.2)
Keywords:

BLE link layer privacy 1.2 BLE identity resolution key IRK BLE resolving list HCI BLE private address 15 minutes BLE resolvable private address RPA BLE white list device filtering BLE advertising filter policy BLE scanner filter policy 4.2 BlueZ LE_Add_Device_To_Resolving_List

8.14 — Link Layer Privacy 1.2

Every time a BLE device broadcasts an advertising packet, it includes its Bluetooth address. If that address never changes, any device nearby — a phone, a laptop, or a dedicated sniffer — can passively log every packet and build a precise picture of where that device (and the person carrying it) has been. This is a real privacy problem for wearable devices.

BLE privacy addresses this by using rotating, pseudo-random addresses instead of the permanent hardware address. This file explains exactly how the mechanism works and introduces the White List — a power-saving filter that limits which devices the link layer will interact with.

The Fitness Band Tracking Problem — A Concrete Example

How Passive Tracking Works Without Privacy

Imagine a user wearing a fitness band on their wrist. Every time the band needs to sync data with the user’s phone, it starts advertising — broadcasting packets on channels 37, 38, and 39 to announce its presence. These packets contain the band’s fixed Bluetooth address.

The problem is that anyone nearby — not just the paired phone — can receive these advertising packets. A mall, an office building, or any public space could have passive BLE scanners that log every address they hear along with a timestamp. By correlating the same fixed address appearing at different locations and times, a tracker can build a complete movement profile of the device owner.

Without Privacy — Fixed Address Enables Passive Tracking
8:30 AM
ADV_IND | AA:BB:CC:11:22:33 | location: home
9:15 AM
ADV_IND | AA:BB:CC:11:22:33 | location: office lobby
1:00 PM
ADV_IND | AA:BB:CC:11:22:33 | location: hospital
6:45 PM
ADV_IND | AA:BB:CC:11:22:33 | location: gym
Same address every packet → passive observer knows the person’s full daily routine

The solution is to replace the fixed address with a pseudo-random address that changes regularly. A passive observer sees many different addresses and cannot tell which ones belong to the same device. Only the device’s paired partner — who holds a secret key — can resolve the random addresses back to the real identity.

How BLE Privacy Works — Random Addresses and IRK

Instead of advertising with its real hardware address, a privacy-enabled device uses a Resolvable Private Address (RPA). An RPA looks like a random Bluetooth address to any observer. However, it is not truly random — it is generated using a mathematical function that takes the device’s Identity Resolution Key (IRK) as input.

The IRK is a 128-bit secret key. It is exchanged between two devices during the pairing process and stored securely. Because the paired device also has the IRK, it can run the same function in reverse to check whether a given random address was generated by the device it knows. This is called address resolution.

IRK-Based Address Resolution — Only Bonded Devices Can Identify the Sender

Fitness Band
Has IRK_band
Generates RPA using IRK
Advertises with RPA

ADV_IND
addr: 7D:A3:F1:22:C8:B0
(RPA)

📱
Paired Phone
Has IRK_band
Resolves 7D:A3:F1… → AA:BB:CC:11:22:33
✅ Recognises fitness band
🔍
Malicious Sniffer
No IRK
Sees only 7D:A3:F1…
❌ Cannot link to any person

8.14.1 — Address Resolution in the Controller (BLE 4.2)

Moving Privacy From Host to Controller — A Key Power Saving

In BLE 4.0 the generation and resolution of private addresses was done entirely in the Host — the application processor running the software stack. Every time the controller received an advertising packet with a random address, it had to wake up the Host so the Host could check whether the address belonged to a known bonded device.

In a busy environment with many BLE devices advertising nearby, this creates a lot of unnecessary Host wake-ups. Most of those devices are completely irrelevant to the application — they belong to strangers. All those wake-ups burn CPU time and battery on the Host just to sort through irrelevant devices.

BLE 4.2 moved address resolution down into the Controller (the Bluetooth chip). Now the controller handles the filtering itself. The Host only wakes up when the controller finds a packet from a known, bonded device. This is a significant power saving in environments with many BLE devices around.

BLE 4.0 vs 4.2 — Where Address Resolution Happens

BLE 4.0 — Host resolves (slow)
Random ADV packet arrives
Controller wakes up Host
Host checks IRKs (CPU + power)
Host decides: known or unknown?
Every single random packet wakes the Host

BLE 4.2 — Controller resolves (fast)
Random ADV packet arrives
Controller checks Resolving List
Unknown → silently discarded
Known → notify Host
Host only wakes for devices it actually cares about

8.14.1.1 — Device Identity and the Resolving List

For the controller to resolve addresses on its own, the host must first give it the necessary information. This information is called device identity information and is provided for each bonded device. A device identity contains:

Identity Address

The peer device’s real, permanent Bluetooth address. This is the address the host uses when referring to that device (not the RPA).

Local IRK

This device’s own Identity Resolution Key — used to generate this device’s own private addresses.

Peer IRK

The paired device’s Identity Resolution Key — used to resolve incoming addresses that the peer generated.

The complete set of device identities for all bonded devices is called the Resolving List. The host maintains this list and pushes it to the controller. The controller uses it to check every incoming random address — if it can be resolved using any IRK in the list, the packet is from a known bonded device. The host can add or remove individual entries using HCI commands at any time.

There is one practical constraint: the controller chip has limited memory. If there are more bonded devices than the controller can store, the host may need to provide only a subset of the resolving list. The number of entries the controller can hold is reported via the HCI command HCI_LE_Read_Resolving_List_Size.

/* Managing the Resolving List via BlueZ HCI commands */

/* Add a bonded device to the controller's resolving list */
/* OGF=0x08, OCF=0x0027 = LE Add Device To Resolving List */
typedef struct {
    uint8_t  peer_addr_type; /* 0=public, 1=random static  */
    uint8_t  peer_addr[6];   /* peer's identity address    */
    uint8_t  peer_irk[16];   /* peer's IRK (128-bit)       */
    uint8_t  local_irk[16];  /* local IRK (128-bit)        */
} __attribute__((packed)) le_add_device_to_resolving_list_cp;

/* Remove a device from the resolving list                */
/* OGF=0x08, OCF=0x0028 = LE Remove Device From RL       */

/* Enable address resolution in the controller           */
/* OGF=0x08, OCF=0x002D = LE Set Address Resolution En. */

/* Read how many entries the controller can hold         */
/* OGF=0x08, OCF=0x002A = LE Read Resolving List Size   */
/* Returns: uint8_t resolving_list_size                  */

/* Using bluetoothctl to view bonded devices (have IRKs): */
/* bluetoothctl                                           */
/* [bluetooth]# devices Bonded                           */

8.14.2 — Better Privacy — Private Address Details

8.14.2.1 — When Does the Address Rotate? Every 15 Minutes

The link layer uses a timer to control how often it generates a new private address. A new address is generated when either of two things happens:

Condition 1 — Link Layer Reset

When the device boots up or the link layer is reset, a new private address is immediately generated before any advertising begins.

Condition 2 — Timer Expires

The timer counts down to zero. A new address is generated, and the timer is immediately restarted. The specification recommends this timer be set to 15 minutes.

The 15-minute interval is a balance. A very short interval (say, every 30 seconds) would mean the paired device has to constantly re-resolve the address in order to keep the connection alive, which wastes battery. A very long interval (say, 1 hour) would give passive trackers a longer window to track the device before the address changes. 15 minutes is the recommended sweet spot.

/* Setting the RPA rotation timeout via BlueZ HCI       */
/* OGF=0x08, OCF=0x002E = LE Set Resolvable Private    */
/*                         Address Timeout             */
typedef struct {
    uint16_t rpa_timeout; /* timeout in seconds          */
                          /* recommended: 900 (15 min)  */
                          /* range: 1 to 65535 seconds  */
} __attribute__((packed)) le_set_rpa_timeout_cp;

/* Example: set 15-minute rotation */
le_set_rpa_timeout_cp cp = { .rpa_timeout = htobs(900) };
hci_send_cmd(sock, OGF_LE_CTL,
             OCF_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT,
             sizeof(cp), &cp);
8.14.2.2 — Privacy Across All Three Non-Connected States

The private address is not just used during advertising — it is used during advertising, scanning, and initiating states. This is important because a device is identifiable by its address in all three states, not just when it is advertising.

Privacy in Action — Fitness Band Connecting to Phone
⌚ Fitness Band
(Advertising)
ADV_IND or ADV_DIRECT_IND
AdvA = RPA of fitness band (not real address)
📱 Mobile Phone
(Scanning)
SCAN_REQ
ScanA = phone’s own RPA (not phone’s real address)
📱 Phone
(Initiating)
CONNECT_REQ
InitA = phone’s RPA, AdvA = band’s RPA
Both devices use private addresses in every exchange — passive observers cannot identify either device

8.15 — Device Filtering and White List

Why Device Filtering Saves Power

In a busy environment, the BLE radio receives advertising packets from many devices — headsets, keyboards, other people’s phones, beacons, sensors. Most of these are completely irrelevant to the current application. Without filtering, the controller would have to process every packet and wake up the host for each one. That wastes both radio time and processor cycles.

The White List is a set of device addresses stored in the controller. When a filter policy is active, the controller only responds to or reports devices whose addresses appear in this list. Packets from unlisted devices are silently discarded at the hardware level — the host never sees them.

This has two major benefits:

Power Saving

The controller processes fewer packets. The host is woken up less often. The radio sends fewer responses. All of this directly reduces battery consumption on both the controller chip and the host CPU.

Simplified Connection Logic

A host can tell the controller “connect to whichever of these three known sensors becomes available first.” The controller handles the race and reports back which device it connected to, via the Peer_Address field of the LE_Connection_Complete event. The host does not need to write retry loops.

The White List starts empty at controller reset. It is populated by the host using HCI commands. HCI_LE_Add_Device_To_White_List adds an address and HCI_LE_Remove_Device_From_White_List removes one. HCI_LE_Clear_White_List empties the whole list at once.

8.15.1 — Advertising Filter Policy — Four Modes

The Advertising Filter Policy controls how the Advertiser’s link layer handles two types of incoming requests: SCAN_REQ (from scanners wanting more data) and CONNECT_REQ (from initiators wanting to connect). The host sets this policy using HCI_LE_Set_Advertising_Parameters.

There are four possible modes. Only one can be active at a time:

Four Advertising Filter Policy Modes
0
Default (reset state) — White List NOT in use
Process all SCAN_REQ and CONNECT_REQ from any device regardless of whether it is in the white list. This is the open mode.
SCAN ✓ all
CONN ✓ all
1
White List for both — maximum filtering
Process SCAN_REQ and CONNECT_REQ only from devices in the white list. Any request from an unlisted device is dropped silently.
SCAN ✓ WL only
CONN ✓ WL only
2
White List for connections only — open to scanners
Process SCAN_REQ from any device. Process CONNECT_REQ only from devices in the white list. Useful when you want to be discoverable but only connectable by known devices.
SCAN ✓ all
CONN ✓ WL only
3
White List for scans only — open to connections
Process CONNECT_REQ from any device. Process SCAN_REQ only from devices in the white list. Unusual but valid if you want to avoid unknown scanners getting extra data.
SCAN ✓ WL only
CONN ✓ all

Exception for ADV_DIRECT_IND: When the Advertiser is using Connectable Directed advertising (ADV_DIRECT_IND), the filter policy is not used at all. Instead, the Advertiser accepts requests only from the specific device named in the InitA field of the advertising packet. The White List filter is irrelevant in this case.

/* Setting Advertising Filter Policy via BlueZ HCI */
/* Part of HCI_LE_Set_Advertising_Parameters       */
/* OGF=0x08, OCF=0x0006                            */

#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

le_set_advertising_parameters_cp adv_params;
memset(&adv_params, 0, sizeof(adv_params));

adv_params.min_interval   = htobs(0x0320); /* 500ms   */
adv_params.max_interval   = htobs(0x0320);
adv_params.advtype        = 0x00;  /* ADV_IND         */
adv_params.chan_map       = 0x07;  /* all channels    */

/* filter_policy values:                            */
/* 0x00 = allow all (default)                       */
/* 0x01 = white list for both scan and connect      */
/* 0x02 = white list for connect only               */
/* 0x03 = white list for scan only                  */
adv_params.filter_policy  = 0x01;  /* WL for both    */

hci_send_cmd(sock, OGF_LE_CTL,
             OCF_LE_SET_ADVERTISING_PARAMETERS,
             LE_SET_ADVERTISING_PARAMETERS_CP_SIZE,
             &adv_params);

8.15.2 — Scanner Filter Policy — Two Modes (Plus Two New in 4.2)

The Scanner Filter Policy controls which advertising packets the Scanner’s link layer accepts and passes up to the host. It is set using HCI_LE_Set_Scan_Parameters.

BLE 4.0 defined two basic modes. BLE 4.2 added two extended modes specifically to support link layer privacy:

Mode 0 — Default

White List NOT in use

Process all advertising packets from any device regardless of address. This is the default mode after controller reset. The scanner reports every advertising packet to the host.

Mode 1

White List in use

Process advertising packets only from devices in the white list. Packets from all other devices are silently discarded in the controller before they reach the host. Major power saving in a crowded BLE environment.

Mode 2 — BLE 4.2

White List + RPA support

Same as Mode 1 (white list in use), and additionally: do not ignore a connectable directed advertising packet (ADV_DIRECT_IND) if the InitA field contains a resolvable private address. In BLE 4.0, such packets would be dropped because the scanner’s RPA would not match the fixed address the ADV_DIRECT_IND was sent to. BLE 4.2 fixed this by allowing the scanner to resolve the RPA first.

Mode 3 — BLE 4.2

All packets + RPA support

Same as Mode 0 (all packets), and additionally: do not ignore a connectable directed advertising packet if the InitA field is a resolvable private address. The default after reset is still the same as BLE 4.0 Mode 0.

The two 4.2 extensions (Modes 2 and 3) solve a problem that existed with the BLE 4.0 scanner filter policies when both devices were using private addresses. A device sending ADV_DIRECT_IND to a scanner that uses an RPA would include the scanner’s RPA in the InitA field. But the scanner’s RPA changes every 15 minutes. In BLE 4.0, the scanner would not recognise its own old RPA in the InitA and would discard the packet. BLE 4.2 allows the scanner to resolve the InitA address first before deciding to accept or discard.

/* Setting Scanner Filter Policy via BlueZ HCI */
/* Part of LE Set Scan Parameters command       */
/* OGF=0x08, OCF=0x000B                        */

le_set_scan_parameters_cp scan_params;
memset(&scan_params, 0, sizeof(scan_params));

scan_params.type     = 0x01; /* active scan          */
scan_params.interval = htobs(0x0010); /* 10ms        */
scan_params.window   = htobs(0x0010); /* 10ms        */
scan_params.own_bdaddr_type = 0x02;   /* use RPA     */

/* filter_policy values:                             */
/* 0x00 = accept all (default, BLE 4.0)             */
/* 0x01 = white list only (BLE 4.0)                 */
/* 0x02 = white list + allow directed RPA (BLE 4.2) */
/* 0x03 = all    + allow directed RPA (BLE 4.2)     */
scan_params.filter  = 0x02; /* WL + RPA support      */

hci_send_cmd(sock, OGF_LE_CTL,
             OCF_LE_SET_SCAN_PARAMETERS,
             LE_SET_SCAN_PARAMETERS_CP_SIZE,
             &scan_params);

Next — Initiator Filter Policy, Practical Examples & Chapter 8 Summary

You now understand Link Layer Privacy and the Advertising/Scanner Filter Policies. The next file covers the Initiator Filter Policy, real sniffer captures showing procedures in action, and the full Chapter 8 summary.

Next: Initiator Filter, Practical Examples & Summary →

Leave a Reply

Your email address will not be published. Required fields are marked *