| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2011-2012 Intel Corporation |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <ctype.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <alloca.h> |
| #include <getopt.h> |
| #include <stdbool.h> |
| #include <termios.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #include <netdb.h> |
| #include <arpa/inet.h> |
| |
| #include "src/shared/util.h" |
| #include "src/shared/mainloop.h" |
| #include "src/shared/ecc.h" |
| #include "monitor/bt.h" |
| |
| #define HCI_PRIMARY 0x00 |
| #define HCI_AMP 0x01 |
| |
| #define BTPROTO_HCI 1 |
| struct sockaddr_hci { |
| sa_family_t hci_family; |
| unsigned short hci_dev; |
| unsigned short hci_channel; |
| }; |
| #define HCI_CHANNEL_USER 1 |
| |
| static uint16_t hci_index = 0; |
| static bool client_active = false; |
| static bool debug_enabled = false; |
| static bool emulate_ecc = false; |
| static bool skip_first_zero = false; |
| |
| static void hexdump_print(const char *str, void *user_data) |
| { |
| printf("%s%s\n", (char *) user_data, str); |
| } |
| |
| struct proxy { |
| /* Receive commands, ACL and SCO data */ |
| int host_fd; |
| uint8_t host_buf[4096]; |
| uint16_t host_len; |
| bool host_shutdown; |
| bool host_skip_first_zero; |
| |
| /* Receive events, ACL and SCO data */ |
| int dev_fd; |
| uint8_t dev_buf[4096]; |
| uint16_t dev_len; |
| bool dev_shutdown; |
| |
| /* ECC emulation */ |
| uint8_t event_mask[8]; |
| uint8_t local_sk256[32]; |
| }; |
| |
| static bool write_packet(int fd, const void *data, size_t size, |
| void *user_data) |
| { |
| while (size > 0) { |
| ssize_t written; |
| |
| written = write(fd, data, size); |
| if (written < 0) { |
| if (errno == EAGAIN || errno == EINTR) |
| continue; |
| return false; |
| } |
| |
| if (debug_enabled) |
| util_hexdump('<', data, written, hexdump_print, |
| user_data); |
| |
| data += written; |
| size -= written; |
| } |
| |
| return true; |
| } |
| |
| static void host_write_packet(struct proxy *proxy, void *buf, uint16_t len) |
| { |
| if (!write_packet(proxy->dev_fd, buf, len, "D: ")) { |
| fprintf(stderr, "Write to device descriptor failed\n"); |
| mainloop_remove_fd(proxy->dev_fd); |
| } |
| } |
| |
| static void dev_write_packet(struct proxy *proxy, void *buf, uint16_t len) |
| { |
| if (!write_packet(proxy->host_fd, buf, len, "H: ")) { |
| fprintf(stderr, "Write to host descriptor failed\n"); |
| mainloop_remove_fd(proxy->host_fd); |
| } |
| } |
| |
| static void cmd_status(struct proxy *proxy, uint8_t status, uint16_t opcode) |
| { |
| size_t buf_size = 1 + sizeof(struct bt_hci_evt_hdr) + |
| sizeof(struct bt_hci_evt_cmd_status); |
| void *buf = alloca(buf_size); |
| struct bt_hci_evt_hdr *hdr = buf + 1; |
| struct bt_hci_evt_cmd_status *cs = buf + 1 + sizeof(*hdr); |
| |
| *((uint8_t *) buf) = BT_H4_EVT_PKT; |
| |
| hdr->evt = BT_HCI_EVT_CMD_STATUS; |
| hdr->plen = sizeof(*cs); |
| |
| cs->status = status; |
| cs->ncmd = 0x01; |
| cs->opcode = cpu_to_le16(opcode); |
| |
| dev_write_packet(proxy, buf, buf_size); |
| } |
| |
| static void le_meta_event(struct proxy *proxy, uint8_t event, |
| void *data, uint8_t len) |
| { |
| size_t buf_size = 1 + sizeof(struct bt_hci_evt_hdr) + 1 + len; |
| void *buf = alloca(buf_size); |
| struct bt_hci_evt_hdr *hdr = buf + 1; |
| |
| *((uint8_t *) buf) = BT_H4_EVT_PKT; |
| |
| hdr->evt = BT_HCI_EVT_LE_META_EVENT; |
| hdr->plen = 1 + len; |
| |
| *((uint8_t *) (buf + 1 + sizeof(*hdr))) = event; |
| |
| if (len > 0) |
| memcpy(buf + 1 + sizeof(*hdr) + 1, data, len); |
| |
| dev_write_packet(proxy, buf, buf_size); |
| } |
| |
| static void host_emulate_ecc(struct proxy *proxy, void *buf, uint16_t len) |
| { |
| uint8_t pkt_type = *((uint8_t *) buf); |
| struct bt_hci_cmd_hdr *hdr = buf + 1; |
| struct bt_hci_cmd_le_set_event_mask *lsem; |
| struct bt_hci_cmd_le_generate_dhkey *lgd; |
| struct bt_hci_evt_le_read_local_pk256_complete lrlpkc; |
| struct bt_hci_evt_le_generate_dhkey_complete lgdc; |
| |
| if (pkt_type != BT_H4_CMD_PKT) { |
| host_write_packet(proxy, buf, len); |
| return; |
| } |
| |
| switch (le16_to_cpu(hdr->opcode)) { |
| case BT_HCI_CMD_LE_SET_EVENT_MASK: |
| lsem = buf + 1 + sizeof(*hdr); |
| memcpy(proxy->event_mask, lsem->mask, 8); |
| |
| lsem->mask[0] &= ~0x80; /* P-256 Public Key Complete */ |
| lsem->mask[1] &= ~0x01; /* Generate DHKey Complete */ |
| |
| host_write_packet(proxy, buf, len); |
| break; |
| |
| case BT_HCI_CMD_LE_READ_LOCAL_PK256: |
| if (!ecc_make_key(lrlpkc.local_pk256, proxy->local_sk256)) { |
| cmd_status(proxy, BT_HCI_ERR_COMMAND_DISALLOWED, |
| BT_HCI_CMD_LE_READ_LOCAL_PK256); |
| break; |
| } |
| cmd_status(proxy, BT_HCI_ERR_SUCCESS, |
| BT_HCI_CMD_LE_READ_LOCAL_PK256); |
| |
| if (!(proxy->event_mask[0] & 0x80)) |
| break; |
| |
| lrlpkc.status = BT_HCI_ERR_SUCCESS; |
| le_meta_event(proxy, BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE, |
| &lrlpkc, sizeof(lrlpkc)); |
| break; |
| |
| case BT_HCI_CMD_LE_GENERATE_DHKEY: |
| lgd = buf + 1 + sizeof(*hdr); |
| if (!ecdh_shared_secret(lgd->remote_pk256, proxy->local_sk256, |
| lgdc.dhkey)) { |
| cmd_status(proxy, BT_HCI_ERR_COMMAND_DISALLOWED, |
| BT_HCI_CMD_LE_GENERATE_DHKEY); |
| break; |
| } |
| cmd_status(proxy, BT_HCI_ERR_SUCCESS, |
| BT_HCI_CMD_LE_GENERATE_DHKEY); |
| |
| if (!(proxy->event_mask[1] & 0x01)) |
| break; |
| |
| lgdc.status = BT_HCI_ERR_SUCCESS; |
| le_meta_event(proxy, BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE, |
| &lgdc, sizeof(lgdc)); |
| break; |
| |
| default: |
| host_write_packet(proxy, buf, len); |
| break; |
| } |
| } |
| |
| static void dev_emulate_ecc(struct proxy *proxy, void *buf, uint16_t len) |
| { |
| uint8_t pkt_type = *((uint8_t *) buf); |
| struct bt_hci_evt_hdr *hdr = buf + 1; |
| struct bt_hci_evt_cmd_complete *cc; |
| struct bt_hci_rsp_read_local_commands *rlc; |
| |
| if (pkt_type != BT_H4_EVT_PKT) { |
| dev_write_packet(proxy, buf, len); |
| return; |
| } |
| |
| switch (hdr->evt) { |
| case BT_HCI_EVT_CMD_COMPLETE: |
| cc = buf + 1 + sizeof(*hdr); |
| |
| switch (le16_to_cpu(cc->opcode)) { |
| case BT_HCI_CMD_READ_LOCAL_COMMANDS: |
| rlc = buf + 1 + sizeof(*hdr) + sizeof(*cc); |
| rlc->commands[34] |= 0x02; /* P-256 Public Key */ |
| rlc->commands[34] |= 0x04; /* Generate DHKey */ |
| break; |
| } |
| |
| dev_write_packet(proxy, buf, len); |
| break; |
| |
| default: |
| dev_write_packet(proxy, buf, len); |
| break; |
| } |
| } |
| |
| static void host_read_destroy(void *user_data) |
| { |
| struct proxy *proxy = user_data; |
| |
| printf("Closing host descriptor\n"); |
| |
| if (proxy->host_shutdown) |
| shutdown(proxy->host_fd, SHUT_RDWR); |
| |
| close(proxy->host_fd); |
| proxy->host_fd = -1; |
| |
| if (proxy->dev_fd < 0) { |
| client_active = false; |
| free(proxy); |
| } else |
| mainloop_remove_fd(proxy->dev_fd); |
| } |
| |
| static void host_read_callback(int fd, uint32_t events, void *user_data) |
| { |
| struct proxy *proxy = user_data; |
| struct bt_hci_cmd_hdr *cmd_hdr; |
| struct bt_hci_acl_hdr *acl_hdr; |
| struct bt_hci_sco_hdr *sco_hdr; |
| ssize_t len; |
| uint16_t pktlen; |
| |
| if (events & (EPOLLERR | EPOLLHUP)) { |
| fprintf(stderr, "Error from host descriptor\n"); |
| mainloop_remove_fd(proxy->host_fd); |
| return; |
| } |
| |
| if (events & EPOLLRDHUP) { |
| fprintf(stderr, "Remote hangup of host descriptor\n"); |
| mainloop_remove_fd(proxy->host_fd); |
| return; |
| } |
| |
| len = read(proxy->host_fd, proxy->host_buf + proxy->host_len, |
| sizeof(proxy->host_buf) - proxy->host_len); |
| if (len < 0) { |
| if (errno == EAGAIN || errno == EINTR) |
| return; |
| |
| fprintf(stderr, "Read from host descriptor failed\n"); |
| mainloop_remove_fd(proxy->host_fd); |
| return; |
| } |
| |
| if (debug_enabled) |
| util_hexdump('>', proxy->host_buf + proxy->host_len, len, |
| hexdump_print, "H: "); |
| |
| if (proxy->host_skip_first_zero && len > 0) { |
| proxy->host_skip_first_zero = false; |
| if (proxy->host_buf[proxy->host_len] == '\0') { |
| printf("Skipping initial zero byte\n"); |
| len--; |
| memmove(proxy->host_buf + proxy->host_len, |
| proxy->host_buf + proxy->host_len + 1, len); |
| } |
| } |
| |
| proxy->host_len += len; |
| |
| process_packet: |
| if (proxy->host_len < 1) |
| return; |
| |
| switch (proxy->host_buf[0]) { |
| case BT_H4_CMD_PKT: |
| if (proxy->host_len < 1 + sizeof(*cmd_hdr)) |
| return; |
| |
| cmd_hdr = (void *) (proxy->host_buf + 1); |
| pktlen = 1 + sizeof(*cmd_hdr) + cmd_hdr->plen; |
| break; |
| case BT_H4_ACL_PKT: |
| if (proxy->host_len < 1 + sizeof(*acl_hdr)) |
| return; |
| |
| acl_hdr = (void *) (proxy->host_buf + 1); |
| pktlen = 1 + sizeof(*acl_hdr) + cpu_to_le16(acl_hdr->dlen); |
| break; |
| case BT_H4_SCO_PKT: |
| if (proxy->host_len < 1 + sizeof(*sco_hdr)) |
| return; |
| |
| sco_hdr = (void *) (proxy->host_buf + 1); |
| pktlen = 1 + sizeof(*sco_hdr) + sco_hdr->dlen; |
| break; |
| case 0xff: |
| /* Notification packet from /dev/vhci - ignore */ |
| proxy->host_len = 0; |
| return; |
| default: |
| fprintf(stderr, "Received unknown host packet type 0x%02x\n", |
| proxy->host_buf[0]); |
| mainloop_remove_fd(proxy->host_fd); |
| return; |
| } |
| |
| if (proxy->host_len < pktlen) |
| return; |
| |
| if (emulate_ecc) |
| host_emulate_ecc(proxy, proxy->host_buf, pktlen); |
| else |
| host_write_packet(proxy, proxy->host_buf, pktlen); |
| |
| if (proxy->host_len > pktlen) { |
| memmove(proxy->host_buf, proxy->host_buf + pktlen, |
| proxy->host_len - pktlen); |
| proxy->host_len -= pktlen; |
| goto process_packet; |
| } |
| |
| proxy->host_len = 0; |
| } |
| |
| static void dev_read_destroy(void *user_data) |
| { |
| struct proxy *proxy = user_data; |
| |
| printf("Closing device descriptor\n"); |
| |
| if (proxy->dev_shutdown) |
| shutdown(proxy->dev_fd, SHUT_RDWR); |
| |
| close(proxy->dev_fd); |
| proxy->dev_fd = -1; |
| |
| if (proxy->host_fd < 0) { |
| client_active = false; |
| free(proxy); |
| } else |
| mainloop_remove_fd(proxy->host_fd); |
| } |
| |
| static void dev_read_callback(int fd, uint32_t events, void *user_data) |
| { |
| struct proxy *proxy = user_data; |
| struct bt_hci_evt_hdr *evt_hdr; |
| struct bt_hci_acl_hdr *acl_hdr; |
| struct bt_hci_sco_hdr *sco_hdr; |
| ssize_t len; |
| uint16_t pktlen; |
| |
| if (events & (EPOLLERR | EPOLLHUP)) { |
| fprintf(stderr, "Error from device descriptor\n"); |
| mainloop_remove_fd(proxy->dev_fd); |
| return; |
| } |
| |
| if (events & EPOLLRDHUP) { |
| fprintf(stderr, "Remote hangup of device descriptor\n"); |
| mainloop_remove_fd(proxy->host_fd); |
| return; |
| } |
| |
| len = read(proxy->dev_fd, proxy->dev_buf + proxy->dev_len, |
| sizeof(proxy->dev_buf) - proxy->dev_len); |
| if (len < 0) { |
| if (errno == EAGAIN || errno == EINTR) |
| return; |
| |
| fprintf(stderr, "Read from device descriptor failed\n"); |
| mainloop_remove_fd(proxy->dev_fd); |
| return; |
| } |
| |
| if (debug_enabled) |
| util_hexdump('>', proxy->dev_buf + proxy->dev_len, len, |
| hexdump_print, "D: "); |
| |
| proxy->dev_len += len; |
| |
| process_packet: |
| if (proxy->dev_len < 1) |
| return; |
| |
| switch (proxy->dev_buf[0]) { |
| case BT_H4_EVT_PKT: |
| if (proxy->dev_len < 1 + sizeof(*evt_hdr)) |
| return; |
| |
| evt_hdr = (void *) (proxy->dev_buf + 1); |
| pktlen = 1 + sizeof(*evt_hdr) + evt_hdr->plen; |
| break; |
| case BT_H4_ACL_PKT: |
| if (proxy->dev_len < 1 + sizeof(*acl_hdr)) |
| return; |
| |
| acl_hdr = (void *) (proxy->dev_buf + 1); |
| pktlen = 1 + sizeof(*acl_hdr) + cpu_to_le16(acl_hdr->dlen); |
| break; |
| case BT_H4_SCO_PKT: |
| if (proxy->dev_len < 1 + sizeof(*sco_hdr)) |
| return; |
| |
| sco_hdr = (void *) (proxy->dev_buf + 1); |
| pktlen = 1 + sizeof(*sco_hdr) + sco_hdr->dlen; |
| break; |
| default: |
| fprintf(stderr, "Received unknown device packet type 0x%02x\n", |
| proxy->dev_buf[0]); |
| mainloop_remove_fd(proxy->dev_fd); |
| return; |
| } |
| |
| if (proxy->dev_len < pktlen) |
| return; |
| |
| if (emulate_ecc) |
| dev_emulate_ecc(proxy, proxy->dev_buf, pktlen); |
| else |
| dev_write_packet(proxy, proxy->dev_buf, pktlen); |
| |
| if (proxy->dev_len > pktlen) { |
| memmove(proxy->dev_buf, proxy->dev_buf + pktlen, |
| proxy->dev_len - pktlen); |
| proxy->dev_len -= pktlen; |
| goto process_packet; |
| } |
| |
| proxy->dev_len = 0; |
| } |
| |
| static bool setup_proxy(int host_fd, bool host_shutdown, |
| int dev_fd, bool dev_shutdown) |
| { |
| struct proxy *proxy; |
| |
| proxy = new0(struct proxy, 1); |
| if (!proxy) |
| return false; |
| |
| if (emulate_ecc) |
| printf("Enabling ECC emulation\n"); |
| |
| proxy->host_fd = host_fd; |
| proxy->host_shutdown = host_shutdown; |
| proxy->host_skip_first_zero = skip_first_zero; |
| |
| proxy->dev_fd = dev_fd; |
| proxy->dev_shutdown = dev_shutdown; |
| |
| mainloop_add_fd(proxy->host_fd, EPOLLIN | EPOLLRDHUP, |
| host_read_callback, proxy, host_read_destroy); |
| |
| mainloop_add_fd(proxy->dev_fd, EPOLLIN | EPOLLRDHUP, |
| dev_read_callback, proxy, dev_read_destroy); |
| |
| return true; |
| } |
| |
| static int open_channel(uint16_t index) |
| { |
| struct sockaddr_hci addr; |
| int fd; |
| |
| printf("Opening user channel for hci%u\n", hci_index); |
| |
| fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); |
| if (fd < 0) { |
| perror("Failed to open Bluetooth socket"); |
| return -1; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.hci_family = AF_BLUETOOTH; |
| addr.hci_dev = index; |
| addr.hci_channel = HCI_CHANNEL_USER; |
| |
| if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| close(fd); |
| perror("Failed to bind Bluetooth socket"); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static void server_callback(int fd, uint32_t events, void *user_data) |
| { |
| union { |
| struct sockaddr common; |
| struct sockaddr_un sun; |
| struct sockaddr_in sin; |
| } addr; |
| socklen_t len; |
| int host_fd, dev_fd; |
| |
| if (events & (EPOLLERR | EPOLLHUP)) { |
| mainloop_quit(); |
| return; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| len = sizeof(addr); |
| |
| if (getsockname(fd, &addr.common, &len) < 0) { |
| perror("Failed to get socket name"); |
| return; |
| } |
| |
| host_fd = accept(fd, &addr.common, &len); |
| if (host_fd < 0) { |
| perror("Failed to accept client socket"); |
| return; |
| } |
| |
| if (client_active) { |
| fprintf(stderr, "Active client already present\n"); |
| close(host_fd); |
| return; |
| } |
| |
| dev_fd = open_channel(hci_index); |
| if (dev_fd < 0) { |
| close(host_fd); |
| return; |
| } |
| |
| printf("New client connected\n"); |
| |
| if (!setup_proxy(host_fd, true, dev_fd, false)) { |
| close(dev_fd); |
| close(host_fd); |
| return; |
| } |
| |
| client_active = true; |
| } |
| |
| static int open_unix(const char *path) |
| { |
| struct sockaddr_un addr; |
| size_t len; |
| int fd; |
| |
| len = strlen(path); |
| if (len > sizeof(addr.sun_path) - 1) { |
| fprintf(stderr, "Path too long\n"); |
| return -1; |
| } |
| |
| unlink(path); |
| |
| fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); |
| if (fd < 0) { |
| perror("Failed to open Unix server socket"); |
| return -1; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| strncpy(addr.sun_path, path, len); |
| |
| if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| perror("Failed to bind Unix server socket"); |
| close(fd); |
| return -1; |
| } |
| |
| if (listen(fd, 1) < 0) { |
| perror("Failed to listen Unix server socket"); |
| close(fd); |
| return -1; |
| } |
| |
| if (chmod(path, 0666) < 0) |
| perror("Failed to change mode"); |
| |
| return fd; |
| } |
| |
| static int open_tcp(const char *address, unsigned int port) |
| { |
| struct sockaddr_in addr; |
| int fd, opt = 1; |
| |
| fd = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); |
| if (fd < 0) { |
| perror("Failed to open TCP server socket"); |
| return -1; |
| } |
| |
| setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = inet_addr(address); |
| addr.sin_port = htons(port); |
| |
| if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| perror("Failed to bind TCP server socket"); |
| close(fd); |
| return -1; |
| } |
| |
| if (listen(fd, 1) < 0) { |
| perror("Failed to listen TCP server socket"); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static int connect_tcp(const char *address, unsigned int port) |
| { |
| struct sockaddr_in addr; |
| int fd; |
| |
| fd = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); |
| if (fd < 0) { |
| perror("Failed to open TCP client socket"); |
| return -1; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = inet_addr(address); |
| addr.sin_port = htons(port); |
| |
| if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| perror("Failed to connect TCP client socket"); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static int open_vhci(uint8_t type) |
| { |
| uint8_t create_req[2] = { 0xff, type }; |
| ssize_t written; |
| int fd; |
| |
| fd = open("/dev/vhci", O_RDWR | O_CLOEXEC); |
| if (fd < 0) { |
| perror("Failed to open /dev/vhci device"); |
| return -1; |
| } |
| |
| written = write(fd, create_req, sizeof(create_req)); |
| if (written < 0) { |
| perror("Failed to set device type"); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static void signal_callback(int signum, void *user_data) |
| { |
| switch (signum) { |
| case SIGINT: |
| case SIGTERM: |
| mainloop_quit(); |
| break; |
| } |
| } |
| |
| static void usage(void) |
| { |
| printf("btproxy - Bluetooth controller proxy\n" |
| "Usage:\n"); |
| printf("\tbtproxy [options]\n"); |
| printf("Options:\n" |
| "\t-c, --connect <address> Connect to server\n" |
| "\t-l, --listen [address] Use TCP server\n" |
| "\t-u, --unix [path] Use Unix server\n" |
| "\t-p, --port <port> Use specified TCP port\n" |
| "\t-i, --index <num> Use specified controller\n" |
| "\t-a, --amp Create AMP controller\n" |
| "\t-e, --ecc Emulate ECC support\n" |
| "\t-d, --debug Enable debugging output\n" |
| "\t-h, --help Show help options\n"); |
| } |
| |
| static const struct option main_options[] = { |
| { "redirect", no_argument, NULL, 'r' }, |
| { "connect", required_argument, NULL, 'c' }, |
| { "listen", optional_argument, NULL, 'l' }, |
| { "unix", optional_argument, NULL, 'u' }, |
| { "port", required_argument, NULL, 'p' }, |
| { "index", required_argument, NULL, 'i' }, |
| { "amp", no_argument, NULL, 'a' }, |
| { "ecc", no_argument, NULL, 'e' }, |
| { "debug", no_argument, NULL, 'd' }, |
| { "version", no_argument, NULL, 'v' }, |
| { "help", no_argument, NULL, 'h' }, |
| { } |
| }; |
| |
| int main(int argc, char *argv[]) |
| { |
| const char *connect_address = NULL; |
| const char *server_address = NULL; |
| const char *unix_path = NULL; |
| unsigned short tcp_port = 0xb1ee; /* 45550 */ |
| bool use_redirect = false; |
| uint8_t type = HCI_PRIMARY; |
| const char *str; |
| sigset_t mask; |
| |
| for (;;) { |
| int opt; |
| |
| opt = getopt_long(argc, argv, "rc:l::u::p:i:aezdvh", |
| main_options, NULL); |
| if (opt < 0) |
| break; |
| |
| switch (opt) { |
| case 'r': |
| use_redirect = true; |
| break; |
| case 'c': |
| connect_address = optarg; |
| break; |
| case 'l': |
| if (optarg) |
| server_address = optarg; |
| else |
| server_address = "0.0.0.0"; |
| break; |
| case 'u': |
| if (optarg) { |
| struct sockaddr_un addr; |
| |
| unix_path = optarg; |
| if (strlen(unix_path) > |
| sizeof(addr.sun_path) - 1) { |
| fprintf(stderr, "Path too long\n"); |
| return EXIT_FAILURE; |
| } |
| } else |
| unix_path = "/tmp/bt-server-bredr"; |
| break; |
| case 'p': |
| tcp_port = atoi(optarg); |
| break; |
| case 'i': |
| if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) |
| str = optarg + 3; |
| else |
| str = optarg; |
| if (!isdigit(*str)) { |
| usage(); |
| return EXIT_FAILURE; |
| } |
| hci_index = atoi(str); |
| break; |
| case 'a': |
| type = HCI_AMP; |
| break; |
| case 'e': |
| emulate_ecc = true; |
| break; |
| case 'z': |
| skip_first_zero = true; |
| break; |
| case 'd': |
| debug_enabled = true; |
| break; |
| case 'v': |
| printf("%s\n", VERSION); |
| return EXIT_SUCCESS; |
| case 'h': |
| usage(); |
| return EXIT_SUCCESS; |
| default: |
| return EXIT_FAILURE; |
| } |
| } |
| |
| if (argc - optind > 0) { |
| fprintf(stderr, "Invalid command line parameters\n"); |
| return EXIT_FAILURE; |
| } |
| |
| if (unix_path && (server_address || use_redirect)) { |
| fprintf(stderr, "Invalid to specify TCP and Unix servers\n"); |
| return EXIT_FAILURE; |
| } |
| |
| if (connect_address && (unix_path || server_address || use_redirect)) { |
| fprintf(stderr, "Invalid to specify client and server mode\n"); |
| return EXIT_FAILURE; |
| } |
| |
| mainloop_init(); |
| |
| sigemptyset(&mask); |
| sigaddset(&mask, SIGINT); |
| sigaddset(&mask, SIGTERM); |
| |
| mainloop_set_signal(&mask, signal_callback, NULL, NULL); |
| |
| if (connect_address || use_redirect) { |
| int host_fd, dev_fd; |
| |
| if (use_redirect) { |
| printf("Creating local redirect\n"); |
| |
| dev_fd = open_channel(hci_index); |
| } else { |
| printf("Connecting to %s:%u\n", connect_address, |
| tcp_port); |
| |
| dev_fd = connect_tcp(connect_address, tcp_port); |
| } |
| |
| if (dev_fd < 0) |
| return EXIT_FAILURE; |
| |
| printf("Opening virtual device\n"); |
| |
| host_fd = open_vhci(type); |
| if (host_fd < 0) { |
| close(dev_fd); |
| return EXIT_FAILURE; |
| } |
| |
| if (!setup_proxy(host_fd, false, dev_fd, true)) { |
| close(dev_fd); |
| close(host_fd); |
| return EXIT_FAILURE; |
| } |
| } else { |
| int server_fd; |
| |
| if (unix_path) { |
| printf("Listening on %s\n", unix_path); |
| |
| server_fd = open_unix(unix_path); |
| } else if (server_address) { |
| printf("Listening on %s:%u\n", server_address, |
| tcp_port); |
| |
| server_fd = open_tcp(server_address, tcp_port); |
| } else { |
| fprintf(stderr, "Missing emulator device\n"); |
| return EXIT_FAILURE; |
| } |
| |
| if (server_fd < 0) |
| return EXIT_FAILURE; |
| |
| mainloop_add_fd(server_fd, EPOLLIN, server_callback, |
| NULL, NULL); |
| } |
| |
| return mainloop_run(); |
| } |