| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2022 Intel Corporation. |
| * Copyright 2023 NXP |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <ctype.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <getopt.h> |
| #include <syslog.h> |
| #include <signal.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/socket.h> |
| #include <sys/ioctl.h> |
| #include <sys/uio.h> |
| #include <linux/sockios.h> |
| #include <time.h> |
| #include <inttypes.h> |
| #include <sys/wait.h> |
| #include <poll.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/hci.h" |
| #include "bluetooth/hci_lib.h" |
| #include "bluetooth/mgmt.h" |
| #include "bluetooth/iso.h" |
| |
| #include "src/shared/util.h" |
| |
| #define NSEC_USEC(_t) (_t / 1000L) |
| #define SEC_USEC(_t) (_t * 1000000L) |
| #define TS_USEC(_ts) (SEC_USEC((_ts)->tv_sec) + NSEC_USEC((_ts)->tv_nsec)) |
| #define ROUND_CLOSEST(_x, _y) (((_x) + (_y / 2)) / (_y)) |
| |
| #define DEFAULT_BIG_ID 0x01 |
| #define DEFAULT_BIS_ID 0x01 |
| |
| #define MAX_DATA_SIZE 0x40000000 |
| |
| /* Test modes */ |
| enum { |
| SEND, |
| RECV, |
| RECONNECT, |
| MULTY, |
| DUMP, |
| CONNECT |
| }; |
| |
| static unsigned char *buf; |
| |
| /* Default data size */ |
| static long data_size = 251; |
| |
| static int mgmt_index = MGMT_INDEX_NONE; |
| static bdaddr_t bdaddr; |
| static int bdaddr_type = BDADDR_LE_PUBLIC; |
| |
| static int defer_setup; |
| static int sndbuf; |
| static struct timeval sndto; |
| 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; |
| }; |
| |
| static struct lookup_table bdaddr_types[] = { |
| { "le_public", BDADDR_LE_PUBLIC }, |
| { "le_random", BDADDR_LE_RANDOM }, |
| { NULL, 0 }, |
| }; |
| |
| static int get_lookup_flag(struct lookup_table *table, char *name) |
| { |
| int i; |
| |
| for (i = 0; table[i].name; i++) |
| if (!strcasecmp(table[i].name, name)) |
| return table[i].flag; |
| |
| return -1; |
| } |
| |
| static void print_lookup_values(struct lookup_table *table, char *header) |
| { |
| int i; |
| |
| printf("%s\n", header); |
| |
| for (i = 0; table[i].name; i++) |
| printf("\t%s\n", table[i].name); |
| } |
| |
| static float tv2fl(struct timeval tv) |
| { |
| return (float)tv.tv_sec + (float)(tv.tv_usec/1000000.0); |
| } |
| |
| static const uint8_t set_iso_socket_param[] = { |
| 0x3e, 0xe0, 0xb4, 0xfd, 0xdd, 0xd6, 0x85, 0x98, /* UUID - ISO Socket */ |
| 0x6a, 0x49, 0xe0, 0x05, 0x88, 0xf1, 0xba, 0x6f, |
| 0x01, /* Action - enable */ |
| }; |
| |
| static int mgmt_recv(int fd) |
| { |
| uint8_t buf[1024]; |
| |
| return read(fd, buf, sizeof(buf)); |
| } |
| |
| static int mgmt_send_cmd(int fd, uint16_t op, uint16_t id, const void *data, |
| size_t len) |
| { |
| struct mgmt_hdr hdr; |
| struct iovec iov[2]; |
| int ret; |
| |
| memset(&hdr, 0, sizeof(hdr)); |
| hdr.opcode = htobs(op); |
| hdr.index = htobs(id); |
| hdr.len = htobs(len); |
| |
| iov[0].iov_base = &hdr; |
| iov[0].iov_len = sizeof(hdr); |
| |
| iov[1].iov_base = (void *)data; |
| iov[1].iov_len = len; |
| |
| ret = writev(fd, iov, 2); |
| if (ret < 0) |
| return ret; |
| |
| /* Wait for MGMT to respond */ |
| ret = mgmt_recv(fd); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int mgmt_open(void) |
| { |
| union { |
| struct sockaddr common; |
| struct sockaddr_hci hci; |
| } addr; |
| int fd, err; |
| |
| fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, |
| BTPROTO_HCI); |
| if (fd < 0) { |
| syslog(LOG_ERR, "Can't create mgmt socket: %s (%d)", |
| strerror(errno), errno); |
| return -errno; |
| } |
| |
| syslog(LOG_ERR, "mgmt socket: fd %d", fd); |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.hci.hci_family = AF_BLUETOOTH; |
| addr.hci.hci_dev = HCI_DEV_NONE; |
| addr.hci.hci_channel = HCI_CHANNEL_CONTROL; |
| |
| if (bind(fd, &addr.common, sizeof(addr.hci)) < 0) { |
| syslog(LOG_ERR, "Can't bind mgmt socket: %s (%d)", |
| strerror(errno), errno); |
| err = -errno; |
| close(fd); |
| return err; |
| } |
| |
| return fd; |
| } |
| |
| |
| static const uint8_t set_le_param[] = { |
| 0x01, /* Action - enable */ |
| }; |
| |
| static int mgmt_set_le(int fd) |
| { |
| int err, index; |
| |
| index = mgmt_index; |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| err = mgmt_send_cmd(fd, MGMT_OP_SET_LE, index, |
| set_le_param, sizeof(set_le_param)); |
| if (err < 0) { |
| syslog(LOG_ERR, "Fail to write mgmt socket: %s (%d)", |
| strerror(errno), errno); |
| err = -errno; |
| } |
| |
| syslog(LOG_ERR, "%s: err %d", __func__, err); |
| |
| return err < 0 ? err : 0; |
| } |
| |
| static int mgmt_set_experimental(void) |
| { |
| int fd, err; |
| |
| fd = mgmt_open(); |
| if (fd < 0) |
| return fd; |
| |
| err = mgmt_set_le(fd); |
| if (err < 0) |
| goto fail; |
| |
| err = mgmt_send_cmd(fd, MGMT_OP_SET_EXP_FEATURE, MGMT_INDEX_NONE, |
| set_iso_socket_param, sizeof(set_iso_socket_param)); |
| if (err < 0) { |
| syslog(LOG_ERR, "Fail to write mgmt socket: %s (%d)", |
| strerror(errno), errno); |
| err = -errno; |
| } |
| |
| syslog(LOG_ERR, "%s: err %d", __func__, err); |
| |
| fail: |
| close(fd); |
| |
| return err < 0 ? err : 0; |
| } |
| |
| static void print_ucast_qos(int sk) |
| { |
| struct bt_iso_qos qos; |
| socklen_t len; |
| |
| /* Read Out QOS */ |
| 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 QoS socket option: %s (%d)", |
| strerror(errno), errno); |
| return; |
| } |
| |
| syslog(LOG_INFO, "QoS CIG 0x%02x CIS 0x%02x Packing 0x%02x " |
| "Framing 0x%02x]", qos.ucast.cig, qos.ucast.cis, |
| qos.ucast.packing, qos.ucast.framing); |
| |
| syslog(LOG_INFO, "Input QoS [Interval %u us Latency %u " |
| "ms SDU %u PHYs 0x%02x RTN %u]", qos.ucast.in.interval, |
| qos.ucast.in.latency, qos.ucast.in.sdu, qos.ucast.in.phys, |
| qos.ucast.in.rtn); |
| |
| syslog(LOG_INFO, "Output QoS [Interval %u us Latency %u " |
| "ms SDU %u PHYs 0x%02x RTN %u]", qos.ucast.out.interval, |
| qos.ucast.out.latency, qos.ucast.out.sdu, qos.ucast.out.phys, |
| qos.ucast.out.rtn); |
| } |
| |
| static void print_bcast_qos(int sk) |
| { |
| struct bt_iso_qos qos; |
| socklen_t len; |
| |
| /* Read Out QOS */ |
| 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 QoS socket option: %s (%d)", |
| strerror(errno), errno); |
| return; |
| } |
| |
| syslog(LOG_INFO, "QoS [BIG 0x%02x BIS 0x%02x Packing 0x%02x " |
| "Framing 0x%02x Encryption 0x%02x]", qos.bcast.big, |
| qos.bcast.bis, qos.bcast.packing, qos.bcast.framing, |
| qos.bcast.encryption); |
| |
| if (qos.bcast.encryption == 0x01) |
| syslog(LOG_INFO, "Broadcast Code 0x%02x 0x%02x 0x%02x 0x%02x " |
| "0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x " |
| "0x%02x 0x%02x 0x%02x 0x%02x", qos.bcast.bcode[0], |
| qos.bcast.bcode[1], qos.bcast.bcode[2], qos.bcast.bcode[3], |
| qos.bcast.bcode[4], qos.bcast.bcode[5], qos.bcast.bcode[6], |
| qos.bcast.bcode[7], qos.bcast.bcode[8], qos.bcast.bcode[9], |
| qos.bcast.bcode[10], qos.bcast.bcode[11], qos.bcast.bcode[12], |
| qos.bcast.bcode[13], qos.bcast.bcode[14], qos.bcast.bcode[15]); |
| |
| syslog(LOG_INFO, "Input QoS [Interval %u us Latency %u " |
| "ms SDU %u PHYs 0x%02x RTN %u]", qos.bcast.in.interval, |
| qos.bcast.in.latency, qos.bcast.in.sdu, |
| qos.bcast.in.phys, qos.bcast.in.rtn); |
| |
| syslog(LOG_INFO, "Output QoS [Interval %u us Latency %u " |
| "ms SDU %u PHYs 0x%02x RTN %u]", qos.bcast.out.interval, |
| qos.bcast.out.latency, qos.bcast.out.sdu, |
| qos.bcast.out.phys, qos.bcast.out.rtn); |
| } |
| |
| static int do_connect(char *peer) |
| { |
| struct sockaddr_iso addr; |
| int sk; |
| |
| /* Create socket */ |
| sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); |
| if (sk < 0) { |
| syslog(LOG_ERR, "Can't create socket: %s (%d)", |
| strerror(errno), errno); |
| return -1; |
| } |
| |
| /* Bind to local address */ |
| memset(&addr, 0, sizeof(addr)); |
| addr.iso_family = AF_BLUETOOTH; |
| bacpy(&addr.iso_bdaddr, mgmt_index != MGMT_INDEX_NONE ? |
| &bdaddr : BDADDR_ANY); |
| addr.iso_bdaddr_type = BDADDR_LE_PUBLIC; |
| |
| if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| syslog(LOG_ERR, "Can't bind socket: %s (%d)", |
| strerror(errno), errno); |
| goto error; |
| } |
| |
| /* Set QoS if available */ |
| if (iso_qos) { |
| if (!inout || !strcmp(peer, "00:00:00:00:00:00")) { |
| iso_qos->ucast.in.phys = 0x00; |
| iso_qos->ucast.in.sdu = 0; |
| } |
| |
| if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, iso_qos, |
| sizeof(*iso_qos)) < 0) { |
| syslog(LOG_ERR, "Can't set QoS socket option: " |
| "%s (%d)", strerror(errno), errno); |
| goto error; |
| } |
| } |
| |
| /* Enable deferred setup */ |
| if (defer_setup && setsockopt(sk, SOL_BLUETOOTH, BT_DEFER_SETUP, |
| &defer_setup, sizeof(defer_setup)) < 0) { |
| syslog(LOG_ERR, "Can't enable deferred setup : %s (%d)", |
| strerror(errno), errno); |
| goto error; |
| } |
| |
| /* Connect to remote device */ |
| memset(&addr, 0, sizeof(addr)); |
| addr.iso_family = AF_BLUETOOTH; |
| str2ba(peer, &addr.iso_bdaddr); |
| addr.iso_bdaddr_type = bdaddr_type; |
| |
| syslog(LOG_INFO, "Connecting %s ...", peer); |
| |
| if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| syslog(LOG_ERR, "Can't connect: %s (%d)", strerror(errno), |
| errno); |
| goto error; |
| } |
| |
| syslog(LOG_INFO, "Connected [%s]", peer); |
| |
| if (!strcmp(peer, "00:00:00:00:00:00")) |
| print_bcast_qos(sk); |
| else |
| print_ucast_qos(sk); |
| |
| return sk; |
| |
| error: |
| close(sk); |
| 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 int accept_conn(int sk, struct sockaddr_iso *addr, char *peer) |
| { |
| socklen_t optlen; |
| int nsk, err, sk_err; |
| struct pollfd fds; |
| socklen_t len; |
| |
| memset(addr, 0, sizeof(*addr) + sizeof(*addr->iso_bc)); |
| optlen = sizeof(*addr); |
| |
| if (peer) |
| optlen += sizeof(*addr->iso_bc); |
| |
| nsk = accept(sk, (struct sockaddr *) addr, &optlen); |
| if (nsk < 0) { |
| syslog(LOG_ERR, "Accept failed: %s (%d)", |
| strerror(errno), errno); |
| return -1; |
| } |
| |
| /* Check if connection was successful */ |
| memset(&fds, 0, sizeof(fds)); |
| fds.fd = nsk; |
| fds.events = POLLERR; |
| |
| if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLERR)) { |
| len = sizeof(sk_err); |
| |
| if (getsockopt(nsk, SOL_SOCKET, SO_ERROR, |
| &sk_err, &len) < 0) |
| err = -errno; |
| else |
| err = -sk_err; |
| |
| if (err < 0) |
| syslog(LOG_ERR, "Connection failed: %s (%d)", |
| strerror(-err), -err); |
| |
| close(nsk); |
| return -1; |
| } |
| |
| return nsk; |
| } |
| |
| static void do_listen(char *filename, |
| void (*handler)(int fd, int sk, char *peer), |
| char *peer) |
| { |
| struct sockaddr_iso *addr = NULL; |
| socklen_t optlen; |
| int sk, nsk, fd = -1; |
| char ba[18]; |
| int read_len; |
| |
| if (filename) { |
| fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644); |
| if (fd < 0) { |
| syslog(LOG_ERR, "Can't open file %s: %s\n", |
| filename, strerror(errno)); |
| exit(1); |
| } |
| } |
| |
| mgmt_set_experimental(); |
| |
| /* Create socket */ |
| sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); |
| if (sk < 0) { |
| syslog(LOG_ERR, "Can't create socket: %s (%d)", |
| strerror(errno), errno); |
| if (fd >= 0) |
| close(fd); |
| exit(1); |
| } |
| |
| /* Bind to local address */ |
| addr = malloc(sizeof(*addr) + sizeof(*addr->iso_bc)); |
| memset(addr, 0, sizeof(*addr) + sizeof(*addr->iso_bc)); |
| addr->iso_family = AF_BLUETOOTH; |
| bacpy(&addr->iso_bdaddr, mgmt_index != MGMT_INDEX_NONE ? |
| &bdaddr : BDADDR_ANY); |
| addr->iso_bdaddr_type = BDADDR_LE_PUBLIC; |
| optlen = sizeof(*addr); |
| |
| if (peer) { |
| str2ba(peer, &addr->iso_bc->bc_bdaddr); |
| addr->iso_bc->bc_bdaddr_type = bdaddr_type; |
| 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); |
| } |
| |
| if (bind(sk, (struct sockaddr *) addr, optlen) < 0) { |
| syslog(LOG_ERR, "Can't bind socket: %s (%d)", |
| strerror(errno), errno); |
| goto error; |
| } |
| |
| /* Enable deferred setup */ |
| if (defer_setup && setsockopt(sk, SOL_BLUETOOTH, BT_DEFER_SETUP, |
| &defer_setup, sizeof(defer_setup)) < 0) { |
| syslog(LOG_ERR, "Can't enable deferred setup : %s (%d)", |
| strerror(errno), errno); |
| goto error; |
| } |
| |
| /* Set QoS if available */ |
| if (iso_qos) { |
| if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, iso_qos, |
| sizeof(*iso_qos)) < 0) { |
| syslog(LOG_ERR, "Can't set QoS socket option: " |
| "%s (%d)", strerror(errno), errno); |
| goto error; |
| } |
| } |
| |
| /* Listen for connections */ |
| if (listen(sk, 10)) { |
| syslog(LOG_ERR, "Can not listen on the socket: %s (%d)", |
| strerror(errno), errno); |
| goto error; |
| } |
| |
| syslog(LOG_INFO, "Waiting for connection %s...", peer ? peer : ""); |
| |
| /* Handle deferred setup */ |
| if (defer_setup && peer) { |
| nsk = accept_conn(sk, addr, peer); |
| if (nsk < 0) |
| goto error; |
| |
| close(sk); |
| sk = nsk; |
| |
| read_len = read(sk, buf, data_size); |
| if (read_len < 0) |
| syslog(LOG_ERR, "Initial read error: %s (%d)", |
| strerror(errno), errno); |
| else |
| syslog(LOG_INFO, "Initial bytes %d", read_len); |
| } |
| |
| while (1) { |
| nsk = accept_conn(sk, addr, peer); |
| if (nsk < 0) |
| continue; |
| |
| if (fork()) { |
| /* Parent */ |
| close(nsk); |
| continue; |
| } |
| /* Child */ |
| close(sk); |
| |
| ba2str(&addr->iso_bdaddr, ba); |
| syslog(LOG_INFO, "Connected [%s]", ba); |
| |
| if (peer) |
| print_bcast_qos(nsk); |
| else |
| print_ucast_qos(nsk); |
| |
| /* Handle deferred setup */ |
| if (defer_setup) { |
| syslog(LOG_INFO, "Waiting for %d seconds", |
| abs(defer_setup) - 1); |
| sleep(abs(defer_setup) - 1); |
| |
| if (defer_setup < 0) { |
| close(nsk); |
| exit(1); |
| } |
| } |
| |
| handler(fd, nsk, peer); |
| |
| syslog(LOG_INFO, "Disconnect"); |
| exit(0); |
| } |
| |
| error: |
| free(addr); |
| |
| if (fd >= 0) |
| close(fd); |
| close(sk); |
| exit(1); |
| } |
| |
| static void dump_mode(int fd, int sk, char *peer) |
| { |
| int len; |
| |
| if (defer_setup && !peer) { |
| len = read(sk, buf, data_size); |
| if (len < 0) |
| syslog(LOG_ERR, "Initial read error: %s (%d)", |
| strerror(errno), errno); |
| else |
| syslog(LOG_INFO, "Initial bytes %d", len); |
| } |
| |
| syslog(LOG_INFO, "Receiving ..."); |
| while ((len = read(sk, buf, data_size)) >= 0) { |
| if (fd >= 0) { |
| len = write(fd, buf, len); |
| if (len < 0) { |
| syslog(LOG_ERR, "Write failed: %s (%d)", |
| strerror(errno), errno); |
| return; |
| } |
| } else if (!quiet) |
| syslog(LOG_INFO, "Received %d bytes", len); |
| } |
| } |
| |
| static void recv_mode(int fd, int sk, char *peer) |
| { |
| struct timeval tv_beg, tv_end, tv_diff; |
| long total; |
| int len; |
| uint32_t seq; |
| |
| if (defer_setup && !peer) { |
| len = read(sk, buf, data_size); |
| if (len < 0) |
| syslog(LOG_ERR, "Initial read error: %s (%d)", |
| strerror(errno), errno); |
| else |
| syslog(LOG_INFO, "Initial bytes %d", len); |
| } |
| |
| syslog(LOG_INFO, "Receiving ..."); |
| |
| for (seq = 0; ; seq++) { |
| gettimeofday(&tv_beg, NULL); |
| total = 0; |
| while (total < data_size) { |
| int r; |
| |
| r = recv(sk, buf, data_size, 0); |
| if (r < 0) { |
| syslog(LOG_ERR, "Read failed: %s (%d)", |
| strerror(errno), errno); |
| |
| if (errno != ENOTCONN) |
| return; |
| |
| r = 0; |
| } |
| |
| if (fd >= 0) { |
| r = write(fd, buf, r); |
| if (r < 0) { |
| syslog(LOG_ERR, "Write failed: %s (%d)", |
| strerror(errno), errno); |
| return; |
| } |
| } |
| |
| total += r; |
| } |
| gettimeofday(&tv_end, NULL); |
| |
| timersub(&tv_end, &tv_beg, &tv_diff); |
| |
| if (!quiet) |
| syslog(LOG_INFO, |
| "[seq %d] %ld bytes in %.2f sec speed %.2f " |
| "kb/s", seq, total, tv2fl(tv_diff), |
| (float)(total * 8 / tv2fl(tv_diff)) / 1024.0); |
| } |
| } |
| |
| static int open_file(const char *filename) |
| { |
| int fd = -1; |
| |
| syslog(LOG_INFO, "Opening %s ...", filename); |
| |
| fd = open(filename, O_RDONLY); |
| if (fd < 0) { |
| syslog(LOG_ERR, "Can't open file %s: %s\n", |
| filename, strerror(errno)); |
| } |
| |
| return fd; |
| } |
| |
| static void send_wait(struct timespec *t_start, uint32_t us) |
| { |
| struct timespec t_now; |
| struct timespec t_diff; |
| int64_t delta_us; |
| |
| /* Skip sleep at start */ |
| if (!us) |
| return; |
| |
| if (clock_gettime(CLOCK_MONOTONIC, &t_now) < 0) { |
| perror("clock_gettime"); |
| exit(EXIT_FAILURE); |
| } |
| |
| t_diff.tv_sec = t_now.tv_sec - t_start->tv_sec; |
| if (t_start->tv_nsec > t_now.tv_nsec) { |
| t_diff.tv_sec--; |
| t_now.tv_nsec += 1000000000L; |
| } |
| t_diff.tv_nsec = t_now.tv_nsec - t_start->tv_nsec; |
| |
| delta_us = us - TS_USEC(&t_diff); |
| |
| if (delta_us < 0) { |
| syslog(LOG_INFO, "Send is behind: %" PRId64 " us", delta_us); |
| delta_us = 1000; |
| } |
| |
| if (!quiet) |
| syslog(LOG_INFO, "Waiting (%" PRId64 " us)...", delta_us); |
| |
| usleep(delta_us); |
| |
| if (clock_gettime(CLOCK_MONOTONIC, t_start) < 0) { |
| perror("clock_gettime"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static int read_stream(int fd, ssize_t count) |
| { |
| ssize_t len, ret = 0; |
| |
| while (ret < count) { |
| len = read(fd, buf + ret, count - ret); |
| if (len < 0) |
| return -errno; |
| |
| ret += len; |
| usleep(1000); |
| } |
| |
| return ret; |
| } |
| |
| static int read_file(int fd, ssize_t count, bool rewind) |
| { |
| ssize_t len; |
| |
| if (fd == STDIN_FILENO) |
| return read_stream(fd, count); |
| |
| len = read(fd, buf, count); |
| if (len <= 0) { |
| if (!len) { |
| if (rewind) { |
| lseek(fd, 0, SEEK_SET); |
| return read_file(fd, count, rewind); |
| } |
| return len; |
| } |
| |
| return -errno; |
| } |
| |
| return len; |
| } |
| |
| static void do_send(int sk, int fd, char *peer, bool repeat) |
| { |
| uint32_t seq; |
| struct timespec t_start; |
| 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 = ROUND_CLOSEST(out->latency * 1000, out->interval); |
| if (!num) |
| num = 1; |
| |
| 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 * (num * 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"); |
| exit(EXIT_FAILURE); |
| } |
| |
| for (seq = 0; ; seq++) { |
| if (fd >= 0) { |
| send_len = read_file(fd, out->sdu, repeat); |
| if (send_len < 0) { |
| syslog(LOG_ERR, "read failed: %s (%d)", |
| strerror(-send_len), -send_len); |
| exit(1); |
| } |
| } else |
| send_len = out->sdu; |
| |
| send_len = send(sk, buf, send_len, 0); |
| if (send_len <= 0) { |
| syslog(LOG_ERR, "send failed: %s (%d)", |
| strerror(errno), errno); |
| exit(1); |
| } |
| |
| ioctl(sk, TIOCOUTQ, &used); |
| |
| if (!quiet) |
| syslog(LOG_INFO, |
| "[seq %d] %d bytes buffered %d (%d bytes)", |
| seq, send_len, used / send_len, used); |
| |
| if (seq && !((seq + 1) % num)) |
| send_wait(&t_start, num * out->interval); |
| } |
| } |
| |
| static void send_mode(char *filename, char *peer, int i, bool repeat) |
| { |
| int sk, fd = -1; |
| 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]; |
| struct stat st; |
| int err; |
| |
| snprintf(altername, PATH_MAX, "%s.%u", filename, i); |
| |
| err = stat(altername, &st); |
| if (!err) |
| fd = open_file(altername); |
| |
| if (fd < 0) |
| 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); |
| if (fd >= 0) |
| close(fd); |
| return; |
| } |
| |
| sk = do_connect(peer); |
| if (sk < 0) { |
| syslog(LOG_ERR, "Can't connect to the server: %s (%d)", |
| strerror(errno), errno); |
| exit(1); |
| } |
| |
| if (defer_setup) { |
| syslog(LOG_INFO, "Waiting for %d seconds", |
| abs(defer_setup) - 1); |
| sleep(abs(defer_setup) - 1); |
| } |
| |
| do_send(sk, fd, peer, repeat); |
| } |
| |
| static void reconnect_mode(char *peer) |
| { |
| mgmt_set_experimental(); |
| |
| while (1) { |
| int sk; |
| |
| sk = do_connect(peer); |
| if (sk < 0) { |
| syslog(LOG_ERR, "Can't connect to the server: %s (%d)", |
| strerror(errno), errno); |
| exit(1); |
| } |
| |
| close(sk); |
| |
| sleep(5); |
| } |
| } |
| |
| static void multy_connect_mode(char *peer) |
| { |
| mgmt_set_experimental(); |
| |
| while (1) { |
| int i, sk; |
| |
| for (i = 0; i < 10; i++) { |
| if (fork()) |
| continue; |
| |
| /* Child */ |
| sk = do_connect(peer); |
| if (sk < 0) { |
| syslog(LOG_ERR, "Can't connect to the server: " |
| "%s (%d)", strerror(errno), errno); |
| } |
| close(sk); |
| exit(0); |
| } |
| |
| sleep(19); |
| } |
| } |
| |
| #define QOS_IO(_interval, _latency, _sdu, _phys, _rtn) \ |
| { \ |
| .interval = _interval, \ |
| .latency = _latency, \ |
| .sdu = _sdu, \ |
| .phys = _phys, \ |
| .rtn = _rtn, \ |
| } |
| |
| #define QOS(_interval, _latency, _sdu, _phy, _rtn) \ |
| { \ |
| .bcast = { \ |
| .big = BT_ISO_QOS_BIG_UNSET, \ |
| .bis = BT_ISO_QOS_BIS_UNSET, \ |
| .sync_factor = 0x07, \ |
| .packing = 0x00, \ |
| .framing = 0x00, \ |
| .out = QOS_IO(_interval, _latency, _sdu, _phy, _rtn), \ |
| .encryption = 0x00, \ |
| .bcode = {0}, \ |
| .options = 0x00, \ |
| .skip = 0x0000, \ |
| .sync_timeout = 0x4000, \ |
| .sync_cte_type = 0x00, \ |
| .mse = 0x00, \ |
| .timeout = 0x4000, \ |
| }, \ |
| } |
| |
| #define QOS_PRESET(_name, _inout, _interval, _latency, _sdu, _phy, _rtn) \ |
| { \ |
| .name = _name, \ |
| .inout = _inout, \ |
| .qos = QOS(_interval, _latency, _sdu, _phy, _rtn), \ |
| } |
| |
| static struct qos_preset { |
| const char *name; |
| bool inout; |
| struct bt_iso_qos qos; |
| } presets[] = { |
| /* QoS Configuration settings for low latency audio data */ |
| QOS_PRESET("8_1_1", true, 7500, 8, 26, 0x02, 2), |
| QOS_PRESET("8_2_1", true, 10000, 10, 30, 0x02, 2), |
| QOS_PRESET("16_1_1", true, 7500, 8, 30, 0x02, 2), |
| QOS_PRESET("16_2_1", true, 10000, 10, 40, 0x02, 2), |
| QOS_PRESET("24_1_1", true, 7500, 8, 45, 0x02, 2), |
| QOS_PRESET("24_2_1", true, 10000, 10, 60, 0x02, 2), |
| QOS_PRESET("32_1_1", true, 7500, 8, 60, 0x02, 2), |
| QOS_PRESET("32_2_1", true, 10000, 10, 80, 0x02, 2), |
| QOS_PRESET("44_1_1", false, 8163, 24, 98, 0x02, 5), |
| QOS_PRESET("44_2_1", false, 10884, 31, 130, 0x02, 5), |
| QOS_PRESET("48_1_1", false, 7500, 15, 75, 0x02, 5), |
| QOS_PRESET("48_2_1", false, 10000, 20, 100, 0x02, 5), |
| QOS_PRESET("48_3_1", false, 7500, 15, 90, 0x02, 5), |
| QOS_PRESET("48_4_1", false, 10000, 20, 120, 0x02, 5), |
| QOS_PRESET("48_5_1", false, 7500, 15, 117, 0x02, 5), |
| QOS_PRESET("44_6_1", false, 10000, 20, 155, 0x02, 5), |
| /* QoS Configuration settings for high reliability audio data */ |
| QOS_PRESET("8_1_2", true, 7500, 75, 26, 0x02, 13), |
| QOS_PRESET("8_2_2", true, 10000, 95, 30, 0x02, 13), |
| QOS_PRESET("16_1_2", true, 7500, 75, 30, 0x02, 13), |
| QOS_PRESET("16_2_2", true, 10000, 95, 40, 0x02, 13), |
| QOS_PRESET("24_1_2", true, 7500, 75, 45, 0x02, 13), |
| QOS_PRESET("24_2_2", true, 10000, 95, 60, 0x02, 13), |
| QOS_PRESET("32_1_2", true, 7500, 75, 60, 0x02, 13), |
| QOS_PRESET("32_2_2", true, 10000, 95, 80, 0x02, 13), |
| QOS_PRESET("44_1_2", false, 8163, 80, 97, 0x02, 13), |
| QOS_PRESET("44_2_2", false, 10884, 85, 130, 0x02, 13), |
| QOS_PRESET("48_1_2", false, 7500, 75, 75, 0x02, 13), |
| QOS_PRESET("48_2_2", false, 10000, 95, 100, 0x02, 13), |
| QOS_PRESET("48_3_2", false, 7500, 75, 90, 0x02, 13), |
| QOS_PRESET("48_4_2", false, 10000, 100, 120, 0x02, 13), |
| QOS_PRESET("48_5_2", false, 7500, 75, 117, 0x02, 13), |
| QOS_PRESET("44_6_2", false, 10000, 100, 155, 0x02, 13), |
| /* QoS configuration support setting requirements for the UGG and UGT */ |
| QOS_PRESET("16_1_gs", true, 7500, 15, 30, 0x02, 1), |
| QOS_PRESET("16_2_gs", true, 10000, 20, 40, 0x02, 1), |
| QOS_PRESET("32_1_gs", true, 7500, 15, 60, 0x02, 1), |
| QOS_PRESET("32_2_gs", true, 10000, 20, 80, 0x02, 1), |
| QOS_PRESET("48_1_gs", true, 7500, 15, 75, 0x02, 1), |
| QOS_PRESET("48_2_gs", true, 10000, 20, 100, 0x02, 1), |
| QOS_PRESET("32_1_gr", true, 7500, 15, 60, 0x02, 1), |
| QOS_PRESET("32_2_gr", true, 10000, 20, 80, 0x02, 1), |
| QOS_PRESET("48_1_gr", true, 7500, 15, 75, 0x02, 1), |
| QOS_PRESET("48_2_gr", true, 10000, 20, 100, 0x02, 1), |
| QOS_PRESET("48_3_gr", true, 7500, 15, 90, 0x02, 1), |
| QOS_PRESET("48_4_gr", true, 10000, 20, 120, 0x02, 1), |
| }; |
| |
| #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) |
| |
| static void usage(void) |
| { |
| printf("isotest - ISO testing\n" |
| "Usage:\n"); |
| printf("\tisotest <mode> [options] [bdaddr] [bdaddr1]...\n"); |
| printf("Modes:\n" |
| "\t-d, --dump [filename] dump (server)\n" |
| "\t-c, --reconnect reconnect (client)\n" |
| "\t-m, --multiple multiple connects (client)\n" |
| "\t-r, --receive [filename] receive (server)\n" |
| "\t-s, --send [filename,...] connect and send " |
| "(client/broadcaster)\n" |
| "\t-n, --silent connect and be silent (client)\n" |
| "Options:\n" |
| "\t[-b, --bytes <value>]\n" |
| "\t[-i, --device <num>]\n" |
| "\t[-j, --jitter <bytes> socket/jitter buffer]\n" |
| "\t[-h, --help]\n" |
| "\t[-q, --quiet disable packet logging]\n" |
| "\t[-t, --timeout <usec> send timeout]\n" |
| "\t[-C, --continue]\n" |
| "\t[-W, --defer <seconds>] enable deferred setup\n" |
| "\t[-M, --mtu <value>]\n" |
| "\t[-S, --sca/adv-interval <value>]\n" |
| "\t[-P, --packing <value>]\n" |
| "\t[-F, --framing <value>]\n" |
| "\t[-I, --interval <useconds>]\n" |
| "\t[-L, --latency <mseconds>]\n" |
| "\t[-Y, --phy <value>]\n" |
| "\t[-R, --rtn <value>]\n" |
| "\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[-N, --nbis <value>] Number of BISes to create/synchronize to\n"); |
| } |
| |
| static const struct option main_options[] = { |
| { "dump", optional_argument, NULL, 'd'}, |
| { "reconnect", no_argument, NULL, 'c'}, |
| { "multiple", no_argument, NULL, 'm'}, |
| { "receive", optional_argument, NULL, 'r'}, |
| { "send", optional_argument, NULL, 's'}, |
| { "silent", no_argument, NULL, 'n'}, |
| { "bytes", required_argument, NULL, 'b'}, |
| { "index", required_argument, NULL, 'i'}, |
| { "jitter", required_argument, NULL, 'j'}, |
| { "help", no_argument, NULL, 'h'}, |
| { "quiet", no_argument, NULL, 'q'}, |
| { "timeout", required_argument, NULL, 't'}, |
| { "continue", no_argument, NULL, 'C'}, |
| { "defer", required_argument, NULL, 'W'}, |
| { "mtu", required_argument, NULL, 'M'}, |
| { "sca", required_argument, NULL, 'S'}, |
| { "packing", required_argument, NULL, 'P'}, |
| { "framing", required_argument, NULL, 'F'}, |
| { "interval", required_argument, NULL, 'I'}, |
| { "latency", required_argument, NULL, 'L'}, |
| { "phy", required_argument, NULL, 'Y'}, |
| { "rtn", required_argument, NULL, 'R'}, |
| { "preset", required_argument, NULL, 'B'}, |
| { "CIG/BIG", required_argument, NULL, 'G'}, |
| { "CIS/BIS", required_argument, NULL, 'T'}, |
| { "type", required_argument, NULL, 'V'}, |
| { "nbis", required_argument, NULL, 'N'}, |
| {} |
| }; |
| |
| static bool str2hex(const char *str, uint16_t in_len, uint8_t *out, |
| uint16_t out_len) |
| { |
| uint16_t i; |
| |
| if (in_len < out_len * 2) |
| return false; |
| |
| if (!strncasecmp(str, "0x", 2)) |
| str += 2; |
| |
| for (i = 0; i < out_len; i++) { |
| if (sscanf(&str[i * 2], "%02hhx", &out[i]) != 1) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct sigaction sa; |
| int sk, mode = RECV; |
| 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 */ |
| *iso_qos = presets[3].qos; |
| inout = true; |
| |
| while (1) { |
| 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:N:", |
| main_options, NULL); |
| if (opt < 0) |
| break; |
| |
| switch (opt) { |
| case 'r': |
| mode = RECV; |
| if (optarg) |
| filename = strdup(optarg); |
| break; |
| |
| case 's': |
| mode = SEND; |
| if (optarg) |
| filename = strdup(optarg); |
| break; |
| |
| case 'd': |
| mode = DUMP; |
| if (optarg) |
| filename = strdup(optarg); |
| break; |
| |
| case 'c': |
| mode = RECONNECT; |
| break; |
| |
| case 'm': |
| mode = MULTY; |
| break; |
| |
| case 'n': |
| mode = CONNECT; |
| break; |
| |
| case 'b': |
| if (optarg && atoi(optarg) < MAX_DATA_SIZE) |
| data_size = atoi(optarg); |
| break; |
| |
| case 'i': |
| if (!optarg) |
| break; |
| |
| if (!strncasecmp(optarg, "hci", 3)) { |
| mgmt_index = atoi(optarg + 3); |
| hci_devba(mgmt_index, &bdaddr); |
| } else |
| str2ba(optarg, &bdaddr); |
| break; |
| |
| case 'j': |
| if (optarg) |
| sndbuf = atoi(optarg); |
| break; |
| |
| case 'q': |
| quiet = true; |
| break; |
| |
| case 't': |
| if (optarg) |
| sndto.tv_usec = atoi(optarg); |
| break; |
| |
| case 'C': |
| repeat = true; |
| break; |
| |
| case 'V': |
| if (optarg) |
| bdaddr_type = get_lookup_flag(bdaddr_types, |
| optarg); |
| |
| if (bdaddr_type == -1) { |
| print_lookup_values(bdaddr_types, |
| "List Address types:"); |
| exit(1); |
| } |
| |
| break; |
| |
| case 'W': |
| if (optarg) |
| defer_setup = atoi(optarg); |
| break; |
| |
| case 'M': |
| if (optarg) |
| iso_qos->ucast.out.sdu = atoi(optarg); |
| break; |
| |
| case 'S': |
| if (optarg) |
| iso_qos->ucast.sca = atoi(optarg); |
| break; |
| |
| |
| case 'P': |
| if (optarg) |
| iso_qos->ucast.packing = atoi(optarg); |
| break; |
| |
| case 'F': |
| if (optarg) |
| iso_qos->ucast.framing = atoi(optarg); |
| break; |
| |
| case 'I': |
| if (optarg) |
| iso_qos->ucast.out.interval = atoi(optarg); |
| break; |
| |
| case 'L': |
| if (optarg) |
| iso_qos->ucast.out.latency = atoi(optarg); |
| break; |
| |
| case 'Y': |
| if (optarg) |
| iso_qos->ucast.out.phys = atoi(optarg); |
| break; |
| |
| case 'R': |
| if (optarg) |
| iso_qos->ucast.out.rtn = atoi(optarg); |
| break; |
| |
| case 'B': |
| if (!optarg) |
| break; |
| |
| for (i = 0; i < ARRAY_SIZE(presets); i++) { |
| if (!strcmp(presets[i].name, optarg)) { |
| *iso_qos = presets[i].qos; |
| inout = presets[i].inout; |
| break; |
| } |
| } |
| |
| break; |
| |
| case 'G': |
| if (optarg) |
| iso_qos->ucast.cig = atoi(optarg); |
| break; |
| |
| case 'T': |
| if (optarg) |
| iso_qos->ucast.cis = atoi(optarg); |
| break; |
| |
| case 'e': |
| if (optarg) |
| iso_qos->bcast.encryption = |
| strtol(optarg, NULL, 16); |
| break; |
| |
| case 'k': |
| if (optarg) |
| if (!str2hex(optarg, strlen(optarg), |
| iso_qos->bcast.bcode, 16)) |
| 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(); |
| exit(1); |
| } |
| } |
| |
| if (inout) { |
| iso_qos->ucast.in = iso_qos->ucast.out; |
| } else { |
| /* Align interval and latency even if is unidirectional */ |
| iso_qos->ucast.in.interval = iso_qos->ucast.out.interval; |
| iso_qos->ucast.in.latency = iso_qos->ucast.out.latency; |
| } |
| |
| buf = malloc(data_size); |
| if (!buf) { |
| perror("Can't allocate data buffer"); |
| exit(1); |
| } |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_handler = SIG_IGN; |
| sa.sa_flags = SA_NOCLDSTOP; |
| sigaction(SIGCHLD, &sa, NULL); |
| |
| openlog("isotest", LOG_PERROR | LOG_PID, LOG_LOCAL0); |
| |
| if (!(argc - optind)) { |
| switch (mode) { |
| case RECV: |
| do_listen(filename, recv_mode, NULL); |
| goto done; |
| |
| case DUMP: |
| do_listen(filename, dump_mode, NULL); |
| goto done; |
| default: |
| usage(); |
| exit(1); |
| } |
| } |
| |
| argc -= optind; |
| |
| for (i = 0; i < (unsigned int) argc; i++) { |
| pid_t pid; |
| |
| pid = fork(); |
| if (pid < 0) { |
| perror("Failed to fork new process"); |
| exit(1); |
| } |
| |
| if (!pid) |
| continue; |
| |
| switch (mode) { |
| case SEND: |
| peer = argv[optind + i]; |
| if (bachk(peer) < 0) { |
| fprintf(stderr, "Invalid peer address '%s'\n", |
| peer); |
| exit(1); |
| } |
| send_mode(filename, peer, i, repeat); |
| if (filename && strchr(filename, ',')) { |
| char *tmp = filename; |
| filename = strdup(strchr(filename, ',') + 1); |
| free(tmp); |
| } |
| break; |
| |
| case RECONNECT: |
| reconnect_mode(argv[optind + i]); |
| break; |
| |
| case MULTY: |
| multy_connect_mode(argv[optind + i]); |
| break; |
| |
| case CONNECT: |
| peer = argv[optind + i]; |
| if (bachk(peer) < 0) { |
| fprintf(stderr, "Invalid peer address '%s'\n", |
| peer); |
| exit(1); |
| } |
| |
| 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 */ |
| if (!strcmp(peer, "00:00:00:00:00:00")) |
| dump_mode(-1, sk_arr[i], peer); |
| else |
| dump_mode(-1, sk_arr[i], NULL); |
| |
| 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(peer); |
| if (sk < 0) |
| exit(1); |
| |
| if (!strcmp(peer, "00:00:00:00:00:00")) |
| dump_mode(-1, sk, peer); |
| else |
| dump_mode(-1, sk, NULL); |
| } |
| |
| break; |
| |
| case RECV: |
| do_listen(filename, recv_mode, argv[optind + i]); |
| break; |
| |
| case DUMP: |
| do_listen(filename, dump_mode, argv[optind + i]); |
| break; |
| } |
| |
| break; |
| } |
| |
| done: |
| free(filename); |
| |
| syslog(LOG_INFO, "Exit"); |
| |
| closelog(); |
| |
| return 0; |
| } |