diff options
| author | Marcel Holtmann <marcel@holtmann.org> | 2004-03-31 21:32:22 +0000 | 
|---|---|---|
| committer | Marcel Holtmann <marcel@holtmann.org> | 2004-03-31 21:32:22 +0000 | 
| commit | df713b4db4fb851b197d982d9e91cd85df50839d (patch) | |
| tree | f23d3600d371e7a28a68975614b6ee651af8c095 | |
| parent | 32c5dbc2343b258dd9f6f59ab69cdf0b958b5e2c (diff) | |
Add the ciptool utility
| -rw-r--r-- | tools/Makefile.am | 4 | ||||
| -rw-r--r-- | tools/ciptool.1 | 68 | ||||
| -rw-r--r-- | tools/ciptool.c | 479 | 
3 files changed, 549 insertions, 2 deletions
| diff --git a/tools/Makefile.am b/tools/Makefile.am index 4ddeb5c9..44b30990 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -6,11 +6,11 @@ mandir = $(prefix)/usr/share/man  sbin_PROGRAMS = hciattach hciconfig -bin_PROGRAMS = hcitool l2ping sdptool +bin_PROGRAMS = hcitool l2ping sdptool ciptool  hciconfig_SOURCES = hciconfig.c csr.h csr.c -man_MANS = hciattach.8 hciconfig.8 hcitool.1 l2ping.1 sdptool.1 +man_MANS = hciattach.8 hciconfig.8 hcitool.1 l2ping.1 sdptool.1 ciptool.1  noinst_PROGRAMS	= ppporc diff --git a/tools/ciptool.1 b/tools/ciptool.1 new file mode 100644 index 00000000..982f414b --- /dev/null +++ b/tools/ciptool.1 @@ -0,0 +1,68 @@ +.\" +.\"	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., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.TH CIPTOOL 1 "JUNE 6, 2003" "" "" + +.SH NAME +ciptool \- Bluetooth Common ISDN Access Profile (CIP) +.SH SYNOPSIS +.BR "ciptool +[ +.I options +] < +.I command +> +.SH DESCRIPTION +.B ciptool +is used to set up, maintain, and inspect the CIP configuration +of the Bluetooth subsystem in the Linux kernel. +.SH OPTIONS +.TP +.BI -h +Gives a list of possible commands. +.TP +.BI -i " <hciX> | <bdaddr>" +The command is applied to device +.I +hciX +, which must be the name or the address of an installed Bluetooth +device. If not specified, the command will be use the first +available Bluetooth device. +.SH COMMANDS +.TP +.BI show +Display information about the connected devices. +.TP +.BI search +Search for Bluetooth devices and connect to first one that +offers CIP support. +.TP +.BI connect " <bdaddr> [psm]" +Connect the local device to the remote Bluetooth device on the +specified PSM number. If no PSM is specified, it will use the +SDP to retrieve it from the remote device. +.TP  +.BI release " [bdaddr]" +Release a connection to the specific device. If no address is +given and only one device is connected this will be released. +.TP  +.BI loopback " <bdaddr> [psm]"  +Create a connection to the remote device for Bluetooth testing. +This command will not provide a CAPI controller, because it is +only for testing the CAPI Message Transport Protocol. +.SH AUTHOR +Written by Marcel Holtmann <marcel@holtmann.org>. +.br diff --git a/tools/ciptool.c b/tools/ciptool.c new file mode 100644 index 00000000..31984985 --- /dev/null +++ b/tools/ciptool.c @@ -0,0 +1,479 @@ +/* + * + *  Bluetooth Common ISDN Access Profile (CIP) + * + *  Copyright (C) 2002-2003  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + */ + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <malloc.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; +	int sk, size; + +	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); +	} + +	addr.l2_family = AF_BLUETOOTH; +	bacpy(&addr.l2_bdaddr, src); +	addr.l2_psm = 0; + +	if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { +		perror("Can't bind L2CAP socket"); +		close(sk); +		exit(1); +	} + +	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); +	} + +	addr.l2_family = AF_BLUETOOTH; +	bacpy(&addr.l2_bdaddr, dst); +	addr.l2_psm = 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]; +	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; + +			free(info); + +			printf("\tConnecting to device %s\n", addr); +			do_connect(ctl, dev_id, &src, &dst, psm, 0); +			return; +		} +	} + +	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 = htobs(4099); +	} else +		psm = htobs(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; +	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 = htobs(4099); +	} else +		psm = htobs(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); + +	p.fd = sk; +	p.events = POLLERR | POLLHUP; + +	while (!__io_canceled) { +		p.revents = 0; +		if (poll(&p, 1, 100)) +			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; +} | 
