tools: Add utility for proxying/forwarding HCI controllers

This commit is contained in:
Marcel Holtmann 2013-12-30 20:48:31 -08:00
parent d0e729fd75
commit 43177237f7
3 changed files with 393 additions and 2 deletions

1
.gitignore vendored
View File

@ -64,6 +64,7 @@ tools/mpris-player
tools/bluetooth-player
tools/l2cap-tester
tools/sco-tester
tools/btproxy
tools/btinfo
tools/3dsp
tools/obexctl

View File

@ -192,8 +192,8 @@ noinst_PROGRAMS += tools/bdaddr tools/avinfo tools/avtest \
tools/scotest tools/amptest tools/hwdb \
tools/hcieventmask tools/hcisecfilter \
tools/btmgmt tools/btinfo tools/btattach \
tools/btsnoop tools/btiotest tools/cltest \
tools/mpris-player
tools/btsnoop tools/btproxy tools/btiotest \
tools/mpris-player tools/cltest
tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c
tools_bdaddr_LDADD = lib/libbluetooth-internal.la @UDEV_LIBS@
@ -221,6 +221,9 @@ tools_btsnoop_SOURCES = tools/btsnoop.c \
src/shared/pcap.h src/shared/pcap.c \
src/shared/btsnoop.h src/shared/btsnoop.c
tools_btproxy_SOURCES = tools/btproxy.c monitor/bt.h \
monitor/mainloop.h monitor/mainloop.c
tools_btiotest_SOURCES = tools/btiotest.c btio/btio.h btio/btio.c
tools_btiotest_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@

387
tools/btproxy.c Normal file
View File

@ -0,0 +1,387 @@
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2011-2012 Intel Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "monitor/mainloop.h"
#include "monitor/bt.h"
#define le16_to_cpu(val) (val)
#define cpu_to_le16(val) (val)
#define BTPROTO_HCI 1
struct sockaddr_hci {
sa_family_t hci_family;
unsigned short hci_dev;
unsigned short hci_channel;
};
#define HCI_CHANNEL_USER 1
static uint16_t hci_index = 0;
static int channel_fd = -1;
static int server_fd = -1;
static int client_fd = -1;
static uint8_t client_buffer[4096];
static uint8_t client_length = 0;
static int open_channel(uint16_t index)
{
struct sockaddr_hci addr;
int fd;
fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
if (fd < 0) {
perror("Failed to open Bluetooth socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = index;
addr.hci_channel = HCI_CHANNEL_USER;
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(fd);
perror("Failed to bind Bluetooth socket");
return -1;
}
return fd;
}
static void channel_callback(int fd, uint32_t events, void *user_data)
{
unsigned char buf[4096];
ssize_t len, written;
if (events & (EPOLLERR | EPOLLHUP)) {
printf("Device disconnected\n");
mainloop_remove_fd(channel_fd);
close(channel_fd);
channel_fd = -1;
mainloop_remove_fd(client_fd);
close(client_fd);
client_fd = -1;
return;
}
len = read(channel_fd, buf, sizeof(buf));
if (len < 1) {
fprintf(stderr, "Failed to read channel packet\n");
return;
}
written = write(client_fd, buf, len);
if (written < 1) {
fprintf(stderr, "Failed to write to unix socket\n");
return;
}
}
static void client_callback(int fd, uint32_t events, void *user_data)
{
ssize_t len, written;
uint16_t pktlen;
if (events & (EPOLLERR | EPOLLHUP)) {
printf("Client disconnected\n");
mainloop_remove_fd(channel_fd);
close(channel_fd);
channel_fd = -1;
mainloop_remove_fd(client_fd);
close(client_fd);
client_fd = -1;
return;
}
len = read(client_fd, client_buffer + client_length,
sizeof(client_buffer) - client_length);
if (len < 1) {
fprintf(stderr, "Failed to read unix packet\n");
return;
}
client_length += len;
switch (client_buffer[0]) {
case BT_H4_CMD_PKT:
{
struct bt_hci_cmd_hdr *hdr;
if (client_length < 1 + sizeof(*hdr))
return;
hdr = (void *) (client_buffer + 1);
pktlen = 1 + sizeof(*hdr) + hdr->plen;
}
break;
case BT_H4_ACL_PKT:
{
struct bt_hci_acl_hdr *hdr;
if (client_length < 1 + sizeof(*hdr))
return;
hdr = (void *) (client_buffer + 1);
pktlen = 1 + sizeof(*hdr) + cpu_to_le16(hdr->dlen);
}
break;
case BT_H4_SCO_PKT:
{
struct bt_hci_sco_hdr *hdr;
if (client_length < 1 + sizeof(*hdr))
return;
hdr = (void *) (client_buffer + 1);
pktlen = 1 + sizeof(*hdr) + hdr->dlen;
}
break;
default:
fprintf(stderr, "Received wrong packet type\n");
return;
}
if (client_length < pktlen)
return;
written = write(channel_fd, client_buffer, pktlen);
if (written < 0) {
fprintf(stderr, "Failed to write channel packet\n");
return;
}
if (client_length > pktlen) {
memmove(client_buffer, client_buffer + pktlen,
client_length - pktlen);
client_length -= pktlen;
}
}
static void server_callback(int fd, uint32_t events, void *user_data)
{
struct sockaddr_un addr;
socklen_t len;
int nfd;
if (events & (EPOLLERR | EPOLLHUP)) {
mainloop_quit();
return;
}
memset(&addr, 0, sizeof(addr));
len = sizeof(addr);
if (getsockname(fd, (struct sockaddr *) &addr, &len) < 0) {
perror("Failed to get socket name");
return;
}
nfd = accept(fd, (struct sockaddr *) &addr, &len);
if (nfd < 0) {
perror("Failed to accept client socket");
return;
}
if (client_fd >= 0) {
fprintf(stderr, "Active client already present\n");
close(nfd);
return;
}
channel_fd = open_channel(hci_index);
if (channel_fd < 0) {
close(nfd);
return;
}
printf("New client connected\n");
if (mainloop_add_fd(channel_fd, EPOLLIN, channel_callback,
NULL, NULL) < 0) {
close(nfd);
close(channel_fd);
channel_fd = -1;
return;
}
if (mainloop_add_fd(nfd, EPOLLIN, client_callback, NULL, NULL) < 0) {
close(nfd);
close(channel_fd);
channel_fd = -1;
return;
}
client_fd = nfd;
}
static int open_unix(const char *path)
{
struct sockaddr_un addr;
int fd;
unlink(path);
fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0) {
perror("Failed to open server socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, path);
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
perror("Failed to bind server socket");
close(fd);
return -1;
}
if (listen(fd, 1) < 0) {
perror("Failed to listen server socket");
close(fd);
return -1;
}
if (chmod(path, 0666) < 0)
perror("Failed to change mode");
return fd;
}
static void signal_callback(int signum, void *user_data)
{
switch (signum) {
case SIGINT:
case SIGTERM:
mainloop_quit();
break;
}
}
static void usage(void)
{
printf("btproxy - Bluetooth controller proxy\n"
"Usage:\n");
printf("\tbtproxy [options]\n");
printf("options:\n"
"\t-u, --unix [unixpath] Use unix server\n"
"\t-i, --index <num> Use specified controller\n"
"\t-h, --help Show help options\n");
}
static const struct option main_options[] = {
{ "unix", optional_argument, NULL, 'U' },
{ "index", required_argument, NULL, 'i' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ }
};
int main(int argc, char *argv[])
{
const char *unixpath = NULL;
const char *str;
sigset_t mask;
for (;;) {
int opt;
opt = getopt_long(argc, argv, "u::i:vh",
main_options, NULL);
if (opt < 0)
break;
switch (opt) {
case 'u':
if (optarg)
unixpath = optarg;
else
unixpath = "/tmp/bt-server-bredr";
break;
case 'i':
if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3))
str = optarg + 3;
else
str = optarg;
if (!isdigit(*str)) {
usage();
return EXIT_FAILURE;
}
hci_index = atoi(str);
break;
case 'v':
printf("%s\n", VERSION);
return EXIT_SUCCESS;
case 'h':
usage();
return EXIT_SUCCESS;
default:
return EXIT_FAILURE;
}
}
if (argc - optind > 0) {
fprintf(stderr, "Invalid command line parameters\n");
return EXIT_FAILURE;
}
mainloop_init();
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
mainloop_set_signal(&mask, signal_callback, NULL, NULL);
if (unixpath) {
server_fd = open_unix(unixpath);
if (server_fd < 0)
return EXIT_FAILURE;
mainloop_add_fd(server_fd, EPOLLIN, server_callback,
NULL, NULL);
} else {
fprintf(stderr, "Missing emulator device\n");
return EXIT_FAILURE;
}
return mainloop_run();
}