#include <mutex>
#include <arpa/inet.h>
+#include <base/logging.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <unistd.h>
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
#include "bt_types.h"
#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 "osi/include/log.h"
#include "osi/include/properties.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
// property
#define DEFAULT_BTSNOOP_SIZE 0xffff
-#define BTSNOOP_ENABLE_PROPERTY "persist.bluetooth.btsnoopenable"
+#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"
// 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 whitelist.
+ 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 whitelist.
+ 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 isWhitelistedL2c(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 isWhitelistedDlci(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();
-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 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(void) {
+static future_t* start_up() {
+ std::array<char, PROPERTY_VALUE_MAX> property = {};
std::lock_guard<std::mutex> lock(btsnoop_mutex);
- if (!is_btsnoop_enabled()) {
- delete_btsnoop_files();
+ // 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_FILTERED);
+ 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);
static future_t* shut_down(void) {
std::lock_guard<std::mutex> lock(btsnoop_mutex);
- if (!is_btsnoop_enabled()) {
- delete_btsnoop_files();
+ 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;
- btsnoop_net_close();
+ if (is_btsnoop_enabled) btsnoop_net_close();
return NULL;
}
uint8_t* p = const_cast<uint8_t*>(buffer->data + buffer->offset);
std::lock_guard<std::mutex> lock(btsnoop_mutex);
- uint64_t timestamp_us = bluetooth::common::time_gettimeofday_us();
+
+ 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;
}
}
-static const btsnoop_t interface = {capture};
+static void whitelist_l2c_channel(uint16_t conn_handle, uint16_t local_cid,
+ uint16_t remote_cid) {
+ LOG(INFO) << __func__
+ << ": Whitelisting l2cap channel. conn_handle=" << conn_handle
+ << " cid=" << loghex(local_cid) << ":" << loghex(remote_cid);
+ std::lock_guard lock(filter_list_mutex);
-const btsnoop_t* btsnoop_get_interface() {
- return &interface;
+ // This will create the entry if there is no associated filter with the
+ // connection.
+ filter_list[conn_handle].addL2cCid(local_cid, remote_cid);
}
-// 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 void whitelist_rfc_dlci(uint16_t local_cid, uint8_t dlci) {
+ LOG(INFO) << __func__
+ << ": Whitelisting rfcomm channel. L2CAP CID=" << loghex(local_cid)
+ << " DLCI=" << loghex(dlci);
+ std::lock_guard lock(filter_list_mutex);
+
+ tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(nullptr, local_cid);
+ filter_list[p_ccb->p_lcb->handle].addRfcDlci(dlci);
}
-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 void add_rfc_l2c_channel(uint16_t conn_handle, uint16_t local_cid,
+ uint16_t remote_cid) {
+ LOG(INFO) << __func__
+ << ": rfcomm data going over l2cap channel. conn_handle="
+ << conn_handle << " cid=" << loghex(local_cid) << ":"
+ << loghex(remote_cid);
+ std::lock_guard lock(filter_list_mutex);
+
+ filter_list[conn_handle].setRfcCid(local_cid, remote_cid);
+ local_cid_to_acl.insert({local_cid, conn_handle});
}
-static char* get_btsnoop_log_path(char* btsnoop_path) {
+static void clear_l2cap_whitelist(uint16_t conn_handle, uint16_t local_cid,
+ uint16_t remote_cid) {
+ LOG(INFO) << __func__
+ << ": Clearing whitelist from l2cap channel. conn_handle="
+ << conn_handle << " cid=" << local_cid << ":" << remote_cid;
+
+ std::lock_guard lock(filter_list_mutex);
+ filter_list[conn_handle].removeL2cCid(local_cid, remote_cid);
+}
+
+static const btsnoop_t interface = {capture, whitelist_l2c_channel,
+ whitelist_rfc_dlci, add_rfc_l2c_channel,
+ clear_l2cap_whitelist};
+
+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);
- return btsnoop_path;
+ std::string result(btsnoop_path);
+ if (filtered) result = result.append(".filtered");
+
+ return result;
}
-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;
+std::string get_btsnoop_last_log_path(std::string btsnoop_path) {
+ return btsnoop_path.append(".last");
}
static void open_next_snoop_file() {
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);
+ 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, last_log_path) != 0 && errno != ENOENT)
- LOG_ERROR(LOG_TAG, "%s unable to rename '%s' to '%s': %s", __func__,
- log_path, last_log_path, strerror(errno));
+ 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, O_WRONLY | O_CREAT | O_TRUNC,
+ 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(LOG_TAG, "%s unable to open '%s': %s", __func__, log_path,
- strerror(errno));
+ LOG(ERROR) << __func__ << ": unable to open '" << log_path
+ << "' : " << strerror(errno);
return;
}
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.isWhitelistedDlci(rfc_dlci)) {
+ return true;
+ }
+ } else if (!filters.isWhitelistedL2c(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;
btsnoop_header_t header;
header.length_original = htonl(length_he);
- header.length_captured = header.length_original;
+
+ bool blacklisted = false;
+ if (is_btsnoop_filtered && type == kAclPacket) {
+ blacklisted = should_filter_log(is_received, packet);
+ }
+
+ header.length_captured =
+ blacklisted ? htonl(L2C_HEADER_SIZE) : header.length_original;
+ if (blacklisted) length_he = L2C_HEADER_SIZE;
header.flags = htonl(flags);
header.dropped_packets = 0;
header.timestamp = htonll(timestamp_us + BTSNOOP_EPOCH_DELTA);