Allow btsnoop logs to be read over a local socket.
This change allows real-time HCI debugging over adb from a
Linux box using hcidump.
Example:
--------
adb forward tcp:8872 tcp:8872
nc localhost 8872 | hcidump -X -r /dev/stdin
Change-Id: I49c32a941f71f612807061284a755a38b76588ff
diff --git a/doc/btsnoop_net.md b/doc/btsnoop_net.md
new file mode 100644
index 0000000..efd1071
--- /dev/null
+++ b/doc/btsnoop_net.md
@@ -0,0 +1,15 @@
+btsnoop_net
+====
+btsnoop_net exposes Bluetooth snoop logs over a local TCP socket which enables
+real-time debugging of HCI data with hcidump.
+
+This feature is enabled by setting `BtSnoopLogOutput=true` in `bt_stack.conf`.
+Once it has been enabled and the stack restarted, bluedroid will listen for
+incoming TCP connections on port 8872.
+
+To use this feature with hcidump on a Linux host, you can run:
+
+```
+ $ adb forward tcp:8872 tcp:8872
+ $ nc localhost 8872 | hcidump -r /dev/stdin
+```
diff --git a/hci/Android.mk b/hci/Android.mk
index bbad3e9..4e956c2 100644
--- a/hci/Android.mk
+++ b/hci/Android.mk
@@ -9,6 +9,7 @@
src/lpm.c \
src/bt_hw.c \
src/btsnoop.c \
+ src/btsnoop_net.c \
src/utils.c
LOCAL_CFLAGS := -Wno-unused-parameter
diff --git a/hci/src/btsnoop.c b/hci/src/btsnoop.c
index c6556d6..e4b1376 100644
--- a/hci/src/btsnoop.c
+++ b/hci/src/btsnoop.c
@@ -34,6 +34,7 @@
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <arpa/inet.h>
#include <netinet/in.h>
@@ -67,10 +68,16 @@
#define SNOOPDBG(param, ...) {}
#endif
-#define HCIT_TYPE_COMMAND 1
-#define HCIT_TYPE_ACL_DATA 2
-#define HCIT_TYPE_SCO_DATA 3
-#define HCIT_TYPE_EVENT 4
+typedef enum {
+ kCommandPacket = 1,
+ kAclPacket = 2,
+ kScoPacket = 3,
+ kEventPacket = 4
+} packet_type;
+
+void btsnoop_net_init();
+void btsnoop_net_cleanup();
+void btsnoop_net_write(const void *data, size_t length);
/* file descriptor of the BT snoop file (by default, -1 means disabled) */
int hci_btsnoop_fd = -1;
@@ -259,117 +266,78 @@
}
/*******************************************************************************
- ** Function btsnoop_write
**
- ** Description Function used to write the actual data to the log
+ ** Function btsnoop_write
**
- ** Returns none
+ ** Description Writes raw bytes to the BTSNOOP sinks.
+ **
+ ** Returns None
*******************************************************************************/
+static void btsnoop_write(const void *data, size_t length) {
+ if (hci_btsnoop_fd != -1) {
+ write(hci_btsnoop_fd, data, length);
+ }
+ btsnoop_net_write(data, length);
+}
-void btsnoop_write(uint8_t *p, uint32_t flags, const uint8_t *ptype, uint32_t len)
-{
- uint32_t value, value_hi;
+/*******************************************************************************
+ **
+ ** Function btsnoop_write_packet
+ **
+ ** Description Writes a single HCI packet to BTSNOOP sinks.
+ **
+ ** Returns None
+*******************************************************************************/
+static void btsnoop_write_packet(packet_type type,
+ const uint8_t *packet,
+ bool is_received) {
+ int length_he;
+ int length;
+ int flags;
+ int drops = 0;
+ switch (type) {
+ case kCommandPacket:
+ length_he = packet[2] + 4;
+ flags = 2;
+ break;
+ case kAclPacket:
+ length_he = (packet[3] << 8) + packet[2] + 5;
+ flags = is_received;
+ break;
+ case kScoPacket:
+ length_he = packet[2] + 4;
+ flags = is_received;
+ break;
+ case kEventPacket:
+ length_he = packet[1] + 3;
+ flags = 3;
+ break;
+ }
+
+ uint32_t time_hi, time_lo;
struct timeval tv;
- struct iovec io[3];
- uint32_t header[6];
-
- /* store the length in both original and included fields */
- header[0] = l_to_be(len + 1);
- header[1] = header[0];
- /* flags: data can be sent or received */
- header[2] = l_to_be(flags);
- /* drops: none */
- header[3] = 0;
- /* time */
gettimeofday(&tv, NULL);
- tv_to_btsnoop_ts(&header[5], &header[4], &tv);
- header[4] = l_to_be(header[4]);
- header[5] = l_to_be(header[5]);
+ tv_to_btsnoop_ts(&time_lo, &time_hi, &tv);
- io[0].iov_base = header;
- io[0].iov_len = sizeof(header);
+ length = l_to_be(length_he);
+ flags = l_to_be(flags);
+ drops = l_to_be(drops);
+ time_hi = l_to_be(time_hi);
+ time_lo = l_to_be(time_lo);
- io[1].iov_base = (void*)ptype;
- io[1].iov_len = 1;
+ /* since these display functions are called from different contexts */
+ utils_lock();
- io[2].iov_base = p;
- io[2].iov_len = len;
+ btsnoop_write(&length, 4);
+ btsnoop_write(&length, 4);
+ btsnoop_write(&flags, 4);
+ btsnoop_write(&drops, 4);
+ btsnoop_write(&time_hi, 4);
+ btsnoop_write(&time_lo, 4);
+ btsnoop_write(&type, 1);
+ btsnoop_write(packet, length_he - 1);
- (void) writev(hci_btsnoop_fd, io, 3);
-}
-
-/*******************************************************************************
- **
- ** Function btsnoop_hci_cmd
- **
- ** Description Function to add a command in the BTSNOOP file
- **
- ** Returns None
-*******************************************************************************/
-void btsnoop_hci_cmd(uint8_t *p)
-{
- const uint8_t cmd = HCIT_TYPE_COMMAND;
- int plen;
- SNOOPDBG("btsnoop_hci_cmd: fd = %d", hci_btsnoop_fd);
- plen = (int) p[2] + 3;
- btsnoop_write(p, 2, &cmd, plen);
-}
-
-
-/*******************************************************************************
- **
- ** Function btsnoop_hci_evt
- **
- ** Description Function to add a event in the BTSNOOP file
- **
- ** Returns None
-*******************************************************************************/
-void btsnoop_hci_evt(uint8_t *p)
-{
- const uint8_t evt = HCIT_TYPE_EVENT;
- int plen;
- SNOOPDBG("btsnoop_hci_evt: fd = %d", hci_btsnoop_fd);
- plen = (int) p[1] + 2;
-
- btsnoop_write(p, 3, &evt, plen);
-}
-
-/*******************************************************************************
- **
- ** Function btsnoop_sco_data
- **
- ** Description Function to add a SCO data packet in the BTSNOOP file
- **
- ** Returns None
-*******************************************************************************/
-void btsnoop_sco_data(uint8_t *p, uint8_t is_rcvd)
-{
- const uint8_t sco = HCIT_TYPE_SCO_DATA;
- int plen;
- SNOOPDBG("btsnoop_sco_data: fd = %d", hci_btsnoop_fd);
- plen = (int) p[2] + 3;
-
- btsnoop_write(p, is_rcvd, &sco, plen);
-}
-
-/*******************************************************************************
- **
- ** Function btsnoop_acl_data
- **
- ** Description Function to add an ACL data packet in the BTSNOOP file
- **
- ** Returns None
-*******************************************************************************/
-void btsnoop_acl_data(uint8_t *p, uint8_t is_rcvd)
-{
- const uint8_t acl = HCIT_TYPE_ACL_DATA;
- int plen;
-
- SNOOPDBG("btsnoop_acl_data: fd = %d", hci_btsnoop_fd);
-
- plen = (((int) p[3]) << 8) + ((int) p[2]) +4;
-
- btsnoop_write(p, is_rcvd, &acl, plen);
+ utils_unlock();
}
/********************************************************************************
@@ -433,7 +401,7 @@
s = accept(s_listen, (struct sockaddr *) &cliaddr, &clilen);
if (s < 0)
-{
+ {
perror("accept");
return -1;
}
@@ -528,7 +496,9 @@
if (pthread_create(&thread_id, NULL,
(void*)ext_parser_thread,NULL)!=0)
perror("pthread_create");
+
#endif
+ btsnoop_net_init();
}
void btsnoop_open(char *p_path)
@@ -549,6 +519,7 @@
void btsnoop_cleanup (void)
{
+ btsnoop_net_cleanup();
#if defined(BTSNOOP_EXT_PARSER_INCLUDED) && (BTSNOOP_EXT_PARSER_INCLUDED == TRUE)
ALOGD("btsnoop_cleanup");
pthread_kill(thread_id, SIGUSR2);
@@ -605,24 +576,22 @@
{
case MSG_HC_TO_STACK_HCI_EVT:
SNOOPDBG("TYPE : EVT");
- btsnoop_hci_evt(p);
+ btsnoop_write_packet(kEventPacket, p, false);
break;
case MSG_HC_TO_STACK_HCI_ACL:
case MSG_STACK_TO_HC_HCI_ACL:
SNOOPDBG("TYPE : ACL");
- btsnoop_acl_data(p, is_rcvd);
+ btsnoop_write_packet(kAclPacket, p, is_rcvd);
break;
case MSG_HC_TO_STACK_HCI_SCO:
case MSG_STACK_TO_HC_HCI_SCO:
SNOOPDBG("TYPE : SCO");
- btsnoop_sco_data(p, is_rcvd);
+ btsnoop_write_packet(kScoPacket, p, is_rcvd);
break;
case MSG_STACK_TO_HC_HCI_CMD:
SNOOPDBG("TYPE : CMD");
- btsnoop_hci_cmd(p);
+ btsnoop_write_packet(kCommandPacket, p, true);
break;
}
#endif // BTSNOOPDISP_INCLUDED
}
-
-
diff --git a/hci/src/btsnoop_net.c b/hci/src/btsnoop_net.c
new file mode 100644
index 0000000..06cc01a
--- /dev/null
+++ b/hci/src/btsnoop_net.c
@@ -0,0 +1,134 @@
+/******************************************************************************
+ *
+ * Copyright (C) 2013 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+#include <assert.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#define LOG_TAG "btsnoop_net"
+#include <cutils/log.h>
+
+static void safe_close_(int *fd);
+static void *listen_fn_(void *context);
+
+static const char *LISTEN_THREAD_NAME_ = "btsnoop_net_listen";
+static const int LOCALHOST_ = 0x7F000001;
+static const int LISTEN_PORT_ = 8872;
+
+static pthread_t listen_thread_;
+static bool listen_thread_valid_ = false;
+static pthread_mutex_t client_socket_lock_ = PTHREAD_MUTEX_INITIALIZER;
+static int listen_socket_ = -1;
+static int client_socket_ = -1;
+
+void btsnoop_net_init() {
+ listen_thread_valid_ = (pthread_create(&listen_thread_, NULL, listen_fn_, NULL) == 0);
+ if (!listen_thread_valid_) {
+ ALOGE("%s pthread_create failed: %s", __func__, strerror(errno));
+ } else {
+ ALOGD("initialized");
+ }
+}
+
+void btsnoop_net_cleanup() {
+ if (listen_thread_valid_) {
+ shutdown(listen_socket_, SHUT_RDWR);
+ pthread_join(listen_thread_, NULL);
+ safe_close_(&client_socket_);
+ listen_thread_valid_ = false;
+ }
+}
+
+void btsnoop_net_write(const void *data, size_t length) {
+ pthread_mutex_lock(&client_socket_lock_);
+ if (client_socket_ != -1) {
+ if (send(client_socket_, data, length, 0) == -1 && errno == ECONNRESET) {
+ safe_close_(&client_socket_);
+ }
+ }
+ pthread_mutex_unlock(&client_socket_lock_);
+}
+
+static void *listen_fn_(void *context) {
+ prctl(PR_SET_NAME, (unsigned long)LISTEN_THREAD_NAME_, 0, 0, 0);
+
+ listen_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (listen_socket_ == -1) {
+ ALOGE("%s socket creation failed: %s", __func__, strerror(errno));
+ goto cleanup;
+ }
+
+ int enable = 1;
+ if (setsockopt(listen_socket_, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1) {
+ ALOGE("%s unable to set SO_REUSEADDR: %s", __func__, strerror(errno));
+ goto cleanup;
+ }
+
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(LOCALHOST_);
+ addr.sin_port = htons(LISTEN_PORT_);
+ if (bind(listen_socket_, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ ALOGE("%s unable to bind listen socket: %s", __func__, strerror(errno));
+ goto cleanup;
+ }
+
+ if (listen(listen_socket_, 10) == -1) {
+ ALOGE("%s unable to listen: %s", __func__, strerror(errno));
+ goto cleanup;
+ }
+
+ for (;;) {
+ ALOGD("waiting for client connection");
+ int client_socket = accept(listen_socket_, NULL, NULL);
+ if (client_socket == -1) {
+ if (errno == EINVAL || errno == EBADF) {
+ break;
+ }
+ ALOGW("%s error accepting socket: %s", __func__, strerror(errno));
+ continue;
+ }
+
+ /* When a new client connects, we have to send the btsnoop file header. This allows
+ a decoder to treat the session as a new, valid btsnoop file. */
+ ALOGI("client connected");
+ pthread_mutex_lock(&client_socket_lock_);
+ safe_close_(&client_socket_);
+ client_socket_ = client_socket;
+ send(client_socket_, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16, 0);
+ pthread_mutex_unlock(&client_socket_lock_);
+ }
+
+cleanup:
+ safe_close_(&listen_socket_);
+ return NULL;
+}
+
+static void safe_close_(int *fd) {
+ assert(fd != NULL);
+ if (*fd != -1) {
+ close(*fd);
+ *fd = -1;
+ }
+}