diff mbox series

[BlueZ,1/1] isotest: Add support for creating/synchronizing to multiple BISes

Message ID 20230529062734.3685-2-iulia.tanasescu@nxp.com
State New
Headers show
Series isotest: Add support for creating/synchronizing to multiple BISes | expand

Commit Message

Iulia Tanasescu May 29, 2023, 6:27 a.m. UTC
This adds an additional command line option for the Broadcast exercise,
so that the user can indicate the number of BISes to create as part of a
BIG (Broadcast Source), or the number of BISes to synchronize
to (Broadcast Sink).

For the Broadcast Source exercise, issue the following command,
in order to create a BIG with handle 0x01, associated with the
advertising handle 0x01, with 2 BISes:

    tools/isotest -i hci0 -s 00:00:00:00:00:00 -N 2 -G 1 -T 1

The isotest and btmon logs will look something like this:

isotest[7178]: mgmt socket: fd 3
isotest[7178]: mgmt_set_le: err 0
isotest[7178]: mgmt_set_experimental: err 0
isotest[7179]: Exit
isotest[7178]: Connecting 00:00:00:00:00:00 ...
isotest[7178]: Connected [00:00:00:00:00:00]
isotest[7178]: QoS [BIG 0x01 BIS 0x01 Packing 0x00 Framing 0x00 Encryption 0x00]
isotest[7178]: Input QoS [Interval 10000 us Latency 10 ms SDU 0 PHY 0x00 RTN 2]
isotest[7178]: Output QoS [Interval 10000 us Latency 10 ms SDU 40 PHY 0x02 RTN 2]
isotest[7178]: Connecting 00:00:00:00:00:00 ...
isotest[7178]: Connected [00:00:00:00:00:00]
isotest[7178]: QoS [BIG 0x01 BIS 0x01 Packing 0x00 Framing 0x00 Encryption 0x00]
isotest[7178]: Input QoS [Interval 10000 us Latency 10 ms SDU 0 PHY 0x00 RTN 2]
isotest[7178]: Output QoS [Interval 10000 us Latency 10 ms SDU 40 PHY 0x02 RTN 2]
isotest[7183]: Sending ...
isotest[7183]: Number of packets: 1
isotest[7183]: Socket jitter buffer: 80 buffer
isotest[7183]: [seq 0] 40 bytes buffered 92 (3712 bytes)
isotest[7184]: Sending ...
isotest[7184]: Number of packets: 1
isotest[7184]: Socket jitter buffer: 80 buffer
isotest[7184]: [seq 0] 40 bytes buffered 92 (3712 bytes)
isotest[7178]: Exit

< HCI Command: LE Create Broadcast Isochronous Group (0x08|0x0068) plen 31
        Handle: 0x01
        Advertising Handle: 0x01
        Number of BIS: 2
        SDU Interval: 10000 us (0x002710)
        Maximum SDU size: 40
        Maximum Latency: 10 ms (0x000a)
        RTN: 0x02
        PHY: LE 2M (0x02)
        Packing: Sequential (0x00)
        Framing: Unframed (0x00)
        Encryption: 0x00
        Broadcast Code: 00000000000000000000000000000000

> HCI Event: Command Status (0x0f) plen 4
      LE Create Broadcast Isochronous Group (0x08|0x0068) ncmd 1
        Status: Success (0x00)

> HCI Event: LE Meta Event (0x3e) plen 23
      LE Broadcast Isochronous Group Complete (0x1b)
        Status: Success (0x00)
        Handle: 0x01
        BIG Synchronization Delay: 1974 us (0x0007b6)
        Transport Latency: 1974 us (0x0007b6)
        PHY: LE 2M (0x02)
        NSE: 3
        BN: 1
        PTO: 1
        IRC: 3
        Maximum PDU: 40
        ISO Interval: 10.00 msec (0x0008)
        Connection Handle #0: 10
        Connection Handle #1: 11

< HCI Command: LE Setup Isochronous Data Path (0x08|0x006e) plen 13
        Handle: 10
        Data Path Direction: Input (Host to Controller) (0x00)
        Data Path: HCI (0x00)
        Coding Format: Transparent (0x03)
        Company Codec ID: Ericsson Technology Licensing (0)
        Vendor Codec ID: 0
        Controller Delay: 0 us (0x000000)
        Codec Configuration Length: 0
        Codec Configuration: 

> HCI Event: Command Complete (0x0e) plen 6
      LE Setup Isochronous Data Path (0x08|0x006e) ncmd 1
        Status: Success (0x00)
        Handle: 10

< HCI Command: LE Setup Isochronous Data Path (0x08|0x006e) plen 13
        Handle: 11
        Data Path Direction: Input (Host to Controller) (0x00)
        Data Path: HCI (0x00)
        Coding Format: Transparent (0x03)
        Company Codec ID: Ericsson Technology Licensing (0)
        Vendor Codec ID: 0
        Controller Delay: 0 us (0x000000)
        Codec Configuration Length: 0
        Codec Configuration: 

> HCI Event: Command Complete (0x0e) plen 6
      LE Setup Isochronous Data Path (0x08|0x006e) ncmd 1
        Status: Success (0x00)
        Handle: 11

< ISO Data TX: Handle 10 flags 0x02 dlen 44

< ISO Data TX: Handle 11 flags 0x02 dlen 44

> HCI Event: Number of Completed Packets (0x13) plen 5
        Num handles: 1
        Handle: 10
        Count: 1

> HCI Event: Number of Completed Packets (0x13) plen 5
        Num handles: 1
        Handle: 11
        Count: 1

For the Broadcast Sink exercise, issue the following command,
in order to synchronize to the BISes created by the source:

    tools/isotest -i hci1 -r 36:13:00:E1:1B:F0 -V le_random -N 2 -G 1

The flow is shown by the isotest log and the filtered btmon snippet below:

isotest[4033]: mgmt socket: fd 3
isotest[4034]: Exit
isotest[4033]: mgmt_set_le: err 0
isotest[4033]: mgmt_set_experimental: err 0
isotest[4033]: Waiting for connection 36:13:00:E1:1B:F0...
isotest[4036]: Connected [36:13:00:E1:1B:F0]
isotest[4036]: QoS [BIG 0x01 BIS 0x00 Packing 0x00 Framing 0x00 Encryption 0x00]
isotest[4036]: Input QoS [Interval 1974 us Latency 10 ms SDU 40 PHY 0x00 RTN 0]
isotest[4036]: Output QoS [Interval 0 us Latency 0 ms SDU 0 PHY 0x00 RTN 0]
isotest[4036]: Receiving ...
isotest[4037]: Connected [36:13:00:E1:1B:F0]
isotest[4037]: QoS [BIG 0x01 BIS 0x00 Packing 0x00 Framing 0x00 Encryption 0x00]
isotest[4037]: Input QoS [Interval 1974 us Latency 10 ms SDU 40 PHY 0x00 RTN 0]
isotest[4037]: Output QoS [Interval 0 us Latency 0 ms SDU 0 PHY 0x00 RTN 0]
isotest[4037]: Receiving ...
isotest[4037]: [seq 0] 280 bytes in 6.48 sec speed 0.34 kb/s
isotest[4036]: [seq 0] 280 bytes in 6.54 sec speed 0.33 kb/s
isotest[4037]: [seq 1] 280 bytes in 7.01 sec speed 0.31 kb/s
isotest[4036]: [seq 1] 280 bytes in 7.02 sec speed 0.31 kb/s
isotest[4037]: [seq 2] 280 bytes in 7.06 sec speed 0.31 kb/s
isotest[4036]: [seq 2] 280 bytes in 7.04 sec speed 0.31 kb/s

< HCI Command: LE Periodic Advertising Create Sync (0x08|0x0044) plen 14
        Options: 0x0000
        Use advertising SID, Advertiser Address Type and address
        Reporting initially enabled
        SID: 0x00
        Adv address type: Random (0x01)
        Adv address: 36:13:00:E1:1B:F0 (Non-Resolvable)
        Skip: 0x0000
        Sync timeout: 163840 msec (0x4000)
        Sync CTE type: 0x0000

> HCI Event: Command Status (0x0f) plen 4
      LE Periodic Advertising Create Sync (0x08|0x0044) ncmd 1
        Status: Success (0x00)

< HCI Command: LE Set Extended Scan Parameters (0x08|0x0041) plen 13
        Own address type: Public (0x00)
        Filter policy: Ignore not in accept list (0x01)
        PHYs: 0x05
        Entry 0: LE 1M
          Type: Passive (0x00)
          Interval: 60.000 msec (0x0060)
          Window: 30.000 msec (0x0030)
        Entry 1: LE Coded
          Type: Passive (0x00)
          Interval: 60.000 msec (0x0060)
          Window: 30.000 msec (0x0030)

> HCI Event: Command Complete (0x0e) plen 4
      LE Set Extended Scan Parameters (0x08|0x0041) ncmd 1
        Status: Success (0x00)

< HCI Command: LE Set Extended Scan Enable (0x08|0x0042) plen 6
        Extended scan: Enabled (0x01)
        Filter duplicates: Enabled (0x01)
        Duration: 0 msec (0x0000)
        Period: 0.00 sec (0x0000)

> HCI Event: Command Complete (0x0e) plen 4
      LE Set Extended Scan Enable (0x08|0x0042) ncmd 1
        Status: Success (0x00)

> HCI Event: LE Meta Event (0x3e) plen 16
      LE Periodic Advertising Sync Established (0x0e)
        Status: Success (0x00)
        Sync handle: 0
        Advertising SID: 0x00
        Advertiser address type: Random (0x01)
        Advertiser address: 36:13:00:E1:1B:F0 (Non-Resolvable)
        Advertiser PHY: LE 2M (0x02)
        Periodic advertising interval: 10.00 msec (0x0008)
        Advertiser clock accuracy: 0x00

> HCI Event: LE Meta Event (0x3e) plen 8
      LE Periodic Advertising Report (0x0f)
        Sync handle: 0
        TX power: 127 dbm (0x7f)
        RSSI: -47 dBm (0xd1)
        CTE Type: No Constant Tone Extension (0xff)
        Data status: Complete
        Data length: 0x00

> HCI Event: LE Meta Event (0x3e) plen 20
      LE Broadcast Isochronous Group Info Advertising Report (0x22)
        Sync Handle: 0x0000
        Number BIS: 2
        NSE: 3
        ISO Interval: 10.00 msec (0x0008)
        BN: 1
        PTO: 1
        IRC: 3
        Maximum PDU: 40
        SDU Interval: 10000 us (0x002710)
        Maximum SDU: 40
        PHY: LE 2M (0x02)
        Framing: Unframed (0x00)
        Encryption: 0x00

< HCI Command: LE Broadcast Isochronous Group Create Sync (0x08|0x006b) plen 26
        BIG Handle: 0x01
        BIG Sync Handle: 0x0000
        Encryption: Unencrypted (0x00)
        Broadcast Code: 00000000000000000000000000000000
        Maximum Number Subevents: 0x00
        Timeout: 163840 ms (0x4000)
        Number of BIS: 2
        BIS ID: 0x01
        BIS ID: 0x02

> HCI Event: LE Meta Event (0x3e) plen 19
      LE Broadcast Isochronous Group Sync Estabilished (0x1d)
        Status: Success (0x00)
        BIG Handle: 0x01
        Transport Latency: 1974 us (0x0007b6)
        NSE: 3
        BN: 1
        PTO: 1
        IRC: 3
        Maximum PDU: 40
        ISO Interval: 10.00 msec (0x0008)
        Connection Handle #0: 10
        Connection Handle #1: 11

< HCI Command: LE Setup Isochronous Data Path (0x08|0x006e) plen 13
        Handle: 10
        Data Path Direction: Output (Controller to Host) (0x01)
        Data Path: HCI (0x00)
        Coding Format: Transparent (0x03)
        Company Codec ID: Ericsson Technology Licensing (0)
        Vendor Codec ID: 0
        Controller Delay: 0 us (0x000000)
        Codec Configuration Length: 0
        Codec Configuration: 

> HCI Event: Command Complete (0x0e) plen 6
      LE Setup Isochronous Data Path (0x08|0x006e) ncmd 1
        Status: Success (0x00)
        Handle: 10

< HCI Command: LE Setup Isochronous Data Path (0x08|0x006e) plen 13
        Handle: 11
        Data Path Direction: Output (Controller to Host) (0x01)
        Data Path: HCI (0x00)
        Coding Format: Transparent (0x03)
        Company Codec ID: Ericsson Technology Licensing (0)
        Vendor Codec ID: 0
        Controller Delay: 0 us (0x000000)
        Codec Configuration Length: 0
        Codec Configuration: 

> HCI Event: Command Complete (0x0e) plen 6
      LE Setup Isochronous Data Path (0x08|0x006e) ncmd 1
        Status: Success (0x00)
        Handle: 11

> ISO Data RX: Handle 10 flags 0x06 dlen 48

> ISO Data RX: Handle 11 flags 0x06 dlen 48

---
 tools/isotest.c   | 290 ++++++++++++++++++++++++++++++++++------------
 tools/isotest.rst |   4 +
 2 files changed, 218 insertions(+), 76 deletions(-)
diff mbox series

Patch

diff --git a/tools/isotest.c b/tools/isotest.c
index caa711b2e..0eae3cdc1 100644
--- a/tools/isotest.c
+++ b/tools/isotest.c
@@ -32,6 +32,7 @@ 
 #include <linux/sockios.h>
 #include <time.h>
 #include <inttypes.h>
+#include <sys/wait.h>
 
 #include "lib/bluetooth.h"
 #include "lib/hci.h"
@@ -45,6 +46,9 @@ 
 #define SEC_USEC(_t)  (_t  * 1000000L)
 #define TS_USEC(_ts)  (SEC_USEC((_ts)->tv_sec) + NSEC_USEC((_ts)->tv_nsec))
 
+#define DEFAULT_BIG_ID 0x01
+#define DEFAULT_BIS_ID 0x01
+
 /* Test modes */
 enum {
 	SEND,
@@ -72,6 +76,8 @@  static bool quiet;
 struct bt_iso_qos *iso_qos;
 static bool inout;
 
+static uint8_t num_bis = 1;
+
 struct lookup_table {
 	const char *name;
 	int flag;
@@ -316,8 +322,6 @@  static int do_connect(char *peer)
 	struct sockaddr_iso addr;
 	int sk;
 
-	mgmt_set_experimental();
-
 	/* Create socket */
 	sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
 	if (sk < 0) {
@@ -390,6 +394,45 @@  error:
 	return -1;
 }
 
+static int *bcast_do_connect_mbis(uint8_t count, char *peer)
+{
+	int *sk;
+	uint8_t sk_cnt = 0;
+
+	sk = malloc(count * sizeof(*sk));
+	if (!sk) {
+		syslog(LOG_ERR, "Can't allocate socket array");
+		return NULL;
+	}
+
+	defer_setup = 1;
+
+	for (int i = 0; i < count; i++) {
+		if (i == count - 1)
+			defer_setup = 0;
+
+		sk[i] = do_connect(peer);
+		if (sk[i] < 0) {
+			syslog(LOG_ERR, "Can't create socket: %s (%d)",
+					strerror(errno), errno);
+
+			goto error;
+		}
+
+		sk_cnt++;
+	}
+
+	return sk;
+
+error:
+	for (int i = 0; i < sk_cnt; i++)
+		close(sk[i]);
+
+	free(sk);
+	return NULL;
+
+}
+
 static void do_listen(char *filename, void (*handler)(int fd, int sk),
 							char *peer)
 {
@@ -431,8 +474,11 @@  static void do_listen(char *filename, void (*handler)(int fd, int sk),
 	if (peer) {
 		str2ba(peer, &addr->iso_bc->bc_bdaddr);
 		addr->iso_bc->bc_bdaddr_type = bdaddr_type;
-		addr->iso_bc->bc_num_bis = 1;
-		addr->iso_bc->bc_bis[0] = 1;
+		addr->iso_bc->bc_num_bis = num_bis;
+
+		for (int i = 0; i < num_bis; i++)
+			addr->iso_bc->bc_bis[i] = i + 1;
+
 		optlen += sizeof(*addr->iso_bc);
 	}
 
@@ -584,6 +630,7 @@  static void recv_mode(int fd, int sk)
 							strerror(errno), errno);
 				if (errno != ENOTCONN)
 					return;
+
 				r = 0;
 			}
 
@@ -704,12 +751,66 @@  static int read_file(int fd, ssize_t count, bool rewind)
 	return len;
 }
 
-static void do_send(int sk, int fd, struct bt_iso_io_qos *out, uint32_t num,
-		    bool repeat)
+static void do_send(int sk, int fd, char *peer, bool repeat)
 {
 	uint32_t seq;
 	struct timespec t_start;
-	int len, used;
+	int send_len, used;
+	socklen_t len;
+	struct bt_iso_qos qos;
+	uint32_t num;
+	struct bt_iso_io_qos *out;
+
+	syslog(LOG_INFO, "Sending ...");
+
+	/* Read QoS */
+	if (!strcmp(peer, "00:00:00:00:00:00"))
+		out = &qos.bcast.out;
+	else
+		out = &qos.ucast.out;
+
+	memset(&qos, 0, sizeof(qos));
+	len = sizeof(qos);
+	if (getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) {
+		syslog(LOG_ERR, "Can't get Output QoS socket option: %s (%d)",
+				strerror(errno), errno);
+		out->sdu = ISO_DEFAULT_MTU;
+	}
+
+	/* num of packets = latency (ms) / interval (us) */
+	num = (out->latency * 1000 / out->interval);
+
+	syslog(LOG_INFO, "Number of packets: %d", num);
+
+	if (!sndbuf)
+		/* Use socket buffer as a jitter buffer for the entire buffer
+		 * latency:
+		 * jitter buffer = 2 * (SDU * subevents)
+		 */
+		sndbuf = 2 * ((out->latency * 1000 / out->interval) *
+							out->sdu);
+
+	len = sizeof(sndbuf);
+	if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &sndbuf, len) < 0) {
+		syslog(LOG_ERR, "Can't set socket SO_SNDBUF option: %s (%d)",
+				strerror(errno), errno);
+	}
+
+	syslog(LOG_INFO, "Socket jitter buffer: %d buffer", sndbuf);
+
+	if (sndto.tv_usec) {
+		len = sizeof(sndto);
+		if (setsockopt(sk, SOL_SOCKET, SO_SNDTIMEO, &sndto, len) < 0) {
+			syslog(LOG_ERR, "Can't set socket SO_SNDTIMEO option: "
+				"%s (%d)", strerror(errno), errno);
+		} else {
+			syslog(LOG_INFO, "Socket send timeout: %ld usec",
+							sndto.tv_usec);
+		}
+	}
+
+	for (int i = 6; i < out->sdu; i++)
+		buf[i] = 0x7f;
 
 	if (clock_gettime(CLOCK_MONOTONIC, &t_start) < 0) {
 		perror("clock_gettime");
@@ -718,17 +819,17 @@  static void do_send(int sk, int fd, struct bt_iso_io_qos *out, uint32_t num,
 
 	for (seq = 0; ; seq++) {
 		if (fd >= 0) {
-			len = read_file(fd, out->sdu, repeat);
-			if (len < 0) {
+			send_len = read_file(fd, out->sdu, repeat);
+			if (send_len < 0) {
 				syslog(LOG_ERR, "read failed: %s (%d)",
-						strerror(-len), -len);
+						strerror(-send_len), -send_len);
 				exit(1);
 			}
 		} else
-			len = out->sdu;
+			send_len = out->sdu;
 
-		len = send(sk, buf, len, 0);
-		if (len <= 0) {
+		send_len = send(sk, buf, send_len, 0);
+		if (send_len <= 0) {
 			syslog(LOG_ERR, "send failed: %s (%d)",
 						strerror(errno), errno);
 			exit(1);
@@ -739,7 +840,7 @@  static void do_send(int sk, int fd, struct bt_iso_io_qos *out, uint32_t num,
 		if (!quiet)
 			syslog(LOG_INFO,
 				"[seq %d] %d bytes buffered %d (%d bytes)",
-				seq, len, used / len, used);
+				seq, send_len, used / send_len, used);
 
 		if (seq && !((seq + 1) % num))
 			send_wait(&t_start, num * out->interval);
@@ -748,11 +849,11 @@  static void do_send(int sk, int fd, struct bt_iso_io_qos *out, uint32_t num,
 
 static void send_mode(char *filename, char *peer, int i, bool repeat)
 {
-	struct bt_iso_qos qos;
-	socklen_t len;
 	int sk, fd = -1;
-	uint32_t num;
-	struct bt_iso_io_qos *out;
+	int *sk_arr;
+	uint8_t nconn = strcmp(peer, "00:00:00:00:00:00") ? 1 : num_bis;
+
+	mgmt_set_experimental();
 
 	if (filename) {
 		char altername[PATH_MAX];
@@ -769,6 +870,33 @@  static void send_mode(char *filename, char *peer, int i, bool repeat)
 			fd = open_file(filename);
 	}
 
+	if (nconn > 1) {
+		sk_arr = bcast_do_connect_mbis(nconn, peer);
+		if (!sk_arr)
+			exit(1);
+
+		for (int i = 0; i < nconn; i++) {
+			if (fork()) {
+				/* Parent */
+				continue;
+			}
+
+			/* Child */
+			do_send(sk_arr[i], fd, peer, repeat);
+			exit(0);
+		}
+
+		/* Wait for children to exit */
+		while (wait(NULL) > 0)
+			;
+
+		for (int i = 0; i < nconn; i++)
+			close(sk_arr[i]);
+
+		free(sk_arr);
+		return;
+	}
+
 	sk = do_connect(peer);
 	if (sk < 0) {
 		syslog(LOG_ERR, "Can't connect to the server: %s (%d)",
@@ -782,62 +910,13 @@  static void send_mode(char *filename, char *peer, int i, bool repeat)
 		sleep(abs(defer_setup) - 1);
 	}
 
-	syslog(LOG_INFO, "Sending ...");
-
-	/* Read QoS */
-	if (!strcmp(peer, "00:00:00:00:00:00"))
-		out = &qos.bcast.out;
-	else
-		out = &qos.ucast.out;
-
-	memset(&qos, 0, sizeof(qos));
-	len = sizeof(qos);
-	if (getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) {
-		syslog(LOG_ERR, "Can't get Output QoS socket option: %s (%d)",
-				strerror(errno), errno);
-		out->sdu = ISO_DEFAULT_MTU;
-	}
-
-	/* num of packets = latency (ms) / interval (us) */
-	num = (out->latency * 1000 / out->interval);
-
-	syslog(LOG_INFO, "Number of packets: %d", num);
-
-	if (!sndbuf)
-		/* Use socket buffer as a jitter buffer for the entire buffer
-		 * latency:
-		 * jitter buffer = 2 * (SDU * subevents)
-		 */
-		sndbuf = 2 * ((out->latency * 1000 / out->interval) *
-							out->sdu);
-
-	len = sizeof(sndbuf);
-	if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &sndbuf, len) < 0) {
-		syslog(LOG_ERR, "Can't set socket SO_SNDBUF option: %s (%d)",
-				strerror(errno), errno);
-	}
-
-	syslog(LOG_INFO, "Socket jitter buffer: %d buffer", sndbuf);
-
-	if (sndto.tv_usec) {
-		len = sizeof(sndto);
-		if (setsockopt(sk, SOL_SOCKET, SO_SNDTIMEO, &sndto, len) < 0) {
-			syslog(LOG_ERR, "Can't set socket SO_SNDTIMEO option: "
-				"%s (%d)", strerror(errno), errno);
-		} else {
-			syslog(LOG_INFO, "Socket send timeout: %ld usec",
-							sndto.tv_usec);
-		}
-	}
-
-	for (i = 6; i < out->sdu; i++)
-		buf[i] = 0x7f;
-
-	do_send(sk, fd, out, num, repeat);
+	do_send(sk, fd, peer, repeat);
 }
 
 static void reconnect_mode(char *peer)
 {
+	mgmt_set_experimental();
+
 	while (1) {
 		int sk;
 
@@ -856,6 +935,8 @@  static void reconnect_mode(char *peer)
 
 static void multy_connect_mode(char *peer)
 {
+	mgmt_set_experimental();
+
 	while (1) {
 		int i, sk;
 
@@ -989,7 +1070,8 @@  static void usage(void)
 		"\t[-B, --preset <value>]\n"
 		"\t[-G, --CIG/BIG <value>]\n"
 		"\t[-T, --CIS/BIS <value>]\n"
-		"\t[-V, --type <value>] address type (help for list)\n");
+		"\t[-V, --type <value>] address type (help for list)\n"
+		"\t[-N, --nbis <value>] Number of BISes to create/synchronize to\n");
 }
 
 static const struct option main_options[] = {
@@ -1019,6 +1101,7 @@  static const struct option main_options[] = {
 	{ "CIG/BIG",   required_argument, NULL, 'G'},
 	{ "CIS/BIS",   required_argument, NULL, 'T'},
 	{ "type",      required_argument, NULL, 'V'},
+	{ "nbis",      required_argument, NULL, 'N'},
 	{}
 };
 
@@ -1048,6 +1131,8 @@  int main(int argc, char *argv[])
 	char *filename = NULL;
 	bool repeat = false;
 	unsigned int i;
+	uint8_t nconn = 1;
+	char *peer;
 
 	iso_qos = malloc(sizeof(*iso_qos));
 	/* Default to 16_2_1 */
@@ -1058,7 +1143,7 @@  int main(int argc, char *argv[])
 		int opt;
 
 		opt = getopt_long(argc, argv,
-			"d::cmr::s::nb:i:j:hqt:CV:W:M:S:P:F:I:L:Y:R:B:G:T:e:k:",
+			"d::cmr::s::nb:i:j:hqt:CV:W:M:S:P:F:I:L:Y:R:B:G:T:e:k:N:",
 			main_options, NULL);
 		if (opt < 0)
 			break;
@@ -1224,6 +1309,23 @@  int main(int argc, char *argv[])
 					exit(1);
 			break;
 
+		case 'N':
+			if (optarg)
+				num_bis = atoi(optarg);
+
+			if (num_bis > 1) {
+				/* If the user requested multiple BISes,
+				 * make sure that all BISes are bound
+				 * for the same BIG and advertising set
+				 */
+				if (iso_qos->bcast.big == BT_ISO_QOS_BIG_UNSET)
+					iso_qos->bcast.big = DEFAULT_BIG_ID;
+
+				if (iso_qos->bcast.bis == BT_ISO_QOS_BIS_UNSET)
+					iso_qos->bcast.bis = DEFAULT_BIS_ID;
+			}
+			break;
+
 		/* fall through */
 		default:
 			usage();
@@ -1297,10 +1399,46 @@  int main(int argc, char *argv[])
 			break;
 
 		case CONNECT:
-			sk = do_connect(argv[optind + i]);
-			if (sk < 0)
-				exit(1);
-			dump_mode(-1, sk);
+			peer = argv[optind + i];
+
+			mgmt_set_experimental();
+
+			if (!strcmp(peer, "00:00:00:00:00:00"))
+				nconn = num_bis;
+
+			if (nconn > 1) {
+				int *sk_arr =  bcast_do_connect_mbis(nconn,
+								peer);
+
+				if (!sk_arr)
+					exit(1);
+
+				for (int i = 0; i < nconn; i++) {
+					if (fork()) {
+						/* Parent */
+						continue;
+					}
+
+					/* Child */
+					dump_mode(-1, sk_arr[i]);
+					exit(0);
+				}
+
+				/* Wait for children to exit */
+				while (wait(NULL) > 0)
+					;
+
+				for (int i = 0; i < nconn; i++)
+					close(sk_arr[i]);
+
+				free(sk_arr);
+			} else {
+				sk = do_connect(argv[optind + i]);
+				if (sk < 0)
+					exit(1);
+				dump_mode(-1, sk);
+			}
+
 			break;
 
 		case RECV:
diff --git a/tools/isotest.rst b/tools/isotest.rst
index 124dc71ab..fc5b3c56f 100644
--- a/tools/isotest.rst
+++ b/tools/isotest.rst
@@ -172,6 +172,10 @@  OPTIONS
 
 -k, --bcode=<BCODE>  Socket QoS Broadcast Code
 
+-N, --nbis=<NBIS>  Number of BISes to create as part of a
+                   BIG (BIS broadcaster) or to synchronize
+                   to (BIS broadcast receiver)
+
 EXAMPLES
 ========