/****************************************************************************** * * Copyright (C) 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 <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 <unistd.h> #include "bt_types.h" #include "hci/include/btsnoop.h" #include "hci/include/btsnoop_mem.h" #include "hci_layer.h" #include "osi/include/log.h" #include "osi/include/properties.h" #include "osi/include/time.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 BTSNOOP_ENABLE_PROPERTY "persist.bluetooth.btsnoopenable" #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 } packet_type_t; // Epoch in microseconds since 01/01/0000. static const uint64_t BTSNOOP_EPOCH_DELTA = 0x00dcddb30f2f8000ULL; static int logfile_fd = INVALID_FD; static std::mutex btsnoop_mutex; static int32_t packets_per_file; static int32_t packet_counter; // 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(); static bool is_btsnoop_enabled(); static char* get_btsnoop_log_path(char* log_path); static char* get_btsnoop_last_log_path(char* last_log_path, char* 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(void) { std::lock_guard<std::mutex> lock(btsnoop_mutex); if (!is_btsnoop_enabled()) { delete_btsnoop_files(); } else { 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()) { delete_btsnoop_files(); } if (logfile_fd != INVALID_FD) close(logfile_fd); logfile_fd = INVALID_FD; 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); uint64_t timestamp_us = time_gettimeofday_us(); 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; } } static const btsnoop_t interface = {capture}; const btsnoop_t* btsnoop_get_interface() { return &interface; } // Internal functions static void delete_btsnoop_files() { LOG_VERBOSE(LOG_TAG, "Deleting snoop log if it exists"); char log_path[PROPERTY_VALUE_MAX]; char last_log_path[PROPERTY_VALUE_MAX + sizeof(".last")]; get_btsnoop_log_path(log_path); get_btsnoop_last_log_path(last_log_path, log_path); remove(log_path); remove(last_log_path); } static bool is_btsnoop_enabled() { char btsnoop_enabled[PROPERTY_VALUE_MAX] = {0}; osi_property_get(BTSNOOP_ENABLE_PROPERTY, btsnoop_enabled, "false"); return strncmp(btsnoop_enabled, "true", 4) == 0; } static char* get_btsnoop_log_path(char* btsnoop_path) { osi_property_get(BTSNOOP_PATH_PROPERTY, btsnoop_path, DEFAULT_BTSNOOP_PATH); return btsnoop_path; } static char* get_btsnoop_last_log_path(char* last_log_path, char* btsnoop_path) { snprintf(last_log_path, PROPERTY_VALUE_MAX + sizeof(".last"), "%s.last", btsnoop_path); return last_log_path; } static void open_next_snoop_file() { packet_counter = 0; if (logfile_fd != INVALID_FD) { close(logfile_fd); logfile_fd = INVALID_FD; } char log_path[PROPERTY_VALUE_MAX]; char last_log_path[PROPERTY_VALUE_MAX + sizeof(".last")]; get_btsnoop_log_path(log_path); get_btsnoop_last_log_path(last_log_path, log_path); if (!rename(log_path, last_log_path) && errno != ENOENT) LOG_ERROR(LOG_TAG, "%s unable to rename '%s' to '%s': %s", __func__, log_path, last_log_path, strerror(errno)); mode_t prevmask = umask(0); logfile_fd = open(log_path, 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(LOG_TAG, "%s unable to open '%s': %s", __func__, log_path, strerror(errno)); return; } 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 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; } btsnoop_header_t header; header.length_original = htonl(length_he); header.length_captured = header.length_original; 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)); } }