Search code examples
cbluetoothraspberry-pibluez

BlueZ unable to accept incoming connection while advertising with SDP due to bluetoothd


I am trying to use a Pi to emulate a Bluetooth device using the BlueZ C api. I am able to separately 1) configure the SDP server to advertise the correct service and 2) listen for and establish an L2CAP connection. However, I'm unable to do both at the same time.

The issue is that sdp_record_register() will segfault unless bluetoothd is both running and in compatibility mode. However, accept() won't return for the Bluetooth socket if bluetoothd is running, because bluetoothd will steal the request.

So I can either:

  1. Register/advertise my service with SDP, but not be able to accepting incoming connections, by running bluetoothd (in compatibility mode).
  2. Be able to accept incoming connections, but not able to register/advertise my service, by not running bluetoothd.

Setting up the SDP service

  int deviceID = hci_get_route(NULL);
  if (deviceID < 0) {
    printf("Error: Bluetooth device not found\n");
    exit(1);
  }

  int bluetoothHCISocket = hci_open_dev(deviceID);
  if (bluetoothHCISocket < 0) {
    perror("hci_open_device");
    exit(2);
  }

  /* some HCI config */

  sdp_session_t *session = sdp_connect(&myBDAddrAny, &myBDAddrLocal, SDP_RETRY_IF_BUSY);
  sdp_record_t record;
  bzero(&record, sizeof(sdp_record_t));
  record.handle = 0x10000;

  /* register all of the attributes for my service */

  printf("Might segfault\n");
  if (sdp_record_register(session, &record, SDP_RECORD_PERSIST) < 0) {
    perror("sdp_record_register");
    exit(7);
  }
  printf("Didn't segfault\n");

This works when bluetoothd is running in compatibility mode, but will segfault when it's either not running or running in default mode.

Accepting a Bluetooth connection

  int btSocket = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
  if (btSocket < 0) {
    perror("socket");
    exit(3);
  }
  struct sockaddr_l2 loc_addr = { 0 };
  loc_addr.l2_family = AF_BLUETOOTH;
  loc_addr.l2_bdaddr = myBDAddrAny;
  loc_addr.l2_psm = htobs(0x11);

  if (bind(btSocket, (struct sockaddr *)&loc_addr, sizeof(loc_addr))) {
    perror("bind");
    exit(4);
  }
  if (listen(btSocket, 1)) {
    perror("listen");
    exit(6);
  }

  struct sockaddr_l2 remoteAddress;
  socklen_t socketSize = sizeof(remoteAddress);
  printf("Waiting for connection\n");
  int clientSocket = accept(btSocket, (struct sockaddr *)&remoteAddress, &socketSize);

This will properly accept an incoming connection when bluetoothd is not running, but accept() will never return if bluetoothd is running (in any mode).

I haven't been unable to reconcile these two issues. It seems like the ideal solution would be to somehow tell bluetoothd to ignore connections on PSM 0x11 (since that means its agent can still handle pairing), but I can't figure out how to do that.


Solution

  • The (unsatisfying but correct) answer is to not use the hci* API. That API is apparently deprecated, so bugs like that segfault are not going to be fixed. The correct way to do this is to use the DBus API. That API is almost as cumbersome as the hci API, but at least it's documented.

    After swapping out the massive amount of hci-based code I'de written with the gdbus API offered by glib-2.0 to set up the SDP service, I was finally able to advertise the service and connect at the same time. My socket code worked without modification.