实战 4:BlueZ 源码中添加自定义设备过滤规则

在嵌入式蓝牙开发中,设备扫描通常面临一个实际问题:周围可能有几十上百个蓝牙设备在广播,但我们只关心特定的几个------某个厂商的传感器、某个 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 改造要点

  1. 过滤位置:在 btd_adapter_update_found_device() 中、eir_parse() 之后、device_is_discoverable() 之前插入 custom_filter_check(),确保广播数据已解析,且在设备创建/D-Bus 上报之前拦截

  2. 六维过滤:黑名单(BDADDR)→ 白名单(BDADDR)→ MAC 前缀 → 名称前缀 → 厂商 ID(Device ID + MSD 双来源)→ RSSI 阈值,覆盖主流过滤需求

  3. 双模式设计:loose 模式(任一匹配即通过)适合宽松筛选,strict 模式(全部匹配才通过)适合精准筛选

  4. 厂商 ID 双来源:同时检查 eir_data.did_vendor(EIR 0x10 Device ID)和 eir_data.msd_list 中的 eir_msd.company(EIR 0xFF Manufacturer Data),覆盖两种厂商标识方式

  5. 配置驱动:通过 /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


相关推荐
墨痕诉清风2 小时前
SSRF漏洞实战记录+OSS桶命令执行+MCP漏洞
linux·服务器·数据库
瓦瓦gogo3 小时前
从一个 TypeScript 函数开始理解 MCP 协议
linux·ubuntu·typescript
鱼听禅4 小时前
Ubuntu安装YOLOv8进行菌落识别训练
linux·python·yolo·ubuntu
测试界的小码农4 小时前
【Linux 命令实战(04):文件内容查看,掌握 cat、less、head、tail】
linux·服务器·less
CodeYp20195 小时前
第13讲:HTTPS 协议原理
linux·https
一条破秋裤6 小时前
【1112】Linux中断【整理】
linux·运维·服务器
CodeYp20197 小时前
第14讲:传输层协议 UDP
linux·udp
北京迅为7 小时前
案例分享 | RK182X算力卡:多路视频VLM监控,四路画面AI同时看懂,一句人话说清现场
linux·人工智能·嵌入式·rk3588·算力卡·rk182x
lzx_00219 小时前
Linux 进程(三) 从进程状态到僵尸进程、孤儿进程
linux·服务器