Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ee0166c authored by Sharvil Nanavati's avatar Sharvil Nanavati
Browse files

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
parent 10452592
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
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
```
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ LOCAL_SRC_FILES := \
        src/lpm.c \
        src/bt_hw.c \
        src/btsnoop.c \
        src/btsnoop_net.c \
        src/utils.c

ifeq ($(BLUETOOTH_HCI_USE_MCT),true)
+77 −108
Original line number Diff line number Diff line
@@ -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;
@@ -258,118 +265,79 @@ static int btsnoop_log_close(void)
#endif
}

/*******************************************************************************
 ** Function          btsnoop_write
 **
 ** Description       Function used to write the actual data to the log
 **
 ** Returns           none
*******************************************************************************/

void btsnoop_write(uint8_t *p, uint32_t flags, const uint8_t *ptype, uint32_t len)
{
    uint32_t value, value_hi;
    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]);

    io[0].iov_base = header;
    io[0].iov_len = sizeof(header);

    io[1].iov_base = (void*)ptype;
    io[1].iov_len = 1;

    io[2].iov_base = p;
    io[2].iov_len = len;

    (void) writev(hci_btsnoop_fd, io, 3);
}

/*******************************************************************************
 **
 ** Function         btsnoop_hci_cmd
 ** Function         btsnoop_write
 **
 ** Description      Function to add a command in the BTSNOOP file
 ** Description      Writes raw bytes to the BTSNOOP sinks.
 **
 ** 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);
static void btsnoop_write(const void *data, size_t length) {
    if (hci_btsnoop_fd != -1) {
        write(hci_btsnoop_fd, data, length);
    }


/*******************************************************************************
 **
 ** 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);
    btsnoop_net_write(data, length);
}

/*******************************************************************************
 **
 ** Function         btsnoop_sco_data
 ** Function         btsnoop_write_packet
 **
 ** Description      Function to add a SCO data packet in the BTSNOOP file
 ** Description      Writes a single HCI packet to BTSNOOP sinks.
 **
 ** 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);
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;
    }

/*******************************************************************************
 **
 ** 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;
    uint32_t time_hi, time_lo;
    struct timeval tv;
    gettimeofday(&tv, NULL);
    tv_to_btsnoop_ts(&time_lo, &time_hi, &tv);

    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);

    SNOOPDBG("btsnoop_acl_data: fd = %d", hci_btsnoop_fd);
    /* since these display functions are called from different contexts */
    utils_lock();

    plen = (((int) p[3]) << 8) + ((int) p[2]) +4;
    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);

    btsnoop_write(p, is_rcvd, &acl, plen);
    utils_unlock();
}

/********************************************************************************
@@ -528,7 +496,9 @@ void btsnoop_init(void)
    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_close(void)

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 @@ void btsnoop_capture(HC_BT_HDR *p_buf, uint8_t is_rcvd)
    {
        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
}

+134 −0
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  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;
  }
}