| /* |
| * |
| * 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 |
| |
| #define _GNU_SOURCE |
| #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> |
| |
| #ifdef NEED_PPOLL |
| #include "ppoll.h" |
| #endif |
| |
| 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; |
| } |