Introduction

For some reason there is not much documentation on Linux Bluetooth stack. Especially if the most low level layer in question. There is one introduction by Albert Huang that covers very basic use-cases. It can be useful but does not go deep enough. At least for me.

Among other sources of information that are worthreading is BlueZ code. Tools and libraries written in C and Python. The code is not self-documented unfortunately.

This overview is intended to get some clue on what is going on with Bluetooth in Linux, and how user-space BlueZ is useful. Not accurate nor complete, I am newbie.

BlueZ

In kernel space BlueZ is a Bluetooth protocol stack. Can be found under /net/bluetooth/ in Linux source. It cannot be found under /Documentation/, kernel part of BlueZ is not documented.

In user-space in Debian it is:

  1. bluetoothd daemon with plug-in based architecture
  2. bunch of command line tools (hciconfig, sdptool and etc.)
  3. shared libraries, feel free to link with bluetooth.so
  4. DBus API

On a filesystem, besides binaries, it is:

  1. /etc/bluetooth/
  2. /var/lib/bluetooth/

User-space part of BlueZ is somehow described in manpages and /usr/share/doc/bluez. DBus API documented under /doc/ in BlueZ sources.

How it works

The reference specification is written by Bluetooth special interest group: https://www.bluetooth.org/en-us/specification .

Most things in BlueZ implemented with network interface. So that communication is reading/writting sockets. Other things can be done with ioctl() interface.

If nothing controlled in user-space, controller still has some sane activity in ether. In particular, this should be enough to make controller pingable with «l2ping» (providing «hci0» interface exists):

# rfkill unblock bluetooth
# hciconfig hci0 up
# hciconfig hci0 pscan

In order to make device participate in more complex processes like pairing, an application should monitor HCI events via a raw socket and send appropriate HCI commands (with socket interface as well). As an example it can be a bluez-simple-agent (do not look at its source code, it does not work with HCI raw socket directly, but through DBus and bluetoothd).

Bluetooth services should be advertised with SDP service. A service itself and an SDP service that keeps a record about that one, are in general different processes. It is technically possible to advertise fake services with SDP service alone, as well as it is possible to run services without SDP. SDP is a network service that talks SDP protocol on a well-known L2CAP channel.

Bluetooth services are network services that talk the advertised protocol.

Prerequisites

In all examples two Bluetooth adapter addresses will be used: «RE:MO:TE:AD:DR:RS» and «LO:CA:LE:AD:DR:RS». Bluetooth interface «hci0» assumed.

To avoid unnecessary notes about specific configuration flavors, there is one:

# rfkill unblock bluetooth
# hciconfig hci0 up
# hciconfig hci0 piscan

Tool for local traffic sniffing:

# hcidump -ti hci0

All presented tools can be interrupted with SIGINT or anything (Ctrl-C).

No any bluetoothd daemons supposed on slave machine. Everything done from scratch (in user-space).

SDP

On a slave:

$ make sdp-server ; sudo ./sdp-server
cc sdp-server.c -Wall -Wextra -g -O2 -lbluetooth -o sdp-server
sdp-server: Waiting for incoming connections...
sdp-server: [4] acccepted incoming connection.
sdp-server: [4] Session established with: RE:MO:TE:AD:DR:RS.
sdp-server: [4] SDP PDU header for incoming connection:
sdp-server: 	[4] PDU ID = 0x06 - SDP_ServiceSearchAttributeRequest
sdp-server: 	[4] Transaction ID = 0x0000
sdp-server: 	[4] Parameters Length = 0x000F
sdp-server: [4] process_sdp_pdu() finished.
sdp-server: [4] process_session() finished.
sdp-server: [4] connection closed.
sdp-server: Waiting for incoming connections...

Meantime on a master:

$ sdptool browse LO:CA:LE:AD:DR:RS
Browsing D0:E7:82:B5:09:D8 ...
Service RecHandle: 0x10000
Service Class ID List:
  "Serial Port" (0x1101)
Protocol Descriptor List:
  "RFCOMM" (0x0003)
    Channel: 11
Profile Descriptor List:
  "Serial Port" (0x1101)
    Version: 0x0100

RFCOMM

On slave the first concurrent process:

$ make rfcomm-server ; sudo ./rfcomm-server 
cc rfcomm-server.c -Wall -Wextra -g -O2 -lbluetooth -o rfcomm-server
rfcomm-server: Listening on RFCOMM channel 11.
rfcomm-server: Incoming connection accepted.
rfcomm-server: [4] sending MOTD:
Incoming address: RE:MO:TE:AD:DR:RS, chan=11.
Type anything to disconnect.

rfcomm-server: [4] some input received.
rfcomm-server: Listening on RFCOMM channel 11.

On slave the second concurrent process:

$ make hci-host ; sudo ./hci-host
cc hci-host.c -Wall -Wextra -g -O2 -lbluetooth -o hci-host
hci-host: first_dev() found device: 0.
hci-host: recv(hdr) accepted: type=0x04, code=0x04, length=10.
hci-host: dispatch_event() pass: 0x04.
hci-host: recv(hdr) accepted: type=0x04, code=0x0F, length=4.
hci-host: dispatch_event() pass: 0x0F.
[…]
hci-host: recv(hdr) accepted: type=0x04, code=0x31, length=6.
hci-host: dispatch_event() process: EVT_IO_CAPABILITY_REQUEST [0x31]
hci-host: send_capabilities(): written successfully.
hci-host: recv(hdr) accepted: type=0x04, code=0x0E, length=10.
hci-host: dispatch_event() pass: 0x0E.
hci-host: recv(hdr) accepted: type=0x04, code=0x33, length=10.
hci-host: dispatch_event() process: EVT_USER_CONFIRM_REQUEST [0x33]
hci-host: recv(hdr) accepted: type=0x04, code=0x0E, length=10.
[…]
hci-host: recv(hdr) accepted: type=0x04, code=0x05, length=4.
hci-host: dispatch_event() pass: 0x05.

On master, if it is Linux desktop, after those both processes are started on slave:

$ sudo rfcomm connect hci0 LO:CA:LE:AD:DR:RS 11
Connected /dev/rfcomm0 to LO:CA:LE:AD:DR:RS on channel 11
Press CTRL-C for hangup
Disconnected

MOTD could be read from /dev/rfcomm0 if anybody cared. But also connecting with Terminal from Android works fine and displays the MOTD.

Note that master might have link key persistent across subsequent re-connections that is not implemented on the slave side. «service bluetooth restart» should be enough.