
1. 从一次设备连不上引发的深挖BLE设备地址到底是个什么玩意做蓝牙开发这些年几乎每个项目都会在设备地址上踩点坑。早期我做第一个BLE外设时遇到一个诡异现象手机App偶尔连不上设备复位一下就好了但过一会儿又复现。后来抓包一看问题出在设备地址类型混乱上——广播包里的地址和设备实际用于连接的地址对不上手机端缓存了旧的地址信息连接请求发过去直接被拒。从那以后我才认真把BLE设备地址这块彻底啃了一遍。说实话BLE地址的规则在蓝牙核心规范里不算复杂但它在整个连接流程里扮演的角色非常关键而且和iOS、Android两大平台的行为差异结合以后坑多到你想象不到。这篇文章我打算把BLE设备地址的前世今生、类型划分、在连接过程中的具体作用、以及用nRF52840抓包分析地址的方法完整写一遍。不管你是在做外设固件、手机App还是网关开发把地址机制吃透能帮你省掉大量排查连接问题的冤枉时间。2. BLE设备地址的核心概念与分类2.1 地址的长度与基本格式BLE设备地址在空口上占48位也就是6个字节这个长度和以太网MAC地址一致。蓝牙核心规范里这48位不是一个单纯的“硬件编号”而是由两部分拼接而成最高位bit 47地址类型标识低47位实际地址内容由于最高位被用来标记地址类型所以BLE地址不像WiFi MAC那样有明确的厂商段OUI约束。你在广播包里看到的一个地址到底是不是出厂烧录的唯一编号光看字节是判断不出来的必须结合类型位去理解。2.2 四大地址类型逐一拆解蓝牙4.0到5.x时代规范保留了4种地址类型排查问题时你要先能分清对方用的是哪一种。Public Device Address公共地址这种地址由IEEE注册的OUI组织唯一标识符加厂商自定义部分构成相当于设备出厂时的“身份证号”全球唯一。使用公共地址的设备不需要额外的密钥交换接收方可以把这个地址当作长期身份来识别。但问题在于公共地址的生产管理成本高小批量产品很难申请到OUI所以很多厂商干脆不往芯片里烧公共地址而是直接使用随机地址。Static Device Address静态随机地址静态地址是随机生成的48位数值只要设备不重启关键是不能复位或掉电这个地址就不会变。比如基于北向芯片做开发时固件固定在Flash里存一个随机数每次上电后读取它作为自己的地址。由于静态地址可以随意生成所以不同厂商的设备撞地址的概率在理论上是存在的虽然48位空间里撞车概率不高但一旦撞上周围两台设备就会“长相”一模一样连接时会非常迷。另外静态地址的随机部分只要保证最高两位是“11”即可。Private Resolvable Address私有可解析地址这个地址是为了保护隐私而生的常见于防丢器、手环这类需要被扫描但不希望被长期追踪的设备。它由两部分组成高位24位prand随机数部分最高两位固定为“10”低位24位hash由IRK身份解析密钥和prand计算得到对端设备如果持有相同的IRK就可以“解析”这个地址反推出设备的真实身份Identity Address。简单说这叫“对暗号”——地址每次广播都可能变化但只有持票人持有IRK的设备能认出它来。Private Non-resolvable Address私有不可解析地址这种地址最高两位是“00”完全随机且不提供任何身份信息。设备用这种地址广播时接收方无法识别它到底是谁。它主要用于一些不希望透露任何身份、也不需要被连接的应用场景比如周期性的信标广播。2.3 为什么需要这么多种地址类型用一个生活化的类比公共地址就像你的身份证号走到哪都亮出来办事方便但也很容易被有心人记下行踪静态随机地址像你的常用网名熟悉你的人能认出来但和身份证号没有直接关联私有可解析地址像一次性暗号每次见面都换但老熟人拿着约定好的暗号本还是能认出你私有不可解析地址则像是蒙面人纯粹不想让你知道他是谁。BLE设计这么多地址类型本质是在“可连接性”和“隐私性”之间做取舍。早期蓝牙设备用公共地址扫到就能连但对用户来说只要你的手机一直在扫描别人也能一直追踪你的设备位置。后来苹果主导推动隐私保护才有了可解析私有地址机制。3. 地址在BLE连接全过程中的作用3.1 从广播到连接地址的完整生命周期理解BLE地址不能只在纸面上看要把它放进连接流程里去体会。整个连接过程可以粗略分成几个阶段地址在每个阶段都有不同的用途。广播阶段外设Peripheral周期性发送广播包包里携带发送者的地址。扫描端Central收到广播包后会把“地址设备名服务UUID”等信息汇总展示给用户。此时地址的作用是“提名”让对方知道你的存在。扫描请求与响应阶段扫描端有时会主动发一个扫描请求Scan Request外设收到后回复扫描响应Scan Response。扫描请求里同样包含外设地址用于指定“我在和谁说话”。此时地址的作用是“寻址”帮双方在一堆无线信号中对上号。连接建立阶段用户点击连接后扫描端发送连接请求Connect Request里面包含两个关键字段InitA发起端地址和 AdvA广播端地址。连接请求发出去后双方在链路层就以这两个地址作为身份标识后续所有数据包里的访问地址Access Address也基于此会话协商出来。连接维持阶段连接建立后双方通过连接事件交换数据。虽然数据包通常不再显式携带完整设备地址用的是Access Address但地址决定的“身份”已经固化在了连接里。3.2 地址在连接请求中的具体格式我翻了很多协议栈的实现以最常见的Controller层为例Connect Request PDU里的字段是这样的InitA发起连接一方的设备地址6字节AdvA被连接一方的设备地址6字节LLData链路层配置数据包括访问地址、跳频增量、连接参数等调试时最容易犯的错就是手机端发起连接前会先核对广播包里的地址。如果你广播时用的是静态随机地址但内部连接参数却按公共地址去配置就有可能导致广播地址和实际连接地址不一致。我在初学阶段就遇到过这类问题后来统一在协议栈初始化时固定地址类型才彻底解决。3.3 白名单机制中的地址使用BLE还提供了一种白名单White List机制设备可以维护一张地址列表只允许列表里的设备连接自己或者只扫描列表里的设备。这在低功耗场景下非常实用。比如做蓝牙门锁时门锁作为外设只允许房主的手机连接。最简单的做法就是门锁固件里存房主手机的公共地址连接请求进来时链路层先查这个地址是否在白名单里白名单外直接丢弃不进入上层协议栈。这样既省电又安全。但白名单也有一个大坑如果手机用的是私有可解析地址iOS设备默认如此那它的地址是不断变化的你以为存了一个地址下次它换个地址来了白名单直接失效。所以用白名单时必须配合IRK解析机制让设备在链路层先解析地址、再比对白名单。4. 实操用nRF52840抓包分析BLE设备地址4.1 为什么选nRF52840做抓包工具做BLE开发光靠软件日志是远远不够的空口上发生了什么必须用抓包工具看。市面上有专业的Ellisys、Frontline价格不菲个人开发者很难配齐。nRF52840 DK板加Wireshark是性价比极高的方案实测稳定性和抓包完整性都很不错。nRF52840支持BLE 5.02M PHY、长距离编码PHYCoded PHY都能抓配合Nordic官方的nRF Sniffer for BLE固件烧录后用Wireshark的Nordic BLE Sniffer插件就能实时解析空口报文。4.2 抓包环境的搭建步骤这套环境我搭建过不止一次步骤说多不多但有几个细节必须注意。第一步烧录Sniffer固件。从Nordic官网下载nRF Sniffer for BLE把hex文件用nRF Connect Programmer烧到nRF52840 DK上。注意DK板上的Switch要拨到正确位置否则烧录后串口不工作。第二步安装Wireshark插件。把nRF Sniffer插件目录下的Python脚本和配置文件放到Wireshark的Personal Extcap目录里不同系统路径不同Windows一般在%APPDATA%\Wireshark\extcap。第三步启动抓包。打开Wireshark在接口列表里会新增一个nRF Sniffer接口选择它并开始捕获。此时能看到周围所有BLE设备的广播包、扫描请求、连接请求和连接事件。4.3 从抓包结果中识别不同类型的地址抓包之后重点来了怎么看地址类型。这里给出一个实操技巧在Wireshark的BLE协议树里展开Bluetooth Low Energy Link Layer层看“Advertiser Address”字段和“AdvA Address Type”字段。我随便挑一个真实抓包例子来说明Bluetooth Low Energy Link Layer Advertiser Address Type: Random Device Address Advertiser Address: 5a:2e:4f:11:22:33看到地址类型的值是Random Device Address再结合地址最高字节0x5A二进制01011010最高两位是“01”就可以判断这是私有不可解析地址。如果最高两位是“11”则是静态随机地址如果是“10”则是私有可解析地址。有一次我抓一个手环的包发现它的广播地址每次都在变但手机仍然能连上。扒开地址一看类型是Private Resolvable Address。这就说明手环和手机之间已经完成了配对绑定手机持有手环的IRK所以能实时解析它变化的地址。4.4 地址解析的验证方法如果你想知道某个私有可解析地址到底是谁可以找对端设备里的IRK做验证。具体做法在Wireshark里右键一条解析成功的连接请求选择解析地址填入设备的IRK值Wireshark会自动计算hash并和地址里的hash比对。这个验证方法我在排查“配对后偶尔连不上”问题时帮了大忙。有一次设备端更新了IRK但手机App没有重新配对导致手机无法解析设备的私有地址界面一直显示“设备已停止广播”。抓包后发现设备的广播地址一直在变但手机因为IRK不匹配根本认不出来自然也就不会发起连接。5. 不同平台对地址的处理差异5.1 iOS的地址策略与“根据deviceID连接”问题这里要多说一点因为和实际的App开发直接相关。iOS从很早的版本开始系统级的CoreBluetooth框架对上层隐藏了底层的MAC地址App开发者拿到的deviceIDUUID是系统对蓝牙设备做的一次映射并不是空口上的真实设备地址。所以uni-app在iOS上开发时用deviceID建立一个连接是完全可行的因为系统把“设备标识符”和“底层地址解析”都替你处理好了。但这个deviceID不是固定的它可能随系统重置、设备解绑发生变化。网上有人问“uni-app ble iOS可以根据蓝牙的deviceid建立连接吗”我实测下来是可以的但要注意deviceID对同一个外设来说在系统不重置的情况下相对稳定系统备份恢复、蓝牙设置重置后deviceID映射可能变不能用这个deviceID去做白名单或者设备唯一标识管理5.2 Android的地址获取与兼容性问题Android这边情况复杂得多。普通App在扫描结果里拿到的device地址就是空口上的真实地址至少在没有特殊隐私策略的机型上是如此。但部分国产ROM会加一层“虚拟MAC”逻辑底层扫描到的地址会被系统替换成一个伪造地址导致App无法用这个地址去区分设备。这事的本质是你在应用层看到的地址不一定等于控制器看到的地址。所以做Android App时我一般建议区分两个场景只负责展示和连接直接用系统返回的地址不用关心真实性需要设备唯一识别不要依赖地址尽量读取设备的Service数据或厂商自定义广播数据拿到内部序列号等业务标识5.3 平台差异对固件开发的启示很多固件工程师只关注嵌入式端忽略了手机端的行为。其实不然如果你做的外设使用静态随机地址Android手机每次扫描都能看到它连接也正常但同一台外设iOS设备在首次配对后会缓存地址信息如果外设因为断电重启换了一个随机地址比如固件在Flash里存储随机数失败iOS就找不到它了。这种问题排查起来极其痛苦因为看起来“设备明明在广播手机就是搜不到”。抓包又看不到iOS端的行为。我后来学到的经验是外设使用静态随机地址时一定要确保地址在Flash里持久化保存每上电一次换一个新地址的固件属于设计缺陷。6. 常见地址问题与排查技巧实录6.1 设备搜不到或偶发连接失败这是BLE开发里最高频的问题。我列一个排查速查表每一条都是亲手踩过的坑。现象可能原因排查方向手机搜不到外设外设地址类型和广播参数不匹配抓包确认广播地址和类型偶尔连接失败白名单配置了旧地址检查白名单是否带IRK解析配对手环重连失败iOS缓存了旧设备标识建议用户忽略设备重新配对两台设备地址一样静态地址随机数未持久化检查Flash存储逻辑Android扫不到iOS设备iOS使用私有可解析地址确认App端是否持有IRK6.2 地址冲突的实测案例之前帮一个客户排查其智能灯产品批量出货后用户偶尔连不上灯的问题。抓了两台灯的空口包发现两台灯的广播地址一模一样。进一步查固件发现它们的地址是根据芯片内部某个未校准的寄存器值生成的因为同一批次芯片寄存器值相同导致所有设备生成了同一个静态随机地址。这就属于典型的静态地址生成策略缺陷。后续我建议他们在出厂测试时生成随机数并写入OTP区域上电时读取OTP中的地址。这个问题在量产场景出现概率非常高但开发阶段因为设备数量少很难暴露。6.3 私有可解析地址的更新周期问题私有可解析地址虽然保护了隐私但它有一个更新周期的问题。规范建议设备在一段时间后更换地址但并没有强制规定多久换一次。我见过一些设备每10秒就换一次地址导致手机端App列表里设备名在变但地址却不断变化用户体验很差。实际项目中我的建议是广播用私有可解析地址时更新周期不要小于连接事件的超时时间已经处于连接状态的设备连接期间不要更换地址如果设备处于可发现模式最好在地址更新前广播一个“即将断开”的提示6.4 地址与配对信息绑定时的注意事项最后再聊一个很多团队容易忽略的事地址和配对信息是绑定的如果地址类型换了之前的配对信息可能全部失效。比如外设原本用公共地址某次固件升级改成了静态随机地址用户手机里存的老配对信息就对不上号了必须重新配对。所以固件升级时除非有明确的安全需求否则不要随意更换地址类型。如果必须换一定要在App端做好兼容逻辑比如自动检测到配对失效后引导用户重新配对。我在实际项目里还见过因为地址类型变更导致旧版本App崩溃的因为老App根本没有处理“设备地址变了但服务没变”的状态。7. 写在最后的几点个人体会做BT开发这些年地址这个东西看起来简单其实就是链路层身份的根。很多项目前期图省事地址随便用一个不持久化的随机数后面排查问题时要花几倍时间还债。我个人的习惯是项目启动第一周就把地址策略定下来是公共地址、静态随机地址还是私有可解析地址配合什么安全等级都要写进设计文档。同时把SNIFFER抓包环境提前搭好因为地址问题往往只有空口抓包才能看清真相。如果这篇文章里有一个观点值得你记住那就是当你看到一个BLE设备连接异常时第一件事不是改代码而是先把它的地址和地址类型搞清楚。地址对了连接大概率就对了一半。