在嵌入式蓝牙开发中,设备扫描通常面临一个实际问题:周围可能有几十上百个蓝牙设备在广播,但我们只关心特定的几个------某个厂商的传感器、某个 MAC 段的设备、名称包含特定前缀的信标。原生 BlueZ 的扫描过滤机制虽然支持 UUID/RSSI/Pathloss 过滤,但无法按 MAC 地址、设备名称、厂商 ID 进行精准筛选,更不支持黑白名单机制。每次扫描都会将所有设备上报给上层应用,造成大量无效数据处理和 D-Bus 通信开销。本文手把手改造 BlueZ 5.x 源码,在设备发现的核心路径中添加自定义过滤规则,支持 MAC 地址过滤、设备名称匹配、BLE 厂商 ID 筛选、黑白名单机制,实现精准设备筛选。
目录
[一、原生 BlueZ 扫描过滤机制全貌](#一、原生 BlueZ 扫描过滤机制全貌)
[八、厂商 ID 速查表](#八、厂商 ID 速查表)
[十、EIR/AD 数据类型速查](#十、EIR/AD 数据类型速查)
一、原生 BlueZ 扫描过滤机制全貌
1.1 设备发现与过滤链路
cpp
┌─────────────────────────────────────────────────────────────────
│ 原生 BlueZ 设备发现与过滤链路
├─────────────────────────────────────────────────────────────────
│
│ 内核 MGMT Core
│ └─ MGMT_EV_DEVICE_FOUND (bdaddr + rssi + eir_data)
│ │
│ ▼
│ src/adapter.c: device_found_callback() ← 第 7097 行
│ ├─ 解析 mgmt_ev_device_found
│ ├─ 提取 bdaddr, addr_type, rssi, flags, eir, eir_len
│ └─ btd_adapter_update_found_device()
│ │ ← 第 6878 行
│ │
│ ├─ bt_ad_new_with_data() → 广告监控过滤
│ │ └─ btd_adv_monitor_content_filter()
│ │
│ ├─ eir_parse(&eir_data, data, data_len)
│ │ 解析 EIR/AD 数据 → 填充 eir_data 结构
│ │ ├─ services (UUID 列表)
│ │ ├─ name (设备名称)
│ │ ├─ tx_power (发射功率)
│ │ ├─ class (设备类)
│ │ ├─ appearance (外观)
│ │ ├─ flags (广播标志)
│ │ ├─ msd_list (厂商特定数据) ← struct eir_msd
│ │ └─ did_vendor/did_product (设备 ID)
│ │
│ ├─ device_is_discoverable() ← 第 6832 行
│ │ 检查设备是否可发现(flags / pattern 匹配)
│ │
│ ├─ btd_adapter_find_device() --- 查找/创建设备对象
│ │
│ ├─ is_filter_match() ← 第 6767 行
│ │ ★ 原生过滤检查点
│ │ ├─ UUID 匹配 (client->discovery_filter->uuids)
│ │ ├─ RSSI 阈值 (item->rssi)
│ │ └─ Pathloss 阈值 (item->pathloss)
│ │
│ └─ 更新设备信息 + D-Bus 通知
│ ├─ device_set_rssi()
│ ├─ device_set_tx_power()
│ ├─ btd_device_device_set_name()
│ └─ g_dbus_emit_property_changed()
│
└─────────────────────────────────────────────────────────────────
1.2 原生过滤机制 源码 解析
is_filter_match() --- 过滤匹配函数
src/adapter.c:
cpp
static bool is_filter_match(GSList *discovery_filter, struct eir_data *eir_data,
int8_t rssi)
{
GSList *l, *m;
bool got_match = false;
for (l = discovery_filter; l != NULL && got_match != true;
l = g_slist_next(l)) {
struct discovery_client *client = l->data;
struct discovery_filter *item = client->discovery_filter;
/* 无过滤器的客户端 → 匹配所有设备 */
if (!item) {
got_match = true;
continue;
}
/* 空 UUID 列表 → 匹配所有设备(仅 proximity 过滤) */
if (!item->uuids)
got_match = true;
else {
/* UUID 匹配:检查设备服务列表是否包含请求的 UUID */
for (m = item->uuids; m != NULL && got_match != true;
m = g_slist_next(m)) {
if (g_slist_find_custom(eir_data->services,
m->data, g_strcmp) != NULL)
got_match = true;
}
}
/* proximity 过滤:RSSI / Pathloss */
if (got_match) {
if (item->rssi == DISTANCE_VAL_INVALID ||
item->rssi <= rssi ||
item->pathloss == DISTANCE_VAL_INVALID ||
(eir_data->tx_power != 127 &&
eir_data->tx_power - rssi <= item->pathloss))
return true;
got_match = false;
}
}
return got_match;
}
discovery_filter 结构体
src/adapter.c:
cpp
struct discovery_filter {
uint8_t type; /* 扫描类型 */
char *pattern; /* 名称/地址模式匹配(前缀) */
uint16_t pathloss; /* 路径损耗阈值 */
int16_t rssi; /* RSSI 阈值 */
GSList *uuids; /* UUID 过滤列表 */
bool duplicate; /* 去重 */
bool discoverable; /* 仅可发现设备 */
};
eir_data 结构体 --- 广播数据解析结果
src/eir.h :
cpp
struct eir_data {
GSList *services; /* 服务 UUID 列表 */
unsigned int flags; /* 广播标志 */
char *name; /* 设备名称 */
uint32_t class; /* 设备类 */
uint16_t appearance; /* GAP 外观 */
bool name_complete; /* 名称是否完整 */
int8_t tx_power; /* 发射功率 */
uint8_t *hash; /* SSP Hash */
uint8_t *randomizer; /* SSP Randomizer */
bdaddr_t addr; /* 地址 */
uint16_t did_vendor; /* ★ 厂商 ID (Device ID) */
uint16_t did_product; /* 产品 ID */
uint16_t did_version; /* 版本 */
uint16_t did_source; /* 来源 */
GSList *msd_list; /* ★ 厂商特定数据列表 (MSD) */
GSList *sd_list; /* 服务数据列表 */
GSList *data_list; /* 原始 AD 数据列表 */
};
eir_msd 结构体 --- 厂商特定数据
src/eir.h:
cpp
struct eir_msd {
uint16_t company; /* ★ 厂商 ID (Company ID) */
uint8_t data[EIR_MSD_MAX_LEN]; /* 厂商数据 (最多 236 字节) */
uint8_t data_len; /* 数据长度 */
};
EIR Manufacturer Specific Data 的解析在 src/eir.c :
cpp
static void eir_parse_msd(struct eir_data *eir, const uint8_t *data, uint8_t len)
{
struct eir_msd *msd;
if (len < 2 || len > 2 + sizeof(msd->data))
return;
msd = g_malloc(sizeof(*msd));
msd->company = get_le16(data); /* 前 2 字节为 Company ID */
msd->data_len = len - 2;
memcpy(&msd->data, data + 2, msd->data_len);
eir->msd_list = g_slist_append(eir->msd_list, msd);
}
1.3 原生过滤机制的五大短板
|--------------------|------------------------------|-------------------------------|
| 短板 | 说明 | 实际影响 |
| 无 MAC 地址过滤 | pattern 只做前缀匹配,无完整 MAC/地址段过滤 | 无法只发现指定设备,所有设备全部上报 |
| 无设备名称过滤 | pattern 只做前缀匹配,不支持多前缀/正则 | 无法按名称精准筛选(如只找 "Sensor_" 前缀设备) |
| 无厂商 ID 过滤 | eir_data.msd_list 已解析但未用于过滤 | 无法按芯片厂商筛选(如只找 Nordic/TI 设备) |
| 无黑白名单 | 不支持全局黑白名单机制 | 无法在 bluetoothd 层面阻止某些设备被发现 |
| 过滤依赖 D-Bus 客户端 | 过滤规则绑定在 discovery_client 上 | 无 D-Bus 客户端时(如自动扫描),所有设备全部通过 |
二、自定义过滤方案设计
2.1 目标功能
|--------------|------------------------------|----------------------|-------------|-------------|
| 功能 | 过滤维度 | 数据来源 | 匹配方式 | 应用场景 |
| MAC 地址过滤 | bdaddr | mgmt_ev_device_found | 精确匹配 / 前缀匹配 | 只发现指定设备或地址段 |
| 设备名称过滤 | eir_data.name | EIR 0x08/0x09 | 前缀匹配(支持多个) | 按设备名筛选传感器 |
| 厂商 ID 过滤 | eir_msd.company / did_vendor | EIR 0xFF/0x10 | 精确匹配 | 按芯片厂商筛选 |
| 黑白名单 | bdaddr | 全局配置 | 全局白名单/黑名单 | 全局放行/阻止特定设备 |
| RSSI 阈值 | rssi | mgmt_ev_device_found | 最小值 | 过滤远距离设备 |
2.2 过滤架构设计
cpp
┌─────────────────────────────────────────────────────────────────
│ 自定义过滤架构
├─────────────────────────────────────────────────────────────────
│
│ btd_adapter_update_found_device()
│ │
│ ├─ eir_parse(&eir_data, data, data_len) ← 解析广播数据
│ │
│ ├─ ★ custom_filter_check(adapter, bdaddr, &eir_data, rssi)
│ │ │ ← 新增:自定义过滤检查点
│ │ │
│ │ ├─ 1. 黑名单检查 (blacklist)
│ │ │ └─ if in blacklist → REJECT
│ │ │
│ │ ├─ 2. 白名单检查 (whitelist)
│ │ │ └─ if whitelist active && not in whitelist → REJECT
│ │ │
│ │ ├─ 3. MAC 地址过滤 (mac_prefix)
│ │ │ └─ if mac_filter active && !mac_match → REJECT
│ │ │
│ │ ├─ 4. 名称过滤 (name_prefix)
│ │ │ └─ if name_filter active && !name_match → REJECT
│ │ │
│ │ ├─ 5. 厂商 ID 过滤 (vendor_id)
│ │ │ └─ if vendor_filter active && !vendor_match → REJECT
│ │ │
│ │ ├─ 6. RSSI 阈值 (rssi_threshold)
│ │ │ └─ if rssi < threshold → REJECT
│ │ │
│ │ └─ return ACCEPT / REJECT
│ │
│ ├─ if REJECT → return (丢弃设备,不上报 D-Bus)
│ │
│ └─ if ACCEPT → 继续原有处理流程
│ └─ btd_adapter_find_device() → D-Bus 通知
│
└─────────────────────────────────────────────────────────────────
2.3 过滤模式
|------------|-----------|------------------------------------------|
| 模式 | 行为 | 示例(NamePrefix=Sensor_ + VendorId=89) |
| loose | 任一条件满足即通过 | 名称以 Sensor_ 开头 OR 厂商为 Nordic → 通过 |
| strict | 所有条件都必须满足 | 名称以 Sensor_ 开头 AND 厂商为 Nordic → 通过 |
黑名单始终最高优先级:即使在 loose 模式下其他条件都满足,黑名单中的设备也会被拒绝。
2.4 配置方式
通过 /etc/bluetooth/main.conf 配置过滤规则:
cpp
[DeviceFilter]
Blacklist=AA:BB:CC:DD:EE:FF,11:22:33:44:55:66
Whitelist=AA:BB:CC:DD:EE:FF
MacPrefix=AA:BB:CC,
NamePrefix=Sensor_,Beacon_
VendorId=89,76
RssiThreshold=-80
FilterMode=loose
三、手把手源码修改
3.1 修改 src/btd.h --- 添加过滤配置字段
打开 src/btd.h,在 struct btd_opts 中添加:
cpp
struct btd_opts {
/* ... 原有字段 ... */
/* ===== 新增:自定义设备过滤配置 ===== */
GSList *filter_blacklist; /* 黑名单 MAC 列表 */
GSList *filter_whitelist; /* 白名单 MAC 列表 */
GSList *filter_mac_prefix; /* MAC 前缀列表 */
GSList *filter_name_prefix; /* 名称前缀列表 */
GSList *filter_vendor_ids; /* 厂商 ID 列表 */
int filter_rssi_threshold; /* RSSI 最小阈值 */
int filter_mode; /* 0=loose, 1=strict */
gboolean filter_enabled; /* 过滤是否启用 */
};
3.2 修改 src/main.c --- 添加配置解析
打开 src/main.c。
修改点 1:在 init_defaults() 中设置默认值
cpp
static void init_defaults(void)
{
/* ... 原有代码 ... */
/* ===== 新增:过滤默认配置 ===== */
btd_opts.filter_enabled = FALSE;
btd_opts.filter_rssi_threshold = -127; /* 默认不过滤 */
btd_opts.filter_mode = 0; /* loose 模式 */
}
修改点 2:在 parse_config() 末尾添加过滤配置解析
cpp
/* ===== 新增:解析 DeviceFilter 配置 ===== */
{
gchar **strlist;
gchar *strval;
int i, val;
/* Blacklist --- MAC 地址列表 */
strlist = g_key_file_get_string_list(config, "DeviceFilter",
"Blacklist", NULL, &err);
if (!err && strlist) {
for (i = 0; strlist[i]; i++) {
bdaddr_t *ba = g_new0(bdaddr_t, 1);
str2ba(strlist[i], ba);
btd_opts.filter_blacklist =
g_slist_append(btd_opts.filter_blacklist, ba);
}
g_strfreev(strlist);
btd_opts.filter_enabled = TRUE;
}
g_clear_error(&err);
/* Whitelist --- MAC 地址列表 */
strlist = g_key_file_get_string_list(config, "DeviceFilter",
"Whitelist", NULL, &err);
if (!err && strlist) {
for (i = 0; strlist[i]; i++) {
bdaddr_t *ba = g_new0(bdaddr_t, 1);
str2ba(strlist[i], ba);
btd_opts.filter_whitelist =
g_slist_append(btd_opts.filter_whitelist, ba);
}
g_strfreev(strlist);
btd_opts.filter_enabled = TRUE;
}
g_clear_error(&err);
/* MacPrefix --- MAC 地址前缀列表 */
strlist = g_key_file_get_string_list(config, "DeviceFilter",
"MacPrefix", NULL, &err);
if (!err && strlist) {
for (i = 0; strlist[i]; i++) {
btd_opts.filter_mac_prefix =
g_slist_append(btd_opts.filter_mac_prefix,
g_strdup(strlist[i]));
}
g_strfreev(strlist);
btd_opts.filter_enabled = TRUE;
}
g_clear_error(&err);
/* NamePrefix --- 设备名称前缀列表 */
strlist = g_key_file_get_string_list(config, "DeviceFilter",
"NamePrefix", NULL, &err);
if (!err && strlist) {
for (i = 0; strlist[i]; i++) {
btd_opts.filter_name_prefix =
g_slist_append(btd_opts.filter_name_prefix,
g_strdup(strlist[i]));
}
g_strfreev(strlist);
btd_opts.filter_enabled = TRUE;
}
g_clear_error(&err);
/* VendorId --- 厂商 ID 列表(十进制或 0x 十六进制) */
strlist = g_key_file_get_string_list(config, "DeviceFilter",
"VendorId", NULL, &err);
if (!err && strlist) {
for (i = 0; strlist[i]; i++) {
uint16_t *vid = g_new(uint16_t, 1);
*vid = (uint16_t)strtol(strlist[i], NULL, 0);
btd_opts.filter_vendor_ids =
g_slist_append(btd_opts.filter_vendor_ids, vid);
}
g_strfreev(strlist);
btd_opts.filter_enabled = TRUE;
}
g_clear_error(&err);
/* RssiThreshold --- RSSI 最小阈值 */
val = g_key_file_get_integer(config, "DeviceFilter",
"RssiThreshold", &err);
if (!err) {
btd_opts.filter_rssi_threshold = val;
btd_opts.filter_enabled = TRUE;
}
g_clear_error(&err);
/* FilterMode --- 过滤模式 */
strval = g_key_file_get_string(config, "DeviceFilter",
"FilterMode", &err);
if (!err && strval) {
if (g_str_equal(strval, "strict"))
btd_opts.filter_mode = 1;
else
btd_opts.filter_mode = 0;
g_free(strval);
}
g_clear_error(&err);
if (btd_opts.filter_enabled)
info("Device filter enabled (mode=%s, rssi_threshold=%d, "
"blacklist=%d, whitelist=%d, mac_prefix=%d, "
"name_prefix=%d, vendor_ids=%d)",
btd_opts.filter_mode ? "strict" : "loose",
btd_opts.filter_rssi_threshold,
g_slist_length(btd_opts.filter_blacklist),
g_slist_length(btd_opts.filter_whitelist),
g_slist_length(btd_opts.filter_mac_prefix),
g_slist_length(btd_opts.filter_name_prefix),
g_slist_length(btd_opts.filter_vendor_ids));
}
3.3 修改 src/adapter.c --- 添加过滤核心逻辑
打开 src/adapter.c。
修改点 1:添加过滤核心函数(在 btd_adapter_update_found_device 之前,约第 6870 行)
cpp
/* ===== 新增:自定义设备过滤核心实现 ===== */
#define FILTER_TAG "[DEV-FILTER] "
/* MAC 地址比较回调(用于 g_slist_find_custom) */
static int bdaddr_cmp(gconstpointer a, gconstpointer b)
{
return bacmp((const bdaddr_t *)a, (const bdaddr_t *)b);
}
/* 检查 MAC 地址是否匹配前缀列表 */
static bool mac_prefix_match(const bdaddr_t *bdaddr, GSList *prefix_list)
{
char addr[18];
GSList *l;
ba2str(bdaddr, addr);
for (l = prefix_list; l; l = l->next) {
const char *prefix = l->data;
size_t plen = strlen(prefix);
/* 去除尾部逗号或空格 */
while (plen > 0 && (prefix[plen-1] == ',' || prefix[plen-1] == ' '))
plen--;
if (plen > 0 && strncmp(addr, prefix, plen) == 0)
return true;
}
return false;
}
/* 检查设备名称是否匹配前缀列表 */
static bool name_prefix_match(const char *name, GSList *prefix_list)
{
GSList *l;
if (!name)
return false;
for (l = prefix_list; l; l = l->next) {
const char *prefix = l->data;
if (g_str_has_prefix(name, prefix))
return true;
}
return false;
}
/* 检查厂商 ID 是否匹配(检查 Device ID 和 MSD 两个来源) */
static bool vendor_id_match(struct eir_data *eir, GSList *vendor_list)
{
GSList *l, *m;
/* 来源 1: Device ID (EIR 0x10) */
if (eir->did_vendor) {
for (l = vendor_list; l; l = l->next) {
uint16_t vid = *(uint16_t *)l->data;
if (eir->did_vendor == vid)
return true;
}
}
/* 来源 2: Manufacturer Specific Data (EIR 0xFF) */
for (m = eir->msd_list; m; m = m->next) {
struct eir_msd *msd = m->data;
for (l = vendor_list; l; l = l->next) {
uint16_t vid = *(uint16_t *)l->data;
if (msd->company == vid)
return true;
}
}
return false;
}
/* ★ 主过滤检查函数 */
static bool custom_filter_check(struct btd_adapter *adapter,
const bdaddr_t *bdaddr,
struct eir_data *eir,
int8_t rssi)
{
char addr[18];
int match_count = 0;
int total_checks = 0;
bool result;
/* 过滤未启用 → 直接通过 */
if (!btd_opts.filter_enabled)
return true;
ba2str(bdaddr, addr);
/* 1. 黑名单检查(最高优先级,直接拒绝) */
if (btd_opts.filter_blacklist) {
if (g_slist_find_custom(btd_opts.filter_blacklist, bdaddr,
bdaddr_cmp)) {
info(FILTER_TAG "REJECT %s (blacklisted)", addr);
return false;
}
}
/* 2. 白名单检查 */
if (btd_opts.filter_whitelist) {
total_checks++;
if (g_slist_find_custom(btd_opts.filter_whitelist, bdaddr,
bdaddr_cmp)) {
match_count++;
if (btd_opts.filter_mode == 0) { /* loose: 白名单匹配直接通过 */
info(FILTER_TAG "ACCEPT %s (whitelist match)", addr);
return true;
}
} else {
/* strict 模式下,白名单是强制条件 */
if (btd_opts.filter_mode == 1) {
info(FILTER_TAG "REJECT %s (not in whitelist)", addr);
return false;
}
}
}
/* 3. MAC 地址前缀过滤 */
if (btd_opts.filter_mac_prefix) {
total_checks++;
if (mac_prefix_match(bdaddr, btd_opts.filter_mac_prefix)) {
match_count++;
if (btd_opts.filter_mode == 0) {
info(FILTER_TAG "ACCEPT %s (mac prefix match)", addr);
return true;
}
}
}
/* 4. 设备名称过滤 */
if (btd_opts.filter_name_prefix) {
total_checks++;
if (name_prefix_match(eir->name, btd_opts.filter_name_prefix)) {
match_count++;
if (btd_opts.filter_mode == 0) {
info(FILTER_TAG "ACCEPT %s (name match: %s)", addr,
eir->name ?: "(none)");
return true;
}
}
}
/* 5. 厂商 ID 过滤 */
if (btd_opts.filter_vendor_ids) {
total_checks++;
if (vendor_id_match(eir, btd_opts.filter_vendor_ids)) {
match_count++;
if (btd_opts.filter_mode == 0) {
info(FILTER_TAG "ACCEPT %s (vendor match)", addr);
return true;
}
}
}
/* 6. RSSI 阈值过滤 */
if (btd_opts.filter_rssi_threshold > -127) {
total_checks++;
if (rssi >= btd_opts.filter_rssi_threshold) {
match_count++;
if (btd_opts.filter_mode == 0) {
info(FILTER_TAG "ACCEPT %s (rssi %d >= %d)", addr,
rssi, btd_opts.filter_rssi_threshold);
return true;
}
}
}
/* 根据模式决定最终结果 */
if (btd_opts.filter_mode == 1) {
/* strict: 所有启用的条件都必须满足 */
result = (total_checks > 0 && match_count == total_checks);
} else {
/* loose: 任一条件满足即可,或无任何条件则通过 */
result = (match_count > 0 || total_checks == 0);
}
if (result)
info(FILTER_TAG "ACCEPT %s (%d/%d checks passed, rssi=%d, name=%s)",
addr, match_count, total_checks, rssi, eir->name ?: "(none)");
else
info(FILTER_TAG "REJECT %s (%d/%d checks passed, rssi=%d, name=%s)",
addr, match_count, total_checks, rssi, eir->name ?: "(none)");
return result;
}
修改点 2:在 btd_adapter_update_found_device 中插入过滤检查
在 src/adapter.c 第 6915 行 eir_parse 之后、device_is_discoverable 之前插入:
cpp
memset(&eir_data, 0, sizeof(eir_data));
eir_parse(&eir_data, data, data_len);
ba2str(bdaddr, addr);
/* ===== 新增:自定义设备过滤检查 ===== */
if (!custom_filter_check(adapter, bdaddr, &eir_data, rssi)) {
DBG(FILTER_TAG "device %s filtered out", addr);
eir_data_free(&eir_data);
return; /* 丢弃设备:不创建/更新设备对象,不上报 D-Bus */
}
discoverable = device_is_discoverable(adapter, &eir_data, addr,
bdaddr_type);
四、配置文件详解
4.1 /etc/bluetooth/main.conf 完整配置
cpp
[General]
# 原有配置...
[DeviceFilter]
# ===== 黑名单(最高优先级,这些设备永远不会被发现)=====
Blacklist=AA:BB:CC:DD:EE:FF,11:22:33:44:55:66
# ===== 白名单(仅这些设备会被发现,为空则不启用)=====
# Whitelist=AA:BB:CC:DD:EE:FF
# ===== MAC 地址前缀过滤(仅发现这些前缀的设备)=====
# MacPrefix=AC:DE:48,
# ===== 设备名称前缀过滤(支持多个,逗号分隔)=====
NamePrefix=Sensor_,Beacon_,iBeacon
# ===== 厂商 ID 过滤(十进制或 0x 十六进制)=====
# 常见: 76=Apple, 89=Nordic, 13=TI, 10=Broadcom, 15=Qualcomm
# VendorId=89,76
# ===== RSSI 最小阈值(dBm,低于此值的设备被过滤)=====
RssiThreshold=-80
# ===== 过滤模式 =====
# strict: 所有启用的条件都必须满足
# loose: 任一条件满足即通过(默认)
FilterMode=loose
4.2 典型场景配置
场景 1:仅发现特定厂商的 BLE 设备
cpp
[DeviceFilter]
VendorId=89
RssiThreshold=-70
FilterMode=strict
场景 2:仅发现特定名称前缀的信标
cpp
[DeviceFilter]
NamePrefix=iBeacon,Estimote,Kontakt
FilterMode=loose
场景 3:黑白名单组合
cpp
[DeviceFilter]
Blacklist=00:11:22:33:44:55,FF:EE:DD:CC:BB:AA
RssiThreshold=-80
FilterMode=loose
场景 4:严格模式(MAC + 名称 + RSSI 全部满足)
cpp
[DeviceFilter]
MacPrefix=AC:DE:48
NamePrefix=MySensor_
RssiThreshold=-75
FilterMode=strict
4.3 过滤模式逻辑图
cpp
Loose 模式 (FilterMode=loose):
┌─────────────────────────────────────────
│ 1. 黑名单检查
│ └─ 在黑名单中? → REJECT
│
│ 2. 逐项检查(白名单/MAC/名称/厂商/RSSI)
│ └─ 任一匹配? → ACCEPT
│
│ 3. 全部不匹配 → REJECT
│ (除非没有任何启用的过滤条件)
└─────────────────────────────────────────
Strict 模式 (FilterMode=strict):
┌─────────────────────────────────────────
│ 1. 黑名单检查
│ └─ 在黑名单中? → REJECT
│
│ 2. 白名单检查
│ └─ 不在白名单中? → REJECT
│
│ 3. 其他条件全部检查
│ └─ 全部匹配? → ACCEPT
│ └─ 任一不匹配 → REJECT
└─────────────────────────────────────────
五、编译部署
5.1 编译
cpp
cd bluez_source
# 配置
./configure --prefix=/usr \
--sysconfdir=/etc \
--localstatedir=/var \
--enable-library
# 编译
make -j$(nproc)
# 验证
./src/bluetoothd --version
5.2 部署
cpp
# 停止系统服务
sudo systemctl stop bluetooth
# 备份
sudo cp /usr/libexec/bluetooth/bluetoothd \
/usr/libexec/bluetooth/bluetoothd.orig
# 替换
sudo cp src/bluetoothd /usr/libexec/bluetooth/bluetoothd
# 更新配置文件
sudo vi /etc/bluetooth/main.conf
# 添加 [DeviceFilter] 段
# 启动
sudo systemctl start bluetooth
5.3 前台调试
cpp
sudo systemctl stop bluetooth
sudo /usr/libexec/bluetooth/bluetoothd -n -d
六、真机调试与验证
6.1 预期日志输出
配置 NamePrefix=Sensor_ + RssiThreshold=-80 + FilterMode=loose 时:
cpp
bluetoothd[1234]: Device filter enabled (mode=loose, rssi_threshold=-80,
blacklist=0, whitelist=0, mac_prefix=0, name_prefix=1, vendor_ids=0)
# 设备1: 名称匹配,通过
bluetoothd[1234]: [DEV-FILTER] ACCEPT AA:BB:CC:DD:EE:FF (name match: Sensor_Temp)
bluetoothd[1234]: [DEV-FILTER] ACCEPT AA:BB:CC:DD:EE:FF (1/2 checks passed, rssi=-65, name=Sensor_Temp)
# 设备2: RSSI 匹配,通过
bluetoothd[1234]: [DEV-FILTER] ACCEPT 11:22:33:44:55:66 (rssi -72 >= -80)
bluetoothd[1234]: [DEV-FILTER] ACCEPT 11:22:33:44:55:66 (1/2 checks passed, rssi=-72, name=Unknown)
# 设备3: 都不匹配,拒绝
bluetoothd[1234]: [DEV-FILTER] REJECT 22:33:44:55:66:77 (0/2 checks passed, rssi=-90, name=OtherDevice)
# 设备4: 黑名单,拒绝
bluetoothd[1234]: [DEV-FILTER] REJECT 00:11:22:33:44:55 (blacklisted)
6.2 验证步骤
cpp
# 1. 确认配置已加载
sudo bluetoothd -n -d 2>&1 | grep "Device filter"
# 应输出: Device filter enabled (mode=loose, ...)
# 2. 查看过滤日志
sudo journalctl -u bluetooth -f | grep DEV-FILTER
# 3. 对比过滤前后设备列表
# 过滤前: bluetoothctl → devices → 20 台
# 过滤后: bluetoothctl → devices → 3 台
# 4. 使用 btmon 确认内核仍在扫描所有设备
# (过滤在 bluetoothd 层,不影响内核扫描)
sudo btmon &
# 应能看到所有设备的广播包
# 5. 验证黑名单
# 添加 Blacklist=AA:BB:CC:DD:EE:FF → 重启 → 该设备消失
6.3 调试模式:打印所有设备信息
在 custom_filter_check 开头添加详细日志:
cpp
/* 调试:打印所有发现设备的关键信息 */
info(FILTER_TAG "SCAN addr=%s rssi=%d name=%s class=0x%06x "
"vendor_id=0x%04x msd_count=%d flags=0x%02x",
addr, rssi,
eir->name ?: "(none)",
eir->class,
eir->did_vendor,
g_slist_length(eir->msd_list),
eir->flags);
七、常见问题排查
7.1 过滤不生效
症状:配置了过滤规则,但所有设备仍然被发现
排查:
cpp
# 1. 确认配置已解析
sudo bluetoothd -n -d 2>&1 | grep "Device filter"
# 无输出 = 配置未加载
# 2. 检查配置段名
grep "DeviceFilter" /etc/bluetooth/main.conf
# 必须是 [DeviceFilter],大小写敏感
# 3. 确认至少配置了一项过滤规则
# filter_enabled 在至少一项配置存在时才设为 TRUE
|----------|-------------------------------------------------------------------------|
| 根因 | 解决 |
| 配置段名错误 | 必须是 [DeviceFilter] |
| 所有过滤项为空 | 至少配置 Blacklist/Whitelist/MacPrefix/NamePrefix/VendorId/RssiThreshold 之一 |
| 配置文件路径不对 | 使用 -f 指定 |
| 编译未包含修改 | make 后确认 src/bluetoothd 已更新 |
7.2 设备误筛
症状:应该通过的设备被过滤掉了
排查:
cpp
# 查看拒绝原因
sudo bluetoothd -n -d 2>&1 | grep "DEV-FILTER.*REJECT"
# 检查 match_count 和 total_checks
# REJECT AA:BB:CC (0/2 checks passed, rssi=-90)
# 表示 2 项检查中 0 项通过
|-----------------------|-----------------------------------|
| 原因 | 解决 |
| 设备名称在 Scan Response 中 | 首次发现可能只有广播数据,名称为空。等待第二次发现 |
| 厂商 ID 在 MSD 中但未解析 | 检查广播数据是否包含 0xFF 类型 |
| RSSI 不稳定 | 适当降低 RssiThreshold(如 -70 → -85) |
| strict 模式太严格 | 改用 FilterMode=loose |
7.3 广播数据解析异常
症状:eir_data 字段值为空或错误
在 custom_filter_check 中添加调试日志:
cpp
info(FILTER_TAG "EIR: name=%s flags=0x%02x class=0x%06x tx_power=%d "
"did_vendor=0x%04x msd=%d services=%d",
eir->name ?: "NULL", eir->flags, eir->class,
eir->tx_power, eir->did_vendor,
g_slist_length(eir->msd_list),
g_slist_length(eir->services));
|----------------|----------------------------------|------------------------------|
| 问题 | 原因 | 解决 |
| name=NULL | 设备未在广播中包含名称 | 正常,部分设备在 Scan Response 中才发名称 |
| did_vendor=0 | 设备未包含 Device ID (EIR 0x10) | 使用 MSD 厂商 ID 过滤(EIR 0xFF) |
| msd_list 为空 | 设备未包含 Manufacturer Specific Data | 使用名称或 MAC 过滤代替 |
| tx_power=127 | 设备未包含 TX Power | 127 是无效值占位符,不影响 RSSI 过滤 |
7.4 与 bluetoothctl scan on 冲突
症状:bluetoothctl 的 `scan on` 发现的设备数量减少
根因:自定义过滤对所有设备发现路径生效。
解决:如果需要仅对自动扫描过滤,在 `custom_filter_check` 开头添加:
cpp
/* 有 D-Bus 客户端在扫描时,跳过过滤(让客户端自行过滤) */
if (adapter->discovery_list)
return true;
八、厂商 ID 速查表
8.1 常见蓝牙芯片厂商 ID
|-----------|---------|--------------------|------------------|
| 厂商 ID | 十进制 | 厂商 | 常见芯片/设备 |
| 0x0002 | 2 | Intel | AX200/AX210 |
| 0x000A | 10 | Broadcom | BCM4343/BCM43455 |
| 0x000D | 13 | Texas Instruments | CC2540/CC2640 |
| 0x000F | 15 | Qualcomm | QCA61x4 |
| 0x004C | 76 | Apple | iBeacon/AirPods |
| 0x0059 | 89 | Nordic Semi | nRF51/nRF52 |
| 0x0075 | 117 | BlueZ | 测试设备 |
| 0x0078 | 120 | STMicroelectronics | BlueNRG |
| 0x0098 | 152 | Realtek | RTL8762 |
| 0x0131 | 305 | Nordic Semi (new) | nRF53 |
| 0x01A8 | 424 | Telink | TLSR8 |
8.2 如何获取设备厂商 ID
cpp
# 方法 1: 在 custom_filter_check 中打印
info(FILTER_TAG "MSD: company=0x%04x data_len=%d",
msd->company, msd->data_len);
# 方法 2: 使用 btmon 抓包
sudo btmon | grep -B1 -A3 "Manufacturer"
# 方法 3: 参考 Bluetooth SIG 官方列表
# https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/
九、扩展功能
9.1 按广播数据内容过滤
匹配 MSD 数据内容(如 iBeacon UUID 前缀):
cpp
/* 检查 MSD 数据是否以指定前缀开头 */
static bool msd_data_match(struct eir_data *eir,
uint16_t company,
const uint8_t *prefix, uint8_t prefix_len)
{
GSList *l;
for (l = eir->msd_list; l; l = l->next) {
struct eir_msd *msd = l->data;
if (msd->company == company &&
msd->data_len >= prefix_len &&
memcmp(msd->data, prefix, prefix_len) == 0)
return true;
}
return false;
}
/* 示例:匹配 Apple iBeacon (company=0x004C, data 以 0x0215 开头) */
static bool is_ibeacon(struct eir_data *eir)
{
uint8_t ibeacon_prefix[] = {0x02, 0x15};
return msd_data_match(eir, 0x004C, ibeacon_prefix, 2);
}
9.2 按服务 UUID 过滤
cpp
/* 检查设备是否提供特定 UUID */
static bool uuid_match(struct eir_data *eir, GSList *uuid_list)
{
GSList *l, *m;
for (l = uuid_list; l; l = l->next) {
const char *wanted_uuid = l->data;
for (m = eir->services; m; m = m->next) {
if (g_str_equal(m->data, wanted_uuid))
return true;
}
}
return false;
}
9.3 运行时动态过滤
通过 D-Bus 接口动态修改过滤规则(无需重启 bluetoothd):
cpp
/* D-Bus 方法: AddDeviceBlacklist */
static DBusMessage *add_blacklist(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
const char *addr_str;
bdaddr_t *ba;
dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &addr_str,
DBUS_TYPE_INVALID);
ba = g_new0(bdaddr_t, 1);
str2ba(addr_str, ba);
btd_opts.filter_blacklist = g_slist_append(
btd_opts.filter_blacklist, ba);
btd_opts.filter_enabled = TRUE;
info(FILTER_TAG "runtime add blacklist: %s", addr_str);
return dbus_message_new_method_return(msg);
}
9.4 过滤统计
cpp
/* 统计结构(添加到 btd_adapter) */
struct filter_stats {
int total; /* 总发现设备数 */
int accepted; /* 通过设备数 */
int rejected; /* 拒绝设备数 */
int rej_blacklist; /* 黑名单拒绝 */
int rej_rssi; /* RSSI 不足 */
int rej_name; /* 名称不匹配 */
int rej_vendor; /* 厂商不匹配 */
int rej_mac; /* MAC 不匹配 */
};
/* 在 custom_filter_check 中更新统计 */
/* 通过 D-Bus 方法 GetFilterStats 返回 JSON */
十、EIR/AD 数据类型速查
|-----------|-----------------------|-------------|--------------------|--------------|
| 类型码 | 常量 | 说明 | eir_data 字段 | 可用于过滤 |
| 0x01 | EIR_FLAGS | 广播标志 | flags | 是 (flags) |
| 0x02-0x07 | EIR_UUID* | 服务 UUID | services | 是 (UUID) |
| 0x08 | EIR_NAME_SHORT | 短名称 | name | 是 (name) |
| 0x09 | EIR_NAME_COMPLETE | 完整名称 | name | 是 (name) |
| 0x0A | EIR_TX_POWER | 发射功率 | tx_power | 是 (pathloss) |
| 0x0D | EIR_CLASS_OF_DEV | 设备类 | class | 是 (class) |
| 0x10 | EIR_DEVICE_ID | 设备 ID | did_vendor/product | 是 (vendor) |
| 0x16 | EIR_SVC_DATA16 | 服务数据(16bit) | sd_list | 扩展 |
| 0x19 | EIR_GAP_APPEARANCE | 外观 | appearance | 扩展 |
| 0xFF | EIR_MANUFACTURER_DATA | ★ 厂商数据 | msd_list | 是 (vendor) |
十一、总结
11.1 改造要点
-
过滤位置:在 btd_adapter_update_found_device() 中、eir_parse() 之后、device_is_discoverable() 之前插入 custom_filter_check(),确保广播数据已解析,且在设备创建/D-Bus 上报之前拦截
-
六维过滤:黑名单(BDADDR)→ 白名单(BDADDR)→ MAC 前缀 → 名称前缀 → 厂商 ID(Device ID + MSD 双来源)→ RSSI 阈值,覆盖主流过滤需求
-
双模式设计:loose 模式(任一匹配即通过)适合宽松筛选,strict 模式(全部匹配才通过)适合精准筛选
-
厂商 ID 双来源:同时检查 eir_data.did_vendor(EIR 0x10 Device ID)和 eir_data.msd_list 中的 eir_msd.company(EIR 0xFF Manufacturer Data),覆盖两种厂商标识方式
-
配置驱动:通过 /etc/bluetooth/main.conf 的 DeviceFilter 段配置,无需重新编译即可调整规则
11.2 关键设计决策
|-----------------------|----------------------------------------|
| 决策 | 理由 |
| 过滤在 bluetoothd 层而非内核层 | 内核层(MGMT)修改风险高,bluetoothd 层修改安全且可热更新 |
| 使用 info() 输出过滤日志 | 始终输出,不需要 -d 参数即可查看过滤结果 |
| 黑名单优先级最高 | 安全考虑:黑名单设备必须被阻止,无论其他条件如何 |
| MSD 厂商 ID 检查 | Device ID (0x10) 很多设备不发,MSD (0xFF) 更通用 |
| 配置默认关闭 | 保持向后兼容,不影响原生行为 |
11.3 扩展方向
-
iBeacon/Eddystone 解析:解析 MSD 数据内容,按 UUID/Major/Minor 过滤
-
动态 D-Bus 接口:通过 D-Bus 方法运行时增删过滤规则
-
过滤统计:统计通过/拒绝设备数,通过 D-Bus 暴露
-
按 Appearance 过滤:按 GAP Appearance 筛选设备类型(如只找心率传感器)
-
时间窗口过滤:仅在特定时间段内发现设备
-
持久化记录:将通过过滤的设备写入文件/数据库
附录:核心源码文件索引
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| 文件 | 关键内容 |
| src/adapter.c | btd_adapter_update_found_device()(6878) 过滤插入点、is_filter_match()(6767) 原生过滤、device_found_callback()(7097) MGMT 事件回调、discovery_filter 结构体(200) |
| src/eir.h | struct eir_data(72) 广播数据结构、struct eir_msd(54) 厂商数据结构、EIR 类型常量(16-40) |
| src/eir.c | eir_parse_msd()(150) 厂商数据解析、eir_parse()(357) 主解析函数 |
| src/btd.h | struct btd_opts 全局配置结构 |
| src/main.c | init_defaults()(945) 默认值、parse_config()(641) 配置解析 |
参考文献
-
BlueZ Source Code:
src/adapter.c,src/eir.h,src/eir.c,src/main.c -
Bluetooth Core Specification Vol 3, Part C, Section 8: Extended Inquiry Response
-
Bluetooth Core Specification Vol 4, Part E, Section 7.7.65: LE Advertising Reports
-
Bluetooth SIG Assigned Numbers: Company Identifiers