NimBLE-Arduino
NimBLE-Arduino copied to clipboard
Change MAC address ESP32
First and Foremost, Thanks for the amazing work. I noticed that BLEAdvertisement doesn't have the setDeviceAddress function, is there a way to add this functionality? The original bluedroid library has it. Does nimBLE support address change and if not, is there a way to get around this issue?
I need to be able to set a custom MAC address and also change it without restarting the ESP32. If i didnt need to change it without restarting, i would have used esp_base_mac_addr_set function from esp-idf api. Hopefully there is a way to add setDeviceAddress to nimBLE.
There is no C++ API for this yet (soon). but you can use the lower level calls:
NimBLEAddress newAddress("AA:BB:CC:DD:EE:FF");
int rc = ble_hs_id_set_rnd(newAddress.getNative());
assert(rc == 0);
That should work, but untested.
There is no C++ API for this yet (soon). but you can use the lower level calls:
NimBLEAddress newAddress("AA:BB:CC:DD:EE:FF"); int rc = ble_hs_id_set_rnd(newAddress.getNative()); assert(rc == 0);That should work, but untested.
It didn't unfortunately, tested on ESP32, after setting a correct random address ex: 0xd2, 0x7b, 0x70, 0x1c, 0x0f, 0xd2, the assert doesn't fail, however the esp also doesn't change its address.
Were any BLE operation's active when you set this?
I called stopadvertising before it, and then started advertising after it.
Strange... I'll see what I can do, thanks!
Simple example where the issue pops up
/** NimBLE_Service_Data_Advertiser Demo:
*
* Simple demo of advertising service data that changes every 5 seconds
*
* Created: on February 7 2021
* Author: H2zero
*
*/
#include <NimBLEDevice.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
uint8_t macaddress[] = {0xd2, 0x7b, 0x70, 0x1c, 0x0f, 0xd2};
static NimBLEUUID dataUuid(SERVICE_UUID);
static NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
static uint32_t count = 0;
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
NimBLEDevice::init("svc data");
}
void loop() {
pAdvertising->stop();
macaddress[3] = count;
int rc = ble_hs_id_set_rnd(macaddress);
assert(rc == 0);
pAdvertising->setServiceData(dataUuid, std::string((char*)&count, sizeof(count)));
pAdvertising->start();
Serial.printf("Advertising count = %d\n", count);
count++;
delay(1000);
}
This is taken for the service advertisement example, it should change the 3rd byte of the mac address when changing the service, the mac address never changed however, and stays the original hardware address in the esp32.
Thanks for the awesome work :smile:
Out of curiosity, what is the purpose of doing this?
The OpenHaystack project allows you to build DIY Apple Airtags, the protocol they use mandates changing the BLE address to send data (hacky solution from apple to get around the BLE advertisement max size). The address would also have to change every 15 mins as per apple spec. The current example code uses bluedroid and esp-idf directly, i was interested in trying to port it to use the Arduino IDE and nimBLE.
https://github.com/seemoo-lab/openhaystack
I see now, thanks! So for this we may need to do some tinkering as you will need a real random address with the type set correctly. This will be a little more involved but certainly doable. I'll play with this a bit and get back to you.
Hi, thanks for helping :smile:. If i may ask, what is a real random address? And what difference does it have to the current address type? With the AirTag protocol, you have to set the mac address to the first 6 bytes (minus 2 bits per the bluetooth specification) of a Public encryption key, thus if i can't control the mac address and change it at will, the Public key will be corrupt.
I have to add, i am using a single core ESP32, compiled the ESP-IDF libraries myself for it to work, maybe this is the reason? Everything else seems to work just fine.
A real random address is basically one that is generated from a specified algorithm and has an address type of 1 in most cases (there are others). The first 6 bytes of the public key being used as the mac is interesting (btw, the minus 2 bits is to set the address as random 11), nice hack Apple!
So the way NimBLE handles addresses is confusing to start with because it is little endian, for your code the first and last byte is the same anyway, but should be kept in mind. Looking at it again, It's also a proper random address anyway (first 2 bits are 11), and since the assert wasn't triggered I'm a bit puzzled that it didn't change. Perhaps a delay is required to take effect?
I confirmed in this code snippet form openhaystack firmware
void set_addr_from_key(esp_bd_addr_t addr, uint8_t *public_key) {
addr[0] = public_key[0] | 0b11000000;
addr[1] = public_key[1];
addr[2] = public_key[2];
addr[3] = public_key[3];
addr[4] = public_key[4];
addr[5] = public_key[5];
}
That the address is indeed set as random. The address is then set using
set_addr_from_key(rnd_addr, public_key);
esp_ble_gap_set_rand_addr(rnd_addr);
The esp_ble_gap_set_rand_addr function seems to be only in bluedroid.
I tried the following code
/** NimBLE_Service_Data_Advertiser Demo:
*
* Simple demo of advertising service data that changes every 5 seconds
*
* Created: on February 7 2021
* Author: H2zero
*
*/
#include <NimBLEDevice.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
uint8_t macaddress[] = {0xd2, 0x7b, 0x70, 0x1c, 0x0f, 0xd2};
static NimBLEUUID dataUuid(SERVICE_UUID);
static NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
static uint32_t count = 0;
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
esp_base_mac_addr_set(macaddress);
NimBLEDevice::init("svc data");
}
void loop() {
pAdvertising->stop();
macaddress[3] = count;
int rc = ble_hs_id_set_rnd(macaddress);
assert(rc == 0);
delay(1000);
pAdvertising->setServiceData(dataUuid, std::string((char*)&count, sizeof(count)));
pAdvertising->start();
Serial.printf("Advertising count = %d\n", count);
count++;
delay(1000);
}
I am using
esp_base_mac_addr_set(macaddress)
function at the start just to check if the esp can change its mac address and it does indeed work and sets the esp base address.
The delay after setting the random address doesn't help. The esp is still stuck with the same address from esp_base_mac_addr_set. Maybe it needs to be told to use the random address instead of the fixed address from esp_base_mac_addr_set?
From what I can see I would suggest putting the delay after pAdvertising->stop(); as it may not have stopped before the change. That said, I think there is something else going on, since the next call to start advertising will use the address type set in NimBLEDevice it may be using the static address still when advertising starts, so an added call to NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM) may be needed in setup.
The delay on its own didn't work. However using NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM) finally made it work! Thanks for the amazing insight.
The following code works
/** NimBLE_Service_Data_Advertiser Demo:
*
* Simple demo of advertising service data that changes every 5 seconds
*
* Created: on February 7 2021
* Author: H2zero
*
*/
#include <NimBLEDevice.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
uint8_t macaddress[] = {0xd2, 0x7b, 0x70, 0x1c, 0x0f, 0xd2};
static NimBLEUUID dataUuid(SERVICE_UUID);
static NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
static uint32_t count = 0;
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
esp_base_mac_addr_set(macaddress);
Serial.println("Base MAC address set");
NimBLEDevice::init("svc data");
Serial.println("nimBLE init done");
pAdvertising->setServiceData(dataUuid, std::string((char*)&count, sizeof(count)));
pAdvertising->start();
Serial.printf("Advertising with count = %d and base mac address\n", count);
count++;
delay(1000);
pAdvertising->stop();
NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM);
}
void loop() {
pAdvertising->setServiceData(dataUuid, std::string((char*)&count, sizeof(count)));
macaddress[3] = count;
int rc = ble_hs_id_set_rnd(macaddress);
assert(rc == 0);
pAdvertising->start();
Serial.printf("Advertising with count = %d, and random mac address\n", count);
count++;
delay(100);
pAdvertising->stop();
}
Note: Indeed as you mentioned, NimBLE uses little endian for mac addresses as opposed to esp_base_mac_addr_set. This is confirmed as the mac address when the loop() is running is inverted with respect to the order in the array.
Great! Thanks for the update!
The mac address type set in this way is random, I want the type to be public, I use NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_PUBLIC); or NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT); The program does not work properly.
You cannot set a custom public mac address, only a random static, resolvable, or non-resolvable. See this for details: https://novelbits.io/bluetooth-address-privacy-ble/
The mac address set by esp_base_mac_addr_set is public, but this API has a limitation (least significant bit of first byte must be zero)
Yes the esp_base_mac_addr_set function is the only way to set the public address as far as I know. This is the address the the NimBLE stack retrieves from the controller when it starts, which is then used as the public MAC address. I do not know of any other method to change this as it is handled by the espressif BLE controller, not NimBLE.
Out of curiosity, what are you trying to achieve and why do you need to do it this way?
I found a solution
1.esp_base_mac_addr_set lsb must be zero limit
Remove the relevant check in esp-idf/components/esp_hw_support/mac_addr.c
if (mac[0] & 0x01) {
ESP_LOGE(TAG, "Base MAC must be a unicast MAC");
return ESP_ERR_INVALID_ARG;
}
Then use esp32-arduino-lib-builder to compile and generate libesp_hw_support.a and replace ~/.arduino15/packages/esp32/hardware/esp32/2.0.3/tools/sdk/esp32/qspi_qspi/libesp_hw_support.a
- No need to restart after modifying the mac address
esp_base_mac_addr_set(newMACAddress);
BLEDevice::stopAdvertising();
pServer->removeService(pService, true);
NimBLEDevice::deinit();
NimBLEDevice::init("esp");
pAdvertising->addServiceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
BLEDevice::startAdvertising();
pAdvertising->start();
Well that's certainly one way to do it :smile:. I'm curious though, why do you need this to be the public address? For most uses it should not matter.