L2CAP connection between nimble and BlueZ

Hello! I’m trying to create a L2CAP connection between nRF52840DK and RPI’s native bluetooth. The nRF52840 running RIOT OS and nimble stack, RPI uses BlueZ 5.50 bluetooth stack.

What hardware and software am I using?

I have a raspberry pi 3b+ and few nRF52840DK board. I’m try to use the RPI’s native bluetooth module rather than a dongle. The BlueZ version is 5.50, the nRF52840DK is running nimble_l2cap_server example

What works?

I have successfully run the example IPv6-over-BLE with my hardware.

I tried to write a C program based on BlueZ stack to create L2CAP connection. For now, the RPI can discover nRF52840 using function hci_le_scan() and create GAP connection using hci_le_create_conn() successfully. During the connection establishment phase, the node reveived two GAP event, which is BLE_GAP_EVENT_CONNECT (code 0) and BLE_GAP_EVENT_MTU (code 15) . The MTU event change the MTU of ATT from 0 to 256.

What is the problem?

However, when I use connect() to create L2CAP socket connection, timeout occured and returns an error: Host is down and error code -1. The nRF52840 received a BLE_GAP_EVENT_DISCONNECT (code 1) . Maybe there is some mistakes in my code because there are few related codes online. How can I make it right? The nRF output is:

and my C program based on BlueZ is:

#include "blescan.h"
#include <sys/socket.h>
#include <bluetooth/l2cap.h>

int main(int argc, char *argv[]){
    int id_type;
    char *id;
    bdaddr_t src_addr;
    uint16_t le_conn_handle = 0;
    char dest_addr_str[18];
    // to check wether the device is advertisiing
    if (argc < 3){
        perror("Too few arguments");
        exit(0);
    }
    id = argv[2];
    if (strcmp(argv[1], "-m") == 0){
        id_type = ID_TYPE_MAC;
    }
    else if (strcmp(argv[1], "-n") == 0){
        id_type = ID_TYPE_NAME;
    }
    else{
        perror("Unknow argument");
        exit(0);
    }
    memset(dest_addr_str, 0, sizeof(dest_addr_str));
    int res = blescan(id_type, id, 100, dest_addr_str);
    if (res == 0){  // Cannot find the corresponding BLE device
        perror("Can not find the target device");
        exit(0);
    }
    printf("find the target\n");
    // open a hci socket
    int device_id = hci_get_route(NULL);
    int hci_socket = hci_open_dev(device_id);
    hci_devba(device_id, &src_addr);

    // Create a le connection
    bdaddr_t dest_bdaddr;
    memset(&dest_bdaddr, 0, sizeof(dest_bdaddr));
    str2ba(dest_addr_str, &dest_bdaddr);
    res = hci_le_create_conn(hci_socket, htobs(0x04), htobs(0x04), 0, LE_RANDOM_ADDRESS,
                             dest_bdaddr, LE_PUBLIC_ADDRESS, htobs(0x0018), htobs(0x0028), 
                             htobs(0), htobs(0x0100), htobs(0x0000), htobs(0x0000), 
                             &le_conn_handle, 25000);
    if (res != 0){
        perror("Connection error");
        goto hciclose;
    }

    // Create a L2CAP socket
    struct sockaddr_l2 dest_sockaddr;
    memset(&dest_sockaddr, 0, sizeof(dest_sockaddr));
    dest_sockaddr.l2_family = AF_BLUETOOTH;
    dest_sockaddr.l2_psm = htobs(0x0235);
    str2ba(dest_addr_str, &dest_sockaddr.l2_bdaddr);
    int l2cap_client_socket = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
    if (l2cap_client_socket < 0){
        perror("Can not open l2cap socket");
        goto hciclose;
    }
    // Connect to nrf52840 failed with a timeout
    res = connect(l2cap_client_socket, (struct sockaddr *)&dest_sockaddr, sizeof(dest_sockaddr));
    if (res == 0){
        res = write(l2cap_client_socket, "hello from linux", 17);
    }
    else if(res < 0){
        printf("error code = %d\n", res);
        perror("l2cap socket connction failed");
        hci_disconnect(hci_socket, le_conn_handle, HCI_OE_USER_ENDED_CONNECTION, 1000);
        goto hciclose;
    }

hciclose:
    if (le_conn_handle != 0){
        hci_disconnect(hci_socket, le_conn_handle, HCI_OE_USER_ENDED_CONNECTION, 1000);
    }
    hci_close_dev(device_id);
    exit(0);
}

Thanks a lot!

Do you want to use IPv6 or was that just to test if it works in general?

I just want to establish a L2CAP connection between RPI and nrf52840 like the tests/nimble_l2cap_client and tests/nimble_l2cap exapmles, but change the client from nrf52840 to RPI.

Just tried to recreate your example on my Linux host to see if I can find out whats going wrong. But unfortunately I am not able to compile your code. I am not sure however, if this is due to differences of my Linux distribution (Mint 20) compared to the PI or something else. Would it be possible that you provide also your, as it seems, custom blescan.h header?!

Hey hauke, Thanks for your reply. I have upload my code to github https://github.com/sada45/bluez-nimble I’m running the code on Raspberry pi 3B+ with kernal 5.4.83-v7+ and bluez 5.50

Thanks! The complete code runs just fine on my Linux host. I can confirm the behavior you describe above: Linux connects to the RIOT board (GAP event 0), but fails to open the actual L2CAP channel. I did a quick trace with my BLE sniffer:

As it seems, LInux tries to do a GATT service discovery. But the NimBLE example is compiled without any GATT support. This seems to upset Linux and it will simply timeout and close the connection again.

So I tried temporarily enabling the GATT server for the tests/nimble_l2cap_server application, but although the GATT discovery now succeeds it does not change anything in the general behavior, after the GATT discovery finishes the connection idles until Linux decides to close it again, as before:

For some reason, LInux does not try to open the actual L2CAP COC. But here my knowledge with BlueZ is quite limited, so I can’t really tell why this is… At least it seems to me that this is the direction you would need to look a little further.

Thanks very much hauke! It just so happens that I bought a sniffer today :slight_smile: . I will study this issue further.

Sweet :slight_smile:

Let us know what you find, would be great to gain some more insights on how BlueZ handles L2CAP channels.