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

Commit eec1b70a authored by Chris Manton's avatar Chris Manton Committed by Automerger Merge Worker
Browse files

legacy: Truncate hci_btsnoop am: 40c7eaf8

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/1959423

Change-Id: I55c7d5b37f028dfb4a7b001403877ce49a57889d
parents 7243d97e 40c7eaf8
Loading
Loading
Loading
Loading
+0 −468
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 2014 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.
 *
 ******************************************************************************/

#define LOG_TAG "bt_snoop"

#include <mutex>

#include <arpa/inet.h>
#include <base/logging.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <unistd.h>
#include <mutex>
#include <unordered_map>
#include <unordered_set>

#include "common/time_util.h"
#include "hci/include/btsnoop.h"
#include "hci/include/btsnoop_mem.h"
#include "hci_layer.h"
#include "internal_include/bt_trace.h"
#include "main/shim/shim.h"
#include "osi/include/log.h"
#include "osi/include/properties.h"
#include "stack/include/bt_hdr.h"
#include "stack/include/hcimsgs.h"
#include "stack/include/rfcdefs.h"
#include "stack/l2cap/l2c_int.h"
#include "stack_config.h"

// The number of of packets per btsnoop file before we rotate to the next
// file. As of right now there are two snoop files that are rotated through.
// The size can be dynamically configured by seting the relevant system
// property
#define DEFAULT_BTSNOOP_SIZE 0xffff

#define IS_DEBUGGABLE_PROPERTY "ro.debuggable"

#define BTSNOOP_LOG_MODE_PROPERTY "persist.bluetooth.btsnooplogmode"
#define BTSNOOP_DEFAULT_MODE_PROPERTY "persist.bluetooth.btsnoopdefaultmode"
#define BTSNOOP_MODE_DISABLED "disabled"
#define BTSNOOP_MODE_FILTERED "filtered"
#define BTSNOOP_MODE_FULL "full"

#define BTSNOOP_PATH_PROPERTY "persist.bluetooth.btsnooppath"
#define DEFAULT_BTSNOOP_PATH "/data/misc/bluetooth/logs/btsnoop_hci.log"
#define BTSNOOP_MAX_PACKETS_PROPERTY "persist.bluetooth.btsnoopsize"

typedef enum {
  kCommandPacket = 1,
  kAclPacket = 2,
  kScoPacket = 3,
  kEventPacket = 4,
  kIsoPacket = 5,
} packet_type_t;

// Epoch in microseconds since 01/01/0000
static const uint64_t BTSNOOP_EPOCH_DELTA = 0x00dcddb30f2f8000ULL;

// Number of bytes into a packet where you can find the value for a channel.
static const size_t ACL_CHANNEL_OFFSET = 0;
static const size_t L2C_CHANNEL_OFFSET = 6;
static const size_t RFC_CHANNEL_OFFSET = 8;
static const size_t RFC_EVENT_OFFSET = 9;

// The size of the L2CAP header. All information past this point is removed from
// a filtered packet.
static const uint32_t L2C_HEADER_SIZE = 9;

static int logfile_fd = INVALID_FD;
static std::mutex btsnoop_mutex;

static int32_t packets_per_file;
static int32_t packet_counter;

// Channel tracking variables for filtering.

// Keeps track of L2CAP channels that need to be filtered out of the snoop
// logs.
class FilterTracker {
 public:
  // NOTE: 1 is used as a static CID for L2CAP signaling
  std::unordered_set<uint16_t> l2c_local_cid = {1};
  std::unordered_set<uint16_t> l2c_remote_cid = {1};
  uint16_t rfc_local_cid = 0;
  uint16_t rfc_remote_cid = 0;
  std::unordered_set<uint16_t> rfc_channels = {0};

  // Adds L2C channel to allowlist.
  void addL2cCid(uint16_t local_cid, uint16_t remote_cid) {
    l2c_local_cid.insert(local_cid);
    l2c_remote_cid.insert(remote_cid);
  }

  // Sets L2CAP channel that RFCOMM uses.
  void setRfcCid(uint16_t local_cid, uint16_t remote_cid) {
    rfc_local_cid = local_cid;
    rfc_remote_cid = remote_cid;
  }

  // Remove L2C channel from allowlist.
  void removeL2cCid(uint16_t local_cid, uint16_t remote_cid) {
    if (rfc_local_cid == local_cid) {
      rfc_channels.clear();
      rfc_channels.insert(0);
      rfc_local_cid = 0;
      rfc_remote_cid = 0;
    }

    l2c_local_cid.erase(local_cid);
    l2c_remote_cid.erase(remote_cid);
  }

  void addRfcDlci(uint8_t channel) { rfc_channels.insert(channel); }

  bool isAllowlistedL2c(bool local, uint16_t cid) {
    const auto& set = local ? l2c_local_cid : l2c_remote_cid;
    return (set.find(cid) != set.end());
  }

  bool isRfcChannel(bool local, uint16_t cid) {
    const auto& channel = local ? rfc_local_cid : rfc_remote_cid;
    return cid == channel;
  }

  bool isAllowlistedDlci(uint8_t dlci) {
    return rfc_channels.find(dlci) != rfc_channels.end();
  }
};

std::mutex filter_list_mutex;
std::unordered_map<uint16_t, FilterTracker> filter_list;
std::unordered_map<uint16_t, uint16_t> local_cid_to_acl;

// Cached value for whether full snoop logs are enabled. So the property isn't
// checked for every packet.
static bool is_btsnoop_enabled;
static bool is_btsnoop_filtered;

// TODO(zachoverflow): merge btsnoop and btsnoop_net together
void btsnoop_net_open();
void btsnoop_net_close();
void btsnoop_net_write(const void* data, size_t length);

static void delete_btsnoop_files(bool filtered);
static std::string get_btsnoop_log_path(bool filtered);
static std::string get_btsnoop_last_log_path(std::string log_path);
static void open_next_snoop_file();
static void btsnoop_write_packet(packet_type_t type, uint8_t* packet,
                                 bool is_received, uint64_t timestamp_us);

// Module lifecycle functions

static future_t* start_up() {
  std::array<char, PROPERTY_VALUE_MAX> property = {};
  std::lock_guard<std::mutex> lock(btsnoop_mutex);

  // Default mode is FILTERED on userdebug/eng build, DISABLED on user build.
  // It can also be overwritten by modifying the global setting.
  int is_debuggable = osi_property_get_int32(IS_DEBUGGABLE_PROPERTY, 0);
  std::string default_mode = BTSNOOP_MODE_DISABLED;
  if (is_debuggable) {
    int len = osi_property_get(BTSNOOP_DEFAULT_MODE_PROPERTY, property.data(),
                               BTSNOOP_MODE_DISABLED);
    default_mode = std::string(property.data(), len);
  }

  // Get the actual mode
  int len = osi_property_get(BTSNOOP_LOG_MODE_PROPERTY, property.data(),
                             default_mode.c_str());
  std::string btsnoop_mode(property.data(), len);

  if (btsnoop_mode == BTSNOOP_MODE_FILTERED) {
    LOG(INFO) << __func__ << ": Filtered Snoop Logs enabled";
    is_btsnoop_enabled = true;
    is_btsnoop_filtered = true;
    delete_btsnoop_files(false);
  } else if (btsnoop_mode == BTSNOOP_MODE_FULL) {
    LOG(INFO) << __func__ << ": Snoop Logs fully enabled";
    is_btsnoop_enabled = true;
    is_btsnoop_filtered = false;
    delete_btsnoop_files(true);
  } else {
    LOG(INFO) << __func__ << ": Snoop Logs disabled";
    is_btsnoop_enabled = false;
    is_btsnoop_filtered = false;
    delete_btsnoop_files(true);
    delete_btsnoop_files(false);
  }

  if (is_btsnoop_enabled) {
    open_next_snoop_file();
    packets_per_file = osi_property_get_int32(BTSNOOP_MAX_PACKETS_PROPERTY,
                                              DEFAULT_BTSNOOP_SIZE);
    btsnoop_net_open();
  }

  return NULL;
}

static future_t* shut_down(void) {
  std::lock_guard<std::mutex> lock(btsnoop_mutex);

  if (is_btsnoop_enabled) {
    if (is_btsnoop_filtered) {
      delete_btsnoop_files(false);
    } else {
      delete_btsnoop_files(true);
    }
  } else {
    delete_btsnoop_files(true);
    delete_btsnoop_files(false);
  }

  if (logfile_fd != INVALID_FD) close(logfile_fd);
  logfile_fd = INVALID_FD;

  if (is_btsnoop_enabled) btsnoop_net_close();

  return NULL;
}

EXPORT_SYMBOL extern const module_t btsnoop_module = {
    .name = BTSNOOP_MODULE,
    .init = NULL,
    .start_up = start_up,
    .shut_down = shut_down,
    .clean_up = NULL,
    .dependencies = {STACK_CONFIG_MODULE, NULL}};

// Interface functions
static void capture(const BT_HDR* buffer, bool is_received) {
  uint8_t* p = const_cast<uint8_t*>(buffer->data + buffer->offset);

  std::lock_guard<std::mutex> lock(btsnoop_mutex);

  struct timespec ts_now = {};
  clock_gettime(CLOCK_REALTIME, &ts_now);
  uint64_t timestamp_us =
      ((uint64_t)ts_now.tv_sec * 1000000L) + ((uint64_t)ts_now.tv_nsec / 1000);

  btsnoop_mem_capture(buffer, timestamp_us);

  if (logfile_fd == INVALID_FD) return;

  switch (buffer->event & MSG_EVT_MASK) {
    case MSG_HC_TO_STACK_HCI_EVT:
      btsnoop_write_packet(kEventPacket, p, false, timestamp_us);
      break;
    case MSG_HC_TO_STACK_HCI_ACL:
    case MSG_STACK_TO_HC_HCI_ACL:
      btsnoop_write_packet(kAclPacket, p, is_received, timestamp_us);
      break;
    case MSG_HC_TO_STACK_HCI_SCO:
    case MSG_STACK_TO_HC_HCI_SCO:
      btsnoop_write_packet(kScoPacket, p, is_received, timestamp_us);
      break;
    case MSG_STACK_TO_HC_HCI_CMD:
      btsnoop_write_packet(kCommandPacket, p, true, timestamp_us);
      break;
    case MSG_HC_TO_STACK_HCI_ISO:
    case MSG_STACK_TO_HC_HCI_ISO:
      btsnoop_write_packet(kIsoPacket, p, is_received, timestamp_us);
      break;
  }
}

static void allowlist_l2c_channel(uint16_t conn_handle, uint16_t local_cid,
                                  uint16_t remote_cid) {
  return;
}

static void allowlist_rfc_dlci(uint16_t local_cid, uint8_t dlci) {
    return;
}

static void add_rfc_l2c_channel(uint16_t conn_handle, uint16_t local_cid,
                                uint16_t remote_cid) {
  return;
}

static void clear_l2cap_allowlist(uint16_t conn_handle, uint16_t local_cid,
                                  uint16_t remote_cid) {
  return;
}

static const btsnoop_t interface = {capture, allowlist_l2c_channel,
                                    allowlist_rfc_dlci, add_rfc_l2c_channel,
                                    clear_l2cap_allowlist};

const btsnoop_t* btsnoop_get_interface() { return &interface; }

static void delete_btsnoop_files(bool filtered) {
  LOG(INFO) << __func__
            << ": Deleting snoop logs if they exist. filtered = " << filtered;
  auto log_path = get_btsnoop_log_path(filtered);
  remove(log_path.c_str());
  remove(get_btsnoop_last_log_path(log_path).c_str());
}

std::string get_btsnoop_log_path(bool filtered) {
  char btsnoop_path[PROPERTY_VALUE_MAX];
  osi_property_get(BTSNOOP_PATH_PROPERTY, btsnoop_path, DEFAULT_BTSNOOP_PATH);
  std::string result(btsnoop_path);
  if (filtered) result = result.append(".filtered");

  return result;
}

std::string get_btsnoop_last_log_path(std::string btsnoop_path) {
  return btsnoop_path.append(".last");
}

static void open_next_snoop_file() {
  packet_counter = 0;

  if (logfile_fd != INVALID_FD) {
    close(logfile_fd);
    logfile_fd = INVALID_FD;
  }

  auto log_path = get_btsnoop_log_path(is_btsnoop_filtered);
  auto last_log_path = get_btsnoop_last_log_path(log_path);

  if (rename(log_path.c_str(), last_log_path.c_str()) != 0 && errno != ENOENT)
    LOG(ERROR) << __func__ << ": unable to rename '" << log_path << "' to '"
               << last_log_path << "' : " << strerror(errno);

  mode_t prevmask = umask(0);
  logfile_fd = open(log_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
                    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
  umask(prevmask);
  if (logfile_fd == INVALID_FD) {
    LOG(ERROR) << __func__ << ": unable to open '" << log_path
               << "' : " << strerror(errno);
    return;
  }

  (void)write(logfile_fd, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16);
}

typedef struct {
  uint32_t length_original;
  uint32_t length_captured;
  uint32_t flags;
  uint32_t dropped_packets;
  uint64_t timestamp;
  uint8_t type;
} __attribute__((__packed__)) btsnoop_header_t;

static uint64_t htonll(uint64_t ll) {
  const uint32_t l = 1;
  if (*(reinterpret_cast<const uint8_t*>(&l)) == 1)
    return static_cast<uint64_t>(htonl(ll & 0xffffffff)) << 32 |
           htonl(ll >> 32);

  return ll;
}

static bool should_filter_log(bool is_received, uint8_t* packet) {
  uint16_t acl_handle =
      HCID_GET_HANDLE((((uint16_t)packet[ACL_CHANNEL_OFFSET + 1]) << 8) +
                      packet[ACL_CHANNEL_OFFSET]);

  std::lock_guard lock(filter_list_mutex);
  auto& filters = filter_list[acl_handle];
  uint16_t l2c_channel =
      (packet[L2C_CHANNEL_OFFSET + 1] << 8) + packet[L2C_CHANNEL_OFFSET];
  if (filters.isRfcChannel(is_received, l2c_channel)) {
    uint8_t rfc_event = packet[RFC_EVENT_OFFSET] & 0b11101111;
    if (rfc_event == RFCOMM_SABME || rfc_event == RFCOMM_UA) {
      return false;
    }

    uint8_t rfc_dlci = packet[RFC_CHANNEL_OFFSET] >> 2;
    if (!filters.isAllowlistedDlci(rfc_dlci)) {
      return true;
    }
  } else if (!filters.isAllowlistedL2c(is_received, l2c_channel)) {
    return true;
  }

  return false;
}

static void btsnoop_write_packet(packet_type_t type, uint8_t* packet,
                                 bool is_received, uint64_t timestamp_us) {
  uint32_t length_he = 0;
  uint32_t flags = 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;
    case kIsoPacket:
      length_he = ((packet[3] & 0x3f) << 8) + packet[2] + 5;
      flags = is_received;
      break;
  }

  btsnoop_header_t header;
  header.length_original = htonl(length_he);

  bool rejectlisted = false;
  if (is_btsnoop_filtered && type == kAclPacket) {
    rejectlisted = should_filter_log(is_received, packet);
  }

  header.length_captured =
      rejectlisted ? htonl(L2C_HEADER_SIZE) : header.length_original;
  if (rejectlisted) length_he = L2C_HEADER_SIZE;
  header.flags = htonl(flags);
  header.dropped_packets = 0;
  header.timestamp = htonll(timestamp_us + BTSNOOP_EPOCH_DELTA);
  header.type = type;

  btsnoop_net_write(&header, sizeof(btsnoop_header_t));
  btsnoop_net_write(packet, length_he - 1);

  if (logfile_fd != INVALID_FD) {
    packet_counter++;
    if (packet_counter > packets_per_file) {
      open_next_snoop_file();
    }

    iovec iov[] = {{&header, sizeof(btsnoop_header_t)},
                   {reinterpret_cast<void*>(packet), length_he - 1}};
    TEMP_FAILURE_RETRY(writev(logfile_fd, iov, 2));
  }
}
+0 −75
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 2015 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 <base/logging.h>

#include "check.h"
#include "gd/common/init_flags.h"
#include "hci/include/btsnoop_mem.h"
#include "stack/include/bt_types.h"

static btsnoop_data_cb data_callback = NULL;
static activity_attribution_cb attribution_callback = NULL;

void btsnoop_mem_set_callback(btsnoop_data_cb cb) { data_callback = cb; }

void activity_attribution_set_callback(activity_attribution_cb cb) {
  attribution_callback = cb;
}

void btsnoop_mem_capture(const BT_HDR* packet, uint64_t timestamp_us) {
  if (!data_callback && !attribution_callback) return;

  CHECK(packet);

  const uint8_t* data = &packet->data[packet->offset];
  const uint16_t type = packet->event & BT_EVT_MASK;
  size_t length = 0;

  switch (type) {
    case BT_EVT_TO_LM_HCI_CMD:
      if (packet->len > 2) length = data[2] + 3;
      break;

    case BT_EVT_TO_BTU_HCI_EVT:
      if (packet->len > 1) length = data[1] + 2;
      break;

    case BT_EVT_TO_LM_HCI_ACL:
    case BT_EVT_TO_BTU_HCI_ACL:
      if (packet->len > 3) length = (data[2] | (data[3] << 8)) + 4;
      break;

    case BT_EVT_TO_LM_HCI_SCO:
    case BT_EVT_TO_BTU_HCI_SCO:
      if (packet->len > 2) length = data[2] + 3;
      break;

    case BT_EVT_TO_LM_HCI_ISO:
    case BT_EVT_TO_BTU_HCI_ISO:
      if (packet->len > 3) length = (data[2] | ((data[3] & 0x3f) << 8)) + 4;
      break;
  }

  if (length && data_callback)
    (*data_callback)(type, data, length, timestamp_us);
  if (length && attribution_callback &&
      bluetooth::common::init_flags::btaa_hci_is_enabled()) {
    (*attribution_callback)(type, data, length, timestamp_us);
  }
}
+0 −154
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 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.
 *
 ******************************************************************************/

#define LOG_TAG "bt_snoop_net"

#include <base/logging.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <mutex>

#include "check.h"
#include "osi/include/log.h"
#include "osi/include/osi.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 std::mutex client_socket_mutex_;
static int listen_socket_ = -1;
static int client_socket_ = -1;

void btsnoop_net_open() {
#if (BT_NET_DEBUG != true)
  return;  // Disable using network sockets for security reasons
#endif

  listen_thread_valid_ =
      (pthread_create(&listen_thread_, NULL, listen_fn_, NULL) == 0);
  if (!listen_thread_valid_)
    LOG_ERROR("%s pthread_create failed: %s", __func__, strerror(errno));
}

void btsnoop_net_close() {
#if (BT_NET_DEBUG != true)
  return;  // Disable using network sockets for security reasons
#endif

  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) {
#if (BT_NET_DEBUG != true)
  return;  // Disable using network sockets for security reasons
#endif

  std::lock_guard<std::mutex> lock(client_socket_mutex_);
  if (client_socket_ != -1) {
    ssize_t ret;
    OSI_NO_INTR(ret = send(client_socket_, data, length, 0));

    if (ret == -1 && errno == ECONNRESET) {
      safe_close_(&client_socket_);
    }
  }
}

static void* listen_fn_(UNUSED_ATTR void* context) {
  int enable = 1;

  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) {
    LOG_ERROR("%s socket creation failed: %s", __func__, strerror(errno));
    goto cleanup;
  }

  if (setsockopt(listen_socket_, SOL_SOCKET, SO_REUSEADDR, &enable,
                 sizeof(enable)) == -1) {
    LOG_ERROR("%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) {
    LOG_ERROR("%s unable to bind listen socket: %s", __func__, strerror(errno));
    goto cleanup;
  }

  if (listen(listen_socket_, 10) == -1) {
    LOG_ERROR("%s unable to listen: %s", __func__, strerror(errno));
    goto cleanup;
  }

  for (;;) {
    int client_socket;
    OSI_NO_INTR(client_socket = accept(listen_socket_, NULL, NULL));
    if (client_socket == -1) {
      if (errno == EINVAL || errno == EBADF) {
        break;
      }
      LOG_WARN("%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. */
    std::lock_guard<std::mutex> lock(client_socket_mutex_);
    safe_close_(&client_socket_);
    client_socket_ = client_socket;

    OSI_NO_INTR(send(client_socket_, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16, 0));
  }

cleanup:
  safe_close_(&listen_socket_);
  return NULL;
}

static void safe_close_(int* fd) {
  CHECK(fd != NULL);
  if (*fd != -1) {
    close(*fd);
    *fd = -1;
  }
}