ABEJA Tech Blog

中の人の興味のある情報を発信していきます

Bluefruit LE Sniffer x WiresharkでBLE通信を見てみる

1.はじめに

ABEJA大田黒です。これはABEJAアドベントカレンダー2024の14日目の記事です。最近、趣味でESP32を触っています。ESP32では、Bluetooth Low Energy (BLE)を簡単に扱うことができます。手軽に使えてしまう反面、具体的にどういう通信が行われているか気になったので、Snifferデバイスを購入して実際に調べてみる事にしました。

  • 筆者環境
    • OS: Windows 10
    • Wireshark: 4.4.1
    • nRF Sniffer for bluetooth le: 4.1.1

2.セットアップ手順

2.1 Snifferの購入

今回、Adafruit ADA-2269を利用しました。身近なところだと、Amazonや千石電商さん、スイッチサイエンスさんで入手が可能です。2024年11月現在で5000円前後という金額感になっています。

重要:必ず技術基準適合証明や工事設計認証を受けているデバイスを使用しましょう。ADA-2269に搭載されているBLEモジュール(RAYRAC社 MDBT40)は工事設計認証を取得しています。

2.2 Wiresharkのインストール

下記からダウンロードが可能です。インストール手順の解説はここでは省略します。

www.wireshark.org

2.3 nRF Snifferのインストール

Fig ダウンロードページ

下記ページ(nRF Sniffer for Bluetooth LE)から、Wiresharkのバージョンに合わせたソフトウェアをダウンロードします。筆者のWiresharkは4系なので「nrf_sniffer_for_bluetooth_le_4.1.1.zip」を使いました。 

www.nordicsemi.com

extcapフォルダのコピー

nrf_sniffer_for_bluetooth_le_4.1.1.zipを展開するとextcapフォルダが出現します。このextcapフォルダの中身をWiresharkのextcapフォルダ配下にコピーします。

Profileのコピー

nrf_sniffer_for_bluetooth_le_4.1.1.zipを展開するとProfile_nRF_Sniffer_Bluetooth_LEフォルダが出現します。このフォルダをWiresharkのprofilesフォルダ配下にコピーします。

pyserialのインストール

依存関係であるpyserialをインストールします。この作業を忘れると正しく動作しません。

$ pip3 install pyserial

動作確認

エラーメッセージではなく、下記のようなメッセージが出てきたら成功です。

$ python nrf_sniffer_ble.py  --extcap-interfaces

2.4 Wiresharkの起動

うまく設定ができていると、下記のようにSnifferデバイスを認識する事ができます。

Snifferデバイスを選択すると、実際に飛び交っているアドバタイズパケットのキャプチャが始まります。

2.5 Snifferコードの一部修正

キャプチャを停止すると、invalid escape sequence \sというエラーメッセージが表示されます。このエラーを調べたところ、下記フォーラムに対応方法が記載されていました。コードを2箇所修正すると良さそうです。(2024年11月20日現在)

invalid escape sequence \s - Nordic Q&A - Nordic DevZone - Nordic DevZone

Line 187 Installed:         "{validation=^\s*((37|38|39)\s*,\s*){0,2}(37|38|39){1}\s*$}{required=true}" % CTRL_ARG_ADVHOP)

Line 187 My Edit:          "{validation=^\\s*((37|38|39)\\s*,\\s*){0,2}(37|38|39){1}\\s*$}{required=true}" % CTRL_ARG_ADVHOP)

Line 716 Installed:         m = re.search("^\s*rssi\s*(>=?)\s*(-?[0-9]+)\s*$", capture_filter, re.IGNORECASE)

Line 716 My Edit:         m = re.search("^\\s*rssi\\s*(>=?)\\s*(-?[0-9]+)\\s*$", capture_filter, re.IGNORECASE)

3.実験

今回はペリフェラルとしてESP32、セントラルとしてAndroidスマホを用意しました。Androidスマホには事前にnRF Connect for Mobileをインストールしておきます。

play.google.com

3.1 ESP32を使った準備

下記コードをArduino IDEもしくはPlatformIOを用いて、ESP32搭載ボードに書き込みます。

このコードは、iBeaconとして振る舞います。また、接続可能になっておりGATT通信によるService・Characteristicを提供しています。

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BLEBeacon.h>

#define DEVICE_NAME         "ESP32"
#define BEACON_UUID_REV     "A134D0B2-1DA2-1BA7-C94C-E8E00C9F7A2D"
#define SERVICE_UUID        "7A0247E7-8E88-409B-A959-AB5092DDB03E"
#define CHARACTERISTIC_UUID "82258BAA-DF72-47E8-99BC-B73D7ECD08A5"

BLEServer *pServer;
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
uint8_t value = 0;
uint16_t loopcnt = 0;

class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;
    Serial.println("deviceConnected = true");
  };

  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
    Serial.println("deviceConnected = false");

    // Restart advertising to be visible and connectable again
    BLEAdvertising *pAdvertising;
    pAdvertising = pServer->getAdvertising();
    pAdvertising->start();
    Serial.println("iBeacon advertising restarted");
  }
};

class MyCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string rxValue = pCharacteristic->getValue();

    if (rxValue.length() > 0) {
      Serial.println("*********");
      Serial.print("Received Value: ");
      for (int i = 0; i < rxValue.length(); i++) {
        Serial.print(rxValue[i]);
      }
      Serial.println();
      Serial.println("*********");
    }
  }
};

void init_service() {
  BLEAdvertising *pAdvertising;
  pAdvertising = pServer->getAdvertising();
  pAdvertising->stop();
  BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID));
  pCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
  );
  pCharacteristic->setCallbacks(new MyCallbacks());
  pCharacteristic->addDescriptor(new BLE2902());
  pAdvertising->addServiceUUID(BLEUUID(SERVICE_UUID));
  pService->start();
  pAdvertising->start();
}

void init_beacon(uint16_t seq_num) {
  BLEAdvertising *pAdvertising;
  pAdvertising = pServer->getAdvertising();
  pAdvertising->stop();
  BLEBeacon myBeacon;
  myBeacon.setManufacturerId(0x4c00);
  myBeacon.setMajor(5);
  myBeacon.setMinor(88);
  myBeacon.setSignalPower(0xc5);
  myBeacon.setProximityUUID(BLEUUID(BEACON_UUID_REV));
  BLEAdvertisementData advertisementData;
  advertisementData.setFlags(0x1A);
  advertisementData.setManufacturerData(myBeacon.getData());
  pAdvertising->setAdvertisementData(advertisementData);
  pAdvertising->start();
}

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing...");
  BLEDevice::init(DEVICE_NAME);
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  init_service();
  init_beacon(loopcnt);
}

void loop() {
  if (deviceConnected) {
    Serial.printf("*** NOTIFY: %d ***\n", value);
    pCharacteristic->setValue(&value, 1);
    pCharacteristic->notify();
    value++;
  }
  delay(1000);
}

参考:

www.programmingelectronics.com

3.2 ESP32アドバタイズの様子

Passive Scan過程で飛んでいるADV_INDのパケットが見えますね。ADV_INDはAdvertising PDUの一種で「コネクション可能、スキャン可能」を表します。 iBeacon互換の信号を出しているので、Manufacture SpecificのCompayIDがApple, Inc.となっています。

Active Scan過程で飛んでいるSCAN_REQも見えました。何かしらのデバイスが情報を取りに行っているみたいです。

SCAN_REQに対するSCAN_RSPも見えますね。デバイス名や送信電力やサービスUUIDを返却している様子が見えます。

3.3 セントラルからの接続

CONNECT_REQが見えました。(CONNECT_INDと表示されていますが、 PDU Type (0x05)から推測するにおそらく CONNECT_REQ信号なはず)

リンク層の制御をしている様子も見えますね。

接続後はセントラル(スマホ)とペリフェラル(ESP32)の間でKeepAliveのような通信が行われている事が確認できます。かなりのハイペースでEmpty PDUが飛び交っています。

3.4 GATTによるデータ通信

セントラル→ペリフェラルへのデータ取得要求 (Read)

Read ReqestとRead Responseが確認できました。

ペリフェラル→セントラルへのNotify (通知)

値に変化がある度に、下記のようなパケットが飛んでくる事がわかりました。

セントラル→ペリフェラルへの書き込み (Write)

スマホアプリから0x30を送信した時の様子です。Write Requestが見れました。

4. 最後に

今回、実際のBLE通信をSnifferデバイスを用いて覗いてみました。ブラックボックスとして使っている通信技術の解像度が少し上がった気がします。

We Are Hiring!

ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)

careers.abejainc.com

特に下記ポジションの募集を強化しています!ぜひ御覧ください!

プラットフォームグループ:シニアソフトウェアエンジニア | 株式会社ABEJA

トランスフォーメーション領域:ソフトウェアエンジニア(リードクラス) | 株式会社ABEJA

トランスフォーメーション領域:データサイエンティスト(シニアクラス) | 株式会社ABEJA