*
* BlueZ - Bluetooth protocol stack for Linux
*
- * Copyright (C) 2011-2012 Intel Corporation
- * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2011-2014 Intel Corporation
+ * Copyright (C) 2002-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 library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful,
+ * This library 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.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#endif
#include <stdio.h>
+#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
-#include <bluetooth/bluetooth.h>
-#include <bluetooth/hci.h>
-#include <bluetooth/mgmt.h>
+#include "lib/bluetooth.h"
+#include "lib/hci.h"
+#include "lib/mgmt.h"
-#include "mainloop.h"
+#include "src/shared/util.h"
+#include "src/shared/btsnoop.h"
+#include "src/shared/mainloop.h"
+
+#include "display.h"
#include "packet.h"
+#include "hcidump.h"
+#include "ellisys.h"
#include "control.h"
+static struct btsnoop *btsnoop_file = NULL;
+static bool hcidump_fallback = false;
+
struct control_data {
uint16_t channel;
int fd;
+ unsigned char buf[BTSNOOP_MAX_PACKET_SIZE];
+ uint16_t offset;
};
static void free_data(void *user_data)
packet_hexdump(buf, len);
}
+static void mgmt_unconf_index_added(uint16_t len, const void *buf)
+{
+ printf("@ Unconfigured Index Added\n");
+
+ packet_hexdump(buf, len);
+}
+
+static void mgmt_unconf_index_removed(uint16_t len, const void *buf)
+{
+ printf("@ Unconfigured Index Removed\n");
+
+ packet_hexdump(buf, len);
+}
+
static void mgmt_controller_error(uint16_t len, const void *buf)
{
const struct mgmt_ev_controller_error *ev = buf;
#define NELEM(x) (sizeof(x) / sizeof((x)[0]))
#endif
+static const char *config_options_str[] = {
+ "external", "public-address",
+};
+
+static void mgmt_new_config_options(uint16_t len, const void *buf)
+{
+ uint32_t options;
+ unsigned int i;
+
+ if (len < 4) {
+ printf("* Malformed New Configuration Options control\n");
+ return;
+ }
+
+ options = get_le32(buf);
+
+ printf("@ New Configuration Options: 0x%4.4x\n", options);
+
+ if (options) {
+ printf("%-12c", ' ');
+ for (i = 0; i < NELEM(config_options_str); i++) {
+ if (options & (1 << i))
+ printf("%s ", config_options_str[i]);
+ }
+ printf("\n");
+ }
+
+ buf += 4;
+ len -= 4;
+
+ packet_hexdump(buf, len);
+}
+
static const char *settings_str[] = {
"powered", "connectable", "fast-connectable", "discoverable",
- "pairable", "link-security", "ssp", "br/edr", "hs", "le"
+ "bondable", "link-security", "ssp", "br/edr", "hs", "le",
+ "advertising", "secure-conn", "debug-keys", "privacy",
+ "configuration", "static-addr",
};
static void mgmt_new_settings(uint16_t len, const void *buf)
return;
}
- settings = bt_get_le32(buf);
+ settings = get_le32(buf);
printf("@ New Settings: 0x%4.4x\n", settings);
- printf("%-12c", ' ');
- for (i = 0; i < NELEM(settings_str); i++) {
- if (settings & (1 << i))
- printf("%s ", settings_str[i]);
+ if (settings) {
+ printf("%-12c", ' ');
+ for (i = 0; i < NELEM(settings_str); i++) {
+ if (settings & (1 << i))
+ printf("%s ", settings_str[i]);
+ }
+ printf("\n");
}
- printf("\n");
buf += 4;
len -= 4;
static void mgmt_new_long_term_key(uint16_t len, const void *buf)
{
const struct mgmt_ev_new_long_term_key *ev = buf;
+ const char *type;
char str[18];
if (len < sizeof(*ev)) {
return;
}
+ /* LE SC keys are both for master and slave */
+ switch (ev->key.type) {
+ case 0x00:
+ if (ev->key.master)
+ type = "Master (Unauthenticated)";
+ else
+ type = "Slave (Unauthenticated)";
+ break;
+ case 0x01:
+ if (ev->key.master)
+ type = "Master (Authenticated)";
+ else
+ type = "Slave (Authenticated)";
+ break;
+ case 0x02:
+ type = "SC (Unauthenticated)";
+ break;
+ case 0x03:
+ type = "SC (Authenticated)";
+ break;
+ case 0x04:
+ type = "SC (Debug)";
+ break;
+ default:
+ type = "<unknown>";
+ break;
+ }
+
ba2str(&ev->key.addr.bdaddr, str);
- printf("@ New Long Term Key: %s (%d)\n", str, ev->key.addr.type);
+ printf("@ New Long Term Key: %s (%d) %s 0x%02x\n", str,
+ ev->key.addr.type, type, ev->key.type);
buf += sizeof(*ev);
len -= sizeof(*ev);
return;
}
- flags = btohs(ev->flags);
+ flags = le32_to_cpu(ev->flags);
ba2str(&ev->addr.bdaddr, str);
printf("@ Device Connected: %s (%d) flags 0x%4.4x\n",
{
const struct mgmt_ev_device_disconnected *ev = buf;
char str[18];
+ uint8_t reason;
+ uint16_t consumed_len;
- if (len < sizeof(*ev)) {
+ if (len < sizeof(struct mgmt_addr_info)) {
printf("* Malformed Device Disconnected control\n");
return;
}
+ if (len < sizeof(*ev)) {
+ reason = MGMT_DEV_DISCONN_UNKNOWN;
+ consumed_len = len;
+ } else {
+ reason = ev->reason;
+ consumed_len = sizeof(*ev);
+ }
+
ba2str(&ev->addr.bdaddr, str);
- printf("@ Device Disconnected: %s (%d)\n", str, ev->addr.type);
+ printf("@ Device Disconnected: %s (%d) reason %u\n", str, ev->addr.type,
+ reason);
- buf += sizeof(*ev);
- len -= sizeof(*ev);
+ buf += consumed_len;
+ len -= consumed_len;
packet_hexdump(buf, len);
}
ba2str(&ev->addr.bdaddr, str);
- printf("@ PIN User Passkey Request: %s (%d)\n", str, ev->addr.type);
+ printf("@ User Passkey Request: %s (%d)\n", str, ev->addr.type);
buf += sizeof(*ev);
len -= sizeof(*ev);
return;
}
- flags = btohs(ev->flags);
+ flags = le32_to_cpu(ev->flags);
ba2str(&ev->addr.bdaddr, str);
printf("@ Device Found: %s (%d) rssi %d flags 0x%4.4x\n",
packet_hexdump(buf, len);
}
+static void mgmt_passkey_notify(uint16_t len, const void *buf)
+{
+ const struct mgmt_ev_passkey_notify *ev = buf;
+ uint32_t passkey;
+ char str[18];
+
+ if (len < sizeof(*ev)) {
+ printf("* Malformed Passkey Notify control\n");
+ return;
+ }
+
+ ba2str(&ev->addr.bdaddr, str);
+
+ passkey = le32_to_cpu(ev->passkey);
+
+ printf("@ Passkey Notify: %s (%d) passkey %06u entered %u\n",
+ str, ev->addr.type, passkey, ev->entered);
+
+ buf += sizeof(*ev);
+ len -= sizeof(*ev);
+
+ packet_hexdump(buf, len);
+}
+
+static void mgmt_new_irk(uint16_t len, const void *buf)
+{
+ const struct mgmt_ev_new_irk *ev = buf;
+ char addr[18], rpa[18];
+
+ if (len < sizeof(*ev)) {
+ printf("* Malformed New IRK control\n");
+ return;
+ }
+
+ ba2str(&ev->rpa, rpa);
+ ba2str(&ev->key.addr.bdaddr, addr);
+
+ printf("@ New IRK: %s (%d) %s\n", addr, ev->key.addr.type, rpa);
+
+ buf += sizeof(*ev);
+ len -= sizeof(*ev);
+
+ packet_hexdump(buf, len);
+}
+
+static void mgmt_new_csrk(uint16_t len, const void *buf)
+{
+ const struct mgmt_ev_new_csrk *ev = buf;
+ const char *type;
+ char addr[18];
+
+ if (len < sizeof(*ev)) {
+ printf("* Malformed New CSRK control\n");
+ return;
+ }
+
+ ba2str(&ev->key.addr.bdaddr, addr);
+
+ switch (ev->key.type) {
+ case 0x00:
+ type = "Local Unauthenticated";
+ break;
+ case 0x01:
+ type = "Remote Unauthenticated";
+ break;
+ case 0x02:
+ type = "Local Authenticated";
+ break;
+ case 0x03:
+ type = "Remote Authenticated";
+ break;
+ default:
+ type = "<unknown>";
+ break;
+ }
+
+ printf("@ New CSRK: %s (%d) %s (%u)\n", addr, ev->key.addr.type,
+ type, ev->key.type);
+
+ buf += sizeof(*ev);
+ len -= sizeof(*ev);
+
+ packet_hexdump(buf, len);
+}
+
+static void mgmt_device_added(uint16_t len, const void *buf)
+{
+ const struct mgmt_ev_device_added *ev = buf;
+ char str[18];
+
+ if (len < sizeof(*ev)) {
+ printf("* Malformed Device Added control\n");
+ return;
+ }
+
+ ba2str(&ev->addr.bdaddr, str);
+
+ printf("@ Device Added: %s (%d) %d\n", str, ev->addr.type, ev->action);
+
+ buf += sizeof(*ev);
+ len -= sizeof(*ev);
+
+ packet_hexdump(buf, len);
+}
+
+static void mgmt_device_removed(uint16_t len, const void *buf)
+{
+ const struct mgmt_ev_device_removed *ev = buf;
+ char str[18];
+
+ if (len < sizeof(*ev)) {
+ printf("* Malformed Device Removed control\n");
+ return;
+ }
+
+ ba2str(&ev->addr.bdaddr, str);
+
+ printf("@ Device Removed: %s (%d)\n", str, ev->addr.type);
+
+ buf += sizeof(*ev);
+ len -= sizeof(*ev);
+
+ packet_hexdump(buf, len);
+}
+
+static void mgmt_new_conn_param(uint16_t len, const void *buf)
+{
+ const struct mgmt_ev_new_conn_param *ev = buf;
+ char addr[18];
+ uint16_t min, max, latency, timeout;
+
+ if (len < sizeof(*ev)) {
+ printf("* Malformed New Connection Parameter control\n");
+ return;
+ }
+
+ ba2str(&ev->addr.bdaddr, addr);
+ min = le16_to_cpu(ev->min_interval);
+ max = le16_to_cpu(ev->max_interval);
+ latency = le16_to_cpu(ev->latency);
+ timeout = le16_to_cpu(ev->timeout);
+
+ printf("@ New Conn Param: %s (%d) hint %d min 0x%4.4x max 0x%4.4x "
+ "latency 0x%4.4x timeout 0x%4.4x\n", addr, ev->addr.type,
+ ev->store_hint, min, max, latency, timeout);
+
+ buf += sizeof(*ev);
+ len -= sizeof(*ev);
+
+ packet_hexdump(buf, len);
+}
+
void control_message(uint16_t opcode, const void *data, uint16_t size)
{
switch (opcode) {
case MGMT_EV_DEVICE_UNPAIRED:
mgmt_device_unpaired(size, data);
break;
+ case MGMT_EV_PASSKEY_NOTIFY:
+ mgmt_passkey_notify(size, data);
+ break;
+ case MGMT_EV_NEW_IRK:
+ mgmt_new_irk(size, data);
+ break;
+ case MGMT_EV_NEW_CSRK:
+ mgmt_new_csrk(size, data);
+ break;
+ case MGMT_EV_DEVICE_ADDED:
+ mgmt_device_added(size, data);
+ break;
+ case MGMT_EV_DEVICE_REMOVED:
+ mgmt_device_removed(size, data);
+ break;
+ case MGMT_EV_NEW_CONN_PARAM:
+ mgmt_new_conn_param(size, data);
+ break;
+ case MGMT_EV_UNCONF_INDEX_ADDED:
+ mgmt_unconf_index_added(size, data);
+ break;
+ case MGMT_EV_UNCONF_INDEX_REMOVED:
+ mgmt_unconf_index_removed(size, data);
+ break;
+ case MGMT_EV_NEW_CONFIG_OPTIONS:
+ mgmt_new_config_options(size, data);
+ break;
default:
printf("* Unknown control (code %d len %d)\n", opcode, size);
packet_hexdump(data, size);
static void data_callback(int fd, uint32_t events, void *user_data)
{
struct control_data *data = user_data;
- unsigned char buf[HCI_MAX_FRAME_SIZE];
unsigned char control[32];
struct mgmt_hdr hdr;
struct msghdr msg;
struct iovec iov[2];
if (events & (EPOLLERR | EPOLLHUP)) {
- mainloop_remove_fd(fd);
+ mainloop_remove_fd(data->fd);
return;
}
iov[0].iov_base = &hdr;
iov[0].iov_len = MGMT_HDR_SIZE;
- iov[1].iov_base = buf;
- iov[1].iov_len = sizeof(buf);
+ iov[1].iov_base = data->buf;
+ iov[1].iov_len = sizeof(data->buf);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
while (1) {
struct cmsghdr *cmsg;
struct timeval *tv = NULL;
+ struct timeval ctv;
uint16_t opcode, index, pktlen;
ssize_t len;
- len = recvmsg(fd, &msg, MSG_DONTWAIT);
+ len = recvmsg(data->fd, &msg, MSG_DONTWAIT);
if (len < 0)
break;
if (cmsg->cmsg_level != SOL_SOCKET)
continue;
- if (cmsg->cmsg_type == SCM_TIMESTAMP)
- tv = (struct timeval *) CMSG_DATA(cmsg);
+ if (cmsg->cmsg_type == SCM_TIMESTAMP) {
+ memcpy(&ctv, CMSG_DATA(cmsg), sizeof(ctv));
+ tv = &ctv;
+ }
}
- opcode = btohs(hdr.opcode);
- index = btohs(hdr.index);
- pktlen = btohs(hdr.len);
+ opcode = le16_to_cpu(hdr.opcode);
+ index = le16_to_cpu(hdr.index);
+ pktlen = le16_to_cpu(hdr.len);
switch (data->channel) {
case HCI_CHANNEL_CONTROL:
- packet_control(tv, index, opcode, buf, pktlen);
+ packet_control(tv, index, opcode, data->buf, pktlen);
break;
case HCI_CHANNEL_MONITOR:
- packet_monitor(tv, index, opcode, buf, pktlen);
+ btsnoop_write_hci(btsnoop_file, tv, index, opcode,
+ data->buf, pktlen);
+ ellisys_inject_hci(tv, index, opcode,
+ data->buf, pktlen);
+ packet_monitor(tv, index, opcode, data->buf, pktlen);
break;
}
}
struct sockaddr_hci addr;
int fd, opt = 1;
- fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+ fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
if (fd < 0) {
perror("Failed to open channel");
return -1;
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
if (errno == EINVAL) {
/* Fallback to hcidump support */
+ hcidump_fallback = true;
close(fd);
return -1;
}
return 0;
}
+static void client_callback(int fd, uint32_t events, void *user_data)
+{
+ struct control_data *data = user_data;
+ ssize_t len;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(data->fd);
+ return;
+ }
+
+ len = recv(data->fd, data->buf + data->offset,
+ sizeof(data->buf) - data->offset, MSG_DONTWAIT);
+ if (len < 0)
+ return;
+
+ data->offset += len;
+
+ if (data->offset > MGMT_HDR_SIZE) {
+ struct mgmt_hdr *hdr = (struct mgmt_hdr *) data->buf;
+ uint16_t pktlen = le16_to_cpu(hdr->len);
+
+ if (data->offset > pktlen + MGMT_HDR_SIZE) {
+ uint16_t opcode = le16_to_cpu(hdr->opcode);
+ uint16_t index = le16_to_cpu(hdr->index);
+
+ packet_monitor(NULL, index, opcode,
+ data->buf + MGMT_HDR_SIZE, pktlen);
+
+ data->offset -= pktlen + MGMT_HDR_SIZE;
+
+ if (data->offset > 0)
+ memmove(data->buf, data->buf +
+ MGMT_HDR_SIZE + pktlen, data->offset);
+ }
+ }
+}
+
+static void server_accept_callback(int fd, uint32_t events, void *user_data)
+{
+ struct control_data *data;
+ struct sockaddr_un addr;
+ socklen_t len;
+ int nfd;
+
+ if (events & (EPOLLERR | EPOLLHUP)) {
+ mainloop_remove_fd(fd);
+ return;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ len = sizeof(addr);
+
+ nfd = accept(fd, (struct sockaddr *) &addr, &len);
+ if (nfd < 0) {
+ perror("Failed to accept client socket");
+ return;
+ }
+
+ printf("--- New monitor connection ---\n");
+
+ data = malloc(sizeof(*data));
+ if (!data) {
+ close(nfd);
+ return;
+ }
+
+ memset(data, 0, sizeof(*data));
+ data->channel = HCI_CHANNEL_MONITOR;
+ data->fd = nfd;
+
+ mainloop_add_fd(data->fd, EPOLLIN, client_callback, data, free_data);
+}
+
+static int server_fd = -1;
+
+void control_server(const char *path)
+{
+ struct sockaddr_un addr;
+ int fd;
+
+ if (server_fd >= 0)
+ return;
+
+ unlink(path);
+
+ fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0) {
+ perror("Failed to open server socket");
+ return;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, path);
+
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ perror("Failed to bind server socket");
+ close(fd);
+ return;
+ }
+
+ if (listen(fd, 5) < 0) {
+ perror("Failed to listen server socket");
+ close(fd);
+ return;
+ }
+
+ if (mainloop_add_fd(fd, EPOLLIN, server_accept_callback,
+ NULL, NULL) < 0) {
+ close(fd);
+ return;
+ }
+
+ server_fd = fd;
+}
+
+bool control_writer(const char *path)
+{
+ btsnoop_file = btsnoop_create(path, BTSNOOP_TYPE_MONITOR);
+
+ return !!btsnoop_file;
+}
+
+void control_reader(const char *path)
+{
+ unsigned char buf[BTSNOOP_MAX_PACKET_SIZE];
+ uint16_t pktlen;
+ uint32_t type;
+ struct timeval tv;
+
+ btsnoop_file = btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT);
+ if (!btsnoop_file)
+ return;
+
+ type = btsnoop_get_type(btsnoop_file);
+
+ switch (type) {
+ case BTSNOOP_TYPE_HCI:
+ case BTSNOOP_TYPE_UART:
+ case BTSNOOP_TYPE_SIMULATOR:
+ packet_del_filter(PACKET_FILTER_SHOW_INDEX);
+ break;
+
+ case BTSNOOP_TYPE_MONITOR:
+ packet_add_filter(PACKET_FILTER_SHOW_INDEX);
+ break;
+ }
+
+ open_pager();
+
+ switch (type) {
+ case BTSNOOP_TYPE_HCI:
+ case BTSNOOP_TYPE_UART:
+ case BTSNOOP_TYPE_MONITOR:
+ while (1) {
+ uint16_t index, opcode;
+
+ if (!btsnoop_read_hci(btsnoop_file, &tv, &index,
+ &opcode, buf, &pktlen))
+ break;
+
+ if (opcode == 0xffff)
+ continue;
+
+ packet_monitor(&tv, index, opcode, buf, pktlen);
+ ellisys_inject_hci(&tv, index, opcode, buf, pktlen);
+ }
+ break;
+
+ case BTSNOOP_TYPE_SIMULATOR:
+ while (1) {
+ uint16_t frequency;
+
+ if (!btsnoop_read_phy(btsnoop_file, &tv, &frequency,
+ buf, &pktlen))
+ break;
+
+ packet_simulator(&tv, frequency, buf, pktlen);
+ }
+ break;
+ }
+
+ close_pager();
+
+ btsnoop_unref(btsnoop_file);
+}
+
int control_tracing(void)
{
- if (open_channel(HCI_CHANNEL_MONITOR) < 0)
- return -1;
+ packet_add_filter(PACKET_FILTER_SHOW_INDEX);
+
+ if (server_fd >= 0)
+ return 0;
+
+ if (open_channel(HCI_CHANNEL_MONITOR) < 0) {
+ if (!hcidump_fallback)
+ return -1;
+ if (hcidump_tracing() < 0)
+ return -1;
+ return 0;
+ }
open_channel(HCI_CHANNEL_CONTROL);