| /****************************************************************************** |
| * |
| * 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 <arpa/inet.h> |
| #include <assert.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 "stack_config.h" |
| |
| 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 const stack_config_t *stack_config; |
| |
| static int logfile_fd = INVALID_FD; |
| static bool module_started; |
| static bool is_logging; |
| static bool logging_enabled_via_api; |
| |
| // 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 btsnoop_write_packet(packet_type_t type, const uint8_t *packet, bool is_received); |
| static void update_logging(); |
| |
| // Module lifecycle functions |
| |
| static future_t *start_up(void) { |
| module_started = true; |
| update_logging(); |
| |
| return NULL; |
| } |
| |
| static future_t *shut_down(void) { |
| module_started = false; |
| update_logging(); |
| |
| 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 set_api_wants_to_log(bool value) { |
| logging_enabled_via_api = value; |
| update_logging(); |
| } |
| |
| static void capture(const BT_HDR *buffer, bool is_received) { |
| const uint8_t *p = buffer->data + buffer->offset; |
| |
| btsnoop_mem_capture(buffer); |
| |
| 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); |
| break; |
| case MSG_HC_TO_STACK_HCI_ACL: |
| case MSG_STACK_TO_HC_HCI_ACL: |
| btsnoop_write_packet(kAclPacket, p, is_received); |
| break; |
| case MSG_HC_TO_STACK_HCI_SCO: |
| case MSG_STACK_TO_HC_HCI_SCO: |
| btsnoop_write_packet(kScoPacket, p, is_received); |
| break; |
| case MSG_STACK_TO_HC_HCI_CMD: |
| btsnoop_write_packet(kCommandPacket, p, true); |
| break; |
| } |
| } |
| |
| static const btsnoop_t interface = { |
| set_api_wants_to_log, |
| capture |
| }; |
| |
| const btsnoop_t *btsnoop_get_interface() { |
| stack_config = stack_config_get_interface(); |
| return &interface; |
| } |
| |
| // Internal functions |
| |
| static uint64_t btsnoop_timestamp(void) { |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| |
| // Timestamp is in microseconds. |
| uint64_t timestamp = tv.tv_sec; |
| timestamp *= (uint64_t)1000000ULL; |
| timestamp += tv.tv_usec; |
| timestamp += BTSNOOP_EPOCH_DELTA; |
| return timestamp; |
| } |
| |
| static void update_logging() { |
| bool should_log = module_started && |
| (logging_enabled_via_api || stack_config->get_btsnoop_turned_on()); |
| |
| if (should_log == is_logging) |
| return; |
| |
| is_logging = should_log; |
| if (should_log) { |
| const char *log_path = stack_config->get_btsnoop_log_path(); |
| |
| // Save the old log if configured to do so |
| if (stack_config->get_btsnoop_should_save_last()) { |
| char last_log_path[PATH_MAX]; |
| snprintf(last_log_path, PATH_MAX, "%s.%" PRIu64, log_path, |
| btsnoop_timestamp()); |
| 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); |
| if (logfile_fd == INVALID_FD) { |
| LOG_ERROR(LOG_TAG, "%s unable to open '%s': %s", __func__, log_path, strerror(errno)); |
| is_logging = false; |
| umask(prevmask); |
| return; |
| } |
| umask(prevmask); |
| |
| write(logfile_fd, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16); |
| btsnoop_net_open(); |
| } else { |
| if (logfile_fd != INVALID_FD) |
| close(logfile_fd); |
| |
| logfile_fd = INVALID_FD; |
| btsnoop_net_close(); |
| } |
| } |
| |
| static void btsnoop_write(const void *data, size_t length) { |
| if (logfile_fd != INVALID_FD) |
| write(logfile_fd, data, length); |
| |
| btsnoop_net_write(data, length); |
| } |
| |
| static void btsnoop_write_packet(packet_type_t type, const uint8_t *packet, bool is_received) { |
| int length_he = 0; |
| 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; |
| } |
| |
| uint64_t timestamp = btsnoop_timestamp(); |
| uint32_t time_hi = timestamp >> 32; |
| uint32_t time_lo = timestamp & 0xFFFFFFFF; |
| |
| length = htonl(length_he); |
| flags = htonl(flags); |
| drops = htonl(drops); |
| time_hi = htonl(time_hi); |
| time_lo = htonl(time_lo); |
| |
| 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); |
| } |