494 lines
10 KiB
C
494 lines
10 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2002-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 <errno.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/hci.h>
|
|
#include <bluetooth/hci_lib.h>
|
|
#include <bluetooth/l2cap.h>
|
|
#include <bluetooth/sdp.h>
|
|
#include <bluetooth/sdp_lib.h>
|
|
#include <bluetooth/cmtp.h>
|
|
|
|
static volatile sig_atomic_t __io_canceled = 0;
|
|
|
|
static void sig_hup(int sig)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void sig_term(int sig)
|
|
{
|
|
__io_canceled = 1;
|
|
}
|
|
|
|
static char *cmtp_state[] = {
|
|
"unknown",
|
|
"connected",
|
|
"open",
|
|
"bound",
|
|
"listening",
|
|
"connecting",
|
|
"connecting",
|
|
"config",
|
|
"disconnecting",
|
|
"closed"
|
|
};
|
|
|
|
static char *cmtp_flagstostr(uint32_t flags)
|
|
{
|
|
static char str[100] = "";
|
|
|
|
strcat(str, "[");
|
|
|
|
if (flags & (1 << CMTP_LOOPBACK))
|
|
strcat(str, "loopback");
|
|
|
|
strcat(str, "]");
|
|
|
|
return str;
|
|
}
|
|
|
|
static int get_psm(bdaddr_t *src, bdaddr_t *dst, unsigned short *psm)
|
|
{
|
|
sdp_session_t *s;
|
|
sdp_list_t *srch, *attrs, *rsp;
|
|
uuid_t svclass;
|
|
uint16_t attr;
|
|
int err;
|
|
|
|
if (!(s = sdp_connect(src, dst, 0)))
|
|
return -1;
|
|
|
|
sdp_uuid16_create(&svclass, CIP_SVCLASS_ID);
|
|
srch = sdp_list_append(NULL, &svclass);
|
|
|
|
attr = SDP_ATTR_PROTO_DESC_LIST;
|
|
attrs = sdp_list_append(NULL, &attr);
|
|
|
|
err = sdp_service_search_attr_req(s, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);
|
|
|
|
sdp_close(s);
|
|
|
|
if (err)
|
|
return 0;
|
|
|
|
for (; rsp; rsp = rsp->next) {
|
|
sdp_record_t *rec = (sdp_record_t *) rsp->data;
|
|
sdp_list_t *protos;
|
|
|
|
if (!sdp_get_access_protos(rec, &protos)) {
|
|
unsigned short p = sdp_get_proto_port(protos, L2CAP_UUID);
|
|
if (p > 0) {
|
|
*psm = p;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_connect(int ctl, int dev_id, bdaddr_t *src, bdaddr_t *dst, unsigned short psm, uint32_t flags)
|
|
{
|
|
struct cmtp_connadd_req req;
|
|
struct hci_dev_info di;
|
|
struct sockaddr_l2 addr;
|
|
struct l2cap_options opts;
|
|
socklen_t size;
|
|
int sk;
|
|
|
|
hci_devinfo(dev_id, &di);
|
|
if (!(di.link_policy & HCI_LP_RSWITCH)) {
|
|
printf("Local device is not accepting role switch\n");
|
|
}
|
|
|
|
if ((sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) {
|
|
perror("Can't create L2CAP socket");
|
|
exit(1);
|
|
}
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.l2_family = AF_BLUETOOTH;
|
|
bacpy(&addr.l2_bdaddr, src);
|
|
|
|
if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
perror("Can't bind L2CAP socket");
|
|
close(sk);
|
|
exit(1);
|
|
}
|
|
|
|
memset(&opts, 0, sizeof(opts));
|
|
size = sizeof(opts);
|
|
|
|
if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) {
|
|
perror("Can't get L2CAP options");
|
|
close(sk);
|
|
exit(1);
|
|
}
|
|
|
|
opts.imtu = CMTP_DEFAULT_MTU;
|
|
opts.omtu = CMTP_DEFAULT_MTU;
|
|
opts.flush_to = 0xffff;
|
|
|
|
if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) {
|
|
perror("Can't set L2CAP options");
|
|
close(sk);
|
|
exit(1);
|
|
}
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.l2_family = AF_BLUETOOTH;
|
|
bacpy(&addr.l2_bdaddr, dst);
|
|
addr.l2_psm = htobs(psm);
|
|
|
|
if (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
perror("Can't connect L2CAP socket");
|
|
close(sk);
|
|
exit(1);
|
|
}
|
|
|
|
req.sock = sk;
|
|
req.flags = flags;
|
|
|
|
if (ioctl(ctl, CMTPCONNADD, &req) < 0) {
|
|
perror("Can't create connection");
|
|
exit(1);
|
|
}
|
|
|
|
return sk;
|
|
}
|
|
|
|
static void cmd_show(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
|
|
{
|
|
struct cmtp_connlist_req req;
|
|
struct cmtp_conninfo ci[16];
|
|
char addr[18];
|
|
unsigned int i;
|
|
|
|
req.cnum = 16;
|
|
req.ci = ci;
|
|
|
|
if (ioctl(ctl, CMTPGETCONNLIST, &req) < 0) {
|
|
perror("Can't get connection list");
|
|
exit(1);
|
|
}
|
|
|
|
for (i = 0; i < req.cnum; i++) {
|
|
ba2str(&ci[i].bdaddr, addr);
|
|
printf("%d %s %s %s\n", ci[i].num, addr,
|
|
cmtp_state[ci[i].state],
|
|
ci[i].flags ? cmtp_flagstostr(ci[i].flags) : "");
|
|
}
|
|
}
|
|
|
|
static void cmd_search(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
|
|
{
|
|
inquiry_info *info = NULL;
|
|
bdaddr_t src, dst;
|
|
unsigned short psm;
|
|
int i, dev_id, num_rsp, length, flags;
|
|
char addr[18];
|
|
uint8_t class[3];
|
|
|
|
ba2str(bdaddr, addr);
|
|
dev_id = hci_devid(addr);
|
|
if (dev_id < 0) {
|
|
dev_id = hci_get_route(NULL);
|
|
hci_devba(dev_id, &src);
|
|
} else
|
|
bacpy(&src, bdaddr);
|
|
|
|
length = 8; /* ~10 seconds */
|
|
num_rsp = 0;
|
|
flags = 0;
|
|
|
|
printf("Searching ...\n");
|
|
|
|
num_rsp = hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags);
|
|
|
|
for (i = 0; i < num_rsp; i++) {
|
|
memcpy(class, (info+i)->dev_class, 3);
|
|
if ((class[1] == 2) && ((class[0] / 4) == 5)) {
|
|
bacpy(&dst, &(info+i)->bdaddr);
|
|
ba2str(&dst, addr);
|
|
|
|
printf("\tChecking service for %s\n", addr);
|
|
if (!get_psm(&src, &dst, &psm))
|
|
continue;
|
|
|
|
bt_free(info);
|
|
|
|
printf("\tConnecting to device %s\n", addr);
|
|
do_connect(ctl, dev_id, &src, &dst, psm, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bt_free(info);
|
|
fprintf(stderr, "\tNo devices in range or visible\n");
|
|
exit(1);
|
|
}
|
|
|
|
static void cmd_create(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
|
|
{
|
|
bdaddr_t src, dst;
|
|
unsigned short psm;
|
|
int dev_id;
|
|
char addr[18];
|
|
|
|
if (argc < 2)
|
|
return;
|
|
|
|
str2ba(argv[1], &dst);
|
|
|
|
ba2str(bdaddr, addr);
|
|
dev_id = hci_devid(addr);
|
|
if (dev_id < 0) {
|
|
dev_id = hci_get_route(&dst);
|
|
hci_devba(dev_id, &src);
|
|
} else
|
|
bacpy(&src, bdaddr);
|
|
|
|
if (argc < 3) {
|
|
if (!get_psm(&src, &dst, &psm))
|
|
psm = 4099;
|
|
} else
|
|
psm = atoi(argv[2]);
|
|
|
|
do_connect(ctl, dev_id, &src, &dst, psm, 0);
|
|
}
|
|
|
|
static void cmd_release(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
|
|
{
|
|
struct cmtp_conndel_req req;
|
|
struct cmtp_connlist_req cl;
|
|
struct cmtp_conninfo ci[16];
|
|
|
|
if (argc < 2) {
|
|
cl.cnum = 16;
|
|
cl.ci = ci;
|
|
|
|
if (ioctl(ctl, CMTPGETCONNLIST, &cl) < 0) {
|
|
perror("Can't get connection list");
|
|
exit(1);
|
|
}
|
|
|
|
if (cl.cnum == 0)
|
|
return;
|
|
|
|
if (cl.cnum != 1) {
|
|
fprintf(stderr, "You have to specifiy the device address.\n");
|
|
exit(1);
|
|
}
|
|
|
|
bacpy(&req.bdaddr, &ci[0].bdaddr);
|
|
} else
|
|
str2ba(argv[1], &req.bdaddr);
|
|
|
|
if (ioctl(ctl, CMTPCONNDEL, &req) < 0) {
|
|
perror("Can't release connection");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void cmd_loopback(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
|
|
{
|
|
struct cmtp_conndel_req req;
|
|
struct sigaction sa;
|
|
struct pollfd p;
|
|
sigset_t sigs;
|
|
bdaddr_t src, dst;
|
|
unsigned short psm;
|
|
int dev_id, sk;
|
|
char addr[18];
|
|
|
|
if (argc < 2)
|
|
return;
|
|
|
|
str2ba(argv[1], &dst);
|
|
|
|
ba2str(bdaddr, addr);
|
|
dev_id = hci_devid(addr);
|
|
if (dev_id < 0) {
|
|
dev_id = hci_get_route(&dst);
|
|
hci_devba(dev_id, &src);
|
|
} else
|
|
bacpy(&src, bdaddr);
|
|
|
|
ba2str(&dst, addr);
|
|
printf("Connecting to %s in loopback mode\n", addr);
|
|
|
|
if (argc < 3) {
|
|
if (!get_psm(&src, &dst, &psm))
|
|
psm = 4099;
|
|
} else
|
|
psm = atoi(argv[2]);
|
|
|
|
sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK));
|
|
|
|
printf("Press CTRL-C for hangup\n");
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_flags = SA_NOCLDSTOP;
|
|
sa.sa_handler = SIG_IGN;
|
|
sigaction(SIGCHLD, &sa, NULL);
|
|
sigaction(SIGPIPE, &sa, NULL);
|
|
|
|
sa.sa_handler = sig_term;
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
sigaction(SIGINT, &sa, NULL);
|
|
|
|
sa.sa_handler = sig_hup;
|
|
sigaction(SIGHUP, &sa, NULL);
|
|
|
|
sigfillset(&sigs);
|
|
sigdelset(&sigs, SIGCHLD);
|
|
sigdelset(&sigs, SIGPIPE);
|
|
sigdelset(&sigs, SIGTERM);
|
|
sigdelset(&sigs, SIGINT);
|
|
sigdelset(&sigs, SIGHUP);
|
|
|
|
p.fd = sk;
|
|
p.events = POLLERR | POLLHUP;
|
|
|
|
while (!__io_canceled) {
|
|
p.revents = 0;
|
|
if (ppoll(&p, 1, NULL, &sigs) > 0)
|
|
break;
|
|
}
|
|
|
|
bacpy(&req.bdaddr, &dst);
|
|
ioctl(ctl, CMTPCONNDEL, &req);
|
|
}
|
|
|
|
static struct {
|
|
char *cmd;
|
|
char *alt;
|
|
void (*func)(int ctl, bdaddr_t *bdaddr, int argc, char **argv);
|
|
char *opt;
|
|
char *doc;
|
|
} command[] = {
|
|
{ "show", "list", cmd_show, 0, "Show remote connections" },
|
|
{ "search", "scan", cmd_search, 0, "Search for a remote device" },
|
|
{ "connect", "create", cmd_create, "<bdaddr>", "Connect a remote device" },
|
|
{ "release", "disconnect", cmd_release, "[bdaddr]", "Disconnect the remote device" },
|
|
{ "loopback", "test", cmd_loopback, "<bdaddr>", "Loopback test of a device" },
|
|
{ NULL, NULL, NULL, 0, 0 }
|
|
};
|
|
|
|
static void usage(void)
|
|
{
|
|
int i;
|
|
|
|
printf("ciptool - Bluetooth Common ISDN Access Profile (CIP)\n\n");
|
|
|
|
printf("Usage:\n"
|
|
"\tciptool [options] [command]\n"
|
|
"\n");
|
|
|
|
printf("Options:\n"
|
|
"\t-i [hciX|bdaddr] Local HCI device or BD Address\n"
|
|
"\t-h, --help Display help\n"
|
|
"\n");
|
|
|
|
printf("Commands:\n");
|
|
for (i = 0; command[i].cmd; i++)
|
|
printf("\t%-8s %-10s\t%s\n", command[i].cmd,
|
|
command[i].opt ? command[i].opt : " ",
|
|
command[i].doc);
|
|
printf("\n");
|
|
}
|
|
|
|
static struct option main_options[] = {
|
|
{ "help", 0, 0, 'h' },
|
|
{ "device", 1, 0, 'i' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
bdaddr_t bdaddr;
|
|
int i, opt, ctl;
|
|
|
|
bacpy(&bdaddr, BDADDR_ANY);
|
|
|
|
while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
|
|
switch(opt) {
|
|
case 'i':
|
|
if (!strncmp(optarg, "hci", 3))
|
|
hci_devba(atoi(optarg + 3), &bdaddr);
|
|
else
|
|
str2ba(optarg, &bdaddr);
|
|
break;
|
|
case 'h':
|
|
usage();
|
|
exit(0);
|
|
default:
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
optind = 0;
|
|
|
|
if (argc < 1) {
|
|
usage();
|
|
return 0;
|
|
}
|
|
|
|
if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_CMTP)) < 0 ) {
|
|
perror("Can't open CMTP control socket");
|
|
exit(1);
|
|
}
|
|
|
|
for (i = 0; command[i].cmd; i++) {
|
|
if (strncmp(command[i].cmd, argv[0], 4) && strncmp(command[i].alt, argv[0], 4))
|
|
continue;
|
|
command[i].func(ctl, &bdaddr, argc, argv);
|
|
close(ctl);
|
|
exit(0);
|
|
}
|
|
|
|
usage();
|
|
|
|
close(ctl);
|
|
|
|
return 0;
|
|
}
|