/******************************************************************************
|
*
|
* 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 "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
|
// 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
|
} 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 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(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;
|
}
|
}
|
|
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);
|
|
// This will create the entry if there is no associated filter with the
|
// connection.
|
filter_list[conn_handle].addL2cCid(local_cid, remote_cid);
|
}
|
|
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 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 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);
|
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;
|
}
|
|
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.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;
|
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);
|
|
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);
|
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));
|
}
|
}
|