ISO(7)
π ISO(7) β iso β ISO transport π
Linux System Administration bestie, and we are literally in the Bluetooth isochronous channels manual page. Itβs giving Linux 6.0 experimental era, and Iβm obsessed. πβ¨
π NAME π
isoβ ISO transport. Thatβs the whole name, no cap. π
β¨ SYNOPSIS β¨ (SINCE LINUX 6.0 [EXPERIMENTAL])
Okay so this is the setup, bestie β since Linux 6.0, and yes it says [EXPERIMENTAL] so read the vibe check:
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/iso.h>
iso_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
π¦ DESCRIPTION π¦
Bluetooth Isochronous Channels is a feature introduced in Bluetooth 5.2 that allows the transmission of multiple, synchronized audio streams between devices. Iconic behavior, honestly. π₯
For unicast/multi-stream audio, connected isochronous group (CIG) and connected isochronous stream (CIS) are used. A CIG is created by the central and it can include one or more CISs. A CIS is a point-to-point data transportation stream between a central and a certain peripheral, and it is a bidirectional communication protocol with acknowledgment. Periodt β bidirectional and acknowledged. π
For broadcast audio, broadcast isochronous group (BIG) and broadcast isochronous stream (BIS) are used. There are two types of devices: isochronous broadcaster and synchronized receiver. A BIG is created by an isochronous broadcaster, and it can include one or more BISs. A BIS is a one-to-many data transportation stream. Itβs giving main character energy on the broadcaster side, ngl. π
πΈ SOCKET ADDRESS πΈ
Here are the structs, bestie β keep them exactly as they are:
struct sockaddr_iso_bc {
bdaddr_t bc_bdaddr;
uint8_t bc_bdaddr_type;
uint8_t bc_sid;
uint8_t bc_num_bis;
uint8_t bc_bis[ISO_MAX_NUM_BIS];
};
struct sockaddr_iso {
sa_family_t iso_family;
bdaddr_t iso_bdaddr;
uint8_t iso_bdaddr_type;
struct sockaddr_iso_bc iso_bc[];
};
π Unicast example
struct sockaddr_iso addr;
memset(&addr, 0, sizeof(addr));
addr.iso_family = AF_BLUETOOTH;
bacpy(&addr.iso_bdaddr, bdaddr);
addr.iso_bdaddr_type = BDADDR_LE_PUBLIC;
π Broadcast example
struct sockaddr_iso *addr;
size_t addr_len;
addr_len = sizeof(*addr) + sizeof(*addr->iso_bc);
memset(addr, 0, addr_len);
addr->iso_family = AF_BLUETOOTH;
bacpy(&addr->iso_bdaddr, bdaddr);
addr->iso_bdaddr_type = BDADDR_LE_PUBLIC;
π Broadcast Source (Broadcaster) example
struct sockaddr_iso *addr;
size_t addr_len;
addr_len = sizeof(*addr) + sizeof(*addr->iso_bc);
memset(addr, 0, addr_len);
addr->iso_family = AF_BLUETOOTH;
/* Set address to BDADR_ANY(00:00:00:00:00:00) */
bacpy(&addr->iso_bdaddr, BADDR_ANY);
addr->iso_bdaddr_type = BDADDR_LE_PUBLIC;
/* Connect to Broadcast address */
connect(iso_socket, (struct sockaddr *)addr, addr_len);
π Broadcast Sink (Receiver) example
struct sockaddr_iso *addr;
size_t addr_len;
addr_len = sizeof(*addr) + sizeof(*addr->iso_bc);
memset(addr, 0, addr_len);
addr->iso_family = AF_BLUETOOTH;
/* Set destination to Broadcaster address */
bacpy(&addr->iso_bdaddr, bdaddr);
addr->iso_bdaddr_type = BDADDR_LE_PUBLIC;
/* Bind to Broadcaster address */
bind(iso_socket, (struct sockaddr *)addr, addr_len);
π Broadcast Source (Broadcaster) or Broadcast Sink (Receiver) Periodic Advertising Sync Transfer (PAST)
struct sockaddr_iso *addr;
size_t addr_len;
addr_len = sizeof(*addr) + sizeof(*addr->iso_bc);
memset(addr, 0, addr_len);
addr->iso_family = AF_BLUETOOTH;
/* Set destination address to PAST destination address */
bacpy(&addr->iso_bc->bc_bdaddr, (void *) bdaddr);
addr->iso_bc->bc_bdaddr_type = bdaddr_type;
/* Rebind already connected socket to PAST address */
bind(iso_socket, (struct sockaddr *)addr, addr_len);
π SOCKET OPTIONS (SOL_BLUETOOTH) π
The socket options listed below can be set by using setsockopt(2) and read with getsockopt(2) with the socket level set to SOL_BLUETOOTH. Thatβs the whole vibe. π«Ά
π BT_SECURITY
Channel security level, possible values:
BT_SECURITY_SDPβ value0(SDP Only); link key type: None; encryption: Not required.BT_SECURITY_LOWβ value1(Low); link key type: Unauthenticated; encryption: Not required.BT_SECURITY_MEDIUMβ value2(Medium - default); link key type: Unauthenticated; encryption: Desired.BT_SECURITY_HIGHβ value3(High); link key type: Authenticated; encryption: Required.BT_SECURITY_FIPS(since Linux 3.15) β value4(Secure Only); link key type: Authenticated (P-256 based Secure Simple Pairing and Secure Authentication); encryption: Required.
Example:
int level = BT_SECURITY_HIGH;
int err = setsockopt(iso_socket, SOL_BLUETOOTH, BT_SECURITY, &level,
sizeof(level));
if (err == -1) {
perror("setsockopt");
return 1;
}
π BT_DEFER_SETUP
Channel defer connection setup, this controls if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level. Possible values:
0β Disable (default); authorization: Not required.1β Enable; authorization: Required.
Example:
int defer_setup = 1;
int err = setsockopt(iso_socket, SOL_BLUETOOTH, BT_DEFER_SETUP,
&defer_setup, sizeof(defer_setup));
if (err == -1) {
perror("setsockopt");
return err;
}
err = listen(iso_socket, 5);
if (err) {
perror("listen");
return err;
}
struct sockaddr_iso remote_addr = {0};
socklen_t addr_len = sizeof(remote_addr);
int new_socket = accept(iso_socket, (struct sockaddr*)&remote_addr,
&addr_len);
if (new_socket < 0) {
perror("accept");
return new_socket;
}
/* To complete the connection setup of new_socket read 1 byte */
char c;
struct pollfd pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = new_socket;
pfd.events = POLLOUT;
err = poll(&pfd, 1, 0);
if (err) {
perror("poll");
return err;
}
if (!(pfd.revents & POLLOUT)) {
err = read(sk, &c, 1);
if (err < 0) {
perror("read");
return err;
}
}
BT_PKT_STATUSβ turns on packet-status reporting, so every received packet comes with aBT_SCM_PKT_STATUSCMSG attached π. Possible values:0β Disable (default)1β Enable
BT_SCM_PKT_STATUS (the actual CMSG)
Level SOL_BLUETOOTH CMSG carrying this data:
uint8_t pkt_status;
The values are equal to the "Packet_Status_Flag" defined in Core Specification v6.1, 5.4.5. HCI ISO Data packets:
https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-61/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-9b5fb085-278b-5084-ac33-bee2839abe6b
0x0β Valid data. The complete SDU was received correctly β¨0x1β Possibly invalid data. The contents of theISO_SDU_Fragment, may contain errors or part of the SDU may be missing. This is reported as 'data with possible errors' π¬0x2β Part(s) of the SDU were not received correctly. This is reported as 'lost data' π
BT_PKT_SEQNUM(since Linux 6.17-rc1) β enables reporting the packet ISO sequence number, delivered as aBT_SCM_PKT_SEQNUMCMSG on received packets π¦. Possible values:0β Disable (default)1β Enable
BT_SCM_PKT_SEQNUM (yes queen, the CMSG)
Level SOL_BLUETOOTH CMSG with data:
uint16_t pkt_seqnum;
The values are equal to the "Packet_Sequence_Number" defined in Core Specification v6.1, 5.4.5. HCI ISO Data packets:
https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-61/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-9b5fb085-278b-5084-ac33-bee2839abe6b
Example (Enable sequence numbers):
uint32_t opt = 1;
if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_SEQNUM, &opt, sizeof(opt)) < 0)
goto error;
Example (Read packet and its sequence number):
char data_buf[256];
uint16_t seqnum;
union {
char buf[CMSG_SPACE(sizeof(uint16_t))];
struct cmsghdr align;
} control;
struct iovec data = {
.iov_base = data_buf,
.iov_len = sizeof(data_buf),
};
struct msghdr msg = {
.msg_iov = &data,
.msg_iovlen = 1,
.msg_control = control.buf,
.msg_controllen = sizeof(control.buf),
};
struct scm_timestamping tss;
res = recvmsg(fd, &msg, 0);
if (res < 0)
goto error;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_PKT_SEQNUM)
memcpy(&seqnum, CMSG_DATA(cmsg), sizeof(seqnum));
}
π SOCKET OPTIONS (SOL_SOCKET) π
Okay bestie, level SOL_SOCKET socket options that tweak generic socket features (SO_SNDBUF, SO_RCVBUF, etc.) have their usual meaning, see socket(7). No reinventing the wheel here π
The SOL_SOCKET level ISO socket options that have Bluetooth-specific handling in kernel are listed below π
β° SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS
Here's the lore: https://docs.kernel.org/networking/timestamping.html
For ISO sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported since Linux 6.15 π₯
The software RX timestamp is the time when the kernel received the packet from the controller driver.
The SCM_TSTAMP_SND timestamp is emitted when packet is sent to the controller driver.
The SCM_TSTAMP_COMPLETION timestamp is emitted when controller reports the packet completed. Completion timestamps are only supported on controllers that have ISO flow control. Other TX timestamp types are not supported π
ββοΈ
You can use SIOCETHTOOL to query supported flags.
The timestamps are in CLOCK_REALTIME time β
Example (Enable RX timestamping):
int flags = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_RX_SOFTWARE;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
Example (Read packet and its RX timestamp):
char data_buf[256];
union {
char buf[CMSG_SPACE(sizeof(struct scm_timestamping))];
struct cmsghdr align;
} control;
struct iovec data = {
.iov_base = data_buf,
.iov_len = sizeof(data_buf),
};
struct msghdr msg = {
.msg_iov = &data,
.msg_iovlen = 1,
.msg_control = control.buf,
.msg_controllen = sizeof(control.buf),
};
struct scm_timestamping tss;
res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (res < 0)
goto error;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING)
memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss));
}
tstamp_clock_realtime = tss.ts[0];
Example (Enable TX timestamping):
int flags = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_OPT_ID;
setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
Example (Read TX timestamps):
union {
char buf[CMSG_SPACE(sizeof(struct scm_timestamping))];
struct cmsghdr align;
} control;
struct iovec data = {
.iov_base = NULL,
.iov_len = 0
};
struct msghdr msg = {
.msg_iov = &data,
.msg_iovlen = 1,
.msg_control = control.buf,
.msg_controllen = sizeof(control.buf),
};
struct cmsghdr *cmsg;
struct scm_timestamping tss;
struct sock_extended_err serr;
int res;
res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (res < 0)
goto error;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING)
memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss));
else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR)
memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr));
}
tstamp_clock_realtime = tss.ts[0];
tstamp_type = serr->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */
tstamp_seqnum = serr->ee_data;
π IOCTLS π
The following ioctls with operation specific for ISO sockets are available. (Yes, ioctls. They're giving legacy but they ate, ngl π«Ά)
π¦ SIOCETHTOOL (since Linux 6.16-rc1)
Supports only command ETHTOOL_GET_TS_INFO, which may be used to query supported SOF_TIMESTAMPING_* flags. The SOF_TIMESTAMPING_OPT_* flags are always available as applicable π
Example:
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
...
struct ifreq ifr = {};
struct ethtool_ts_info cmd = {};
int sk;
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "hci0");
ifr.ifr_data = (void *)&cmd;
cmd.cmd = ETHTOOL_GET_TS_INFO;
sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
if (sk < 0)
goto error;
if (ioctl(sk, SIOCETHTOOL, &ifr))
goto error;
sof_available = cmd.so_timestamping;
πΈ RESOURCES πΈ
http://www.bluez.org
π REPORTING BUGS π
linux-bluetooth@vger.kernel.org
π SEE ALSO π
π COPYRIGHT π
Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL).
and that's the whole page, bestie β you made it to the end, so proud of you π₯Ήπ this one's a BlueZ production, dated July 2025, living in section 7 as ISO(7). now go forth and timestamp something iconic β¨
π¦ where this page comes from
- centos-stream 9: bluez-libs-devel 5.87+2.git8750129efca8-1.el9 Β· GPLv2+
- centos-stream 9: bluez-libs-devel 5.87+2.git8750129efca8-1.el9 Β· GPLv2+
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms