summaryrefslogtreecommitdiff
path: root/pand/main.c
diff options
context:
space:
mode:
authorMarcel Holtmann <marcel@holtmann.org>2004-04-25 21:54:11 +0000
committerMarcel Holtmann <marcel@holtmann.org>2004-04-25 21:54:11 +0000
commit965c347ab154fd36ca98cd1fd29b048068d70dab (patch)
tree0d5a7c75fe6c7e33b18e8e6f2126fa33241f76dc /pand/main.c
parent56cbbde9aaecbfdd1f1e4c820de92110355aa365 (diff)
Add the pand utility
Diffstat (limited to 'pand/main.c')
-rw-r--r--pand/main.c604
1 files changed, 604 insertions, 0 deletions
diff --git a/pand/main.c b/pand/main.c
new file mode 100644
index 000000000..91a9a92c6
--- /dev/null
+++ b/pand/main.c
@@ -0,0 +1,604 @@
+/*
+ pand - Bluetooth PAN daemon for BlueZ
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License, version 2, as
+ published by the Free Software Foundation.
+
+ 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
+*/
+
+/*
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <signal.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <sys/socket.h>
+#include <sys/poll.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/bnep.h>
+
+#include "pand.h"
+
+static uint16_t role = BNEP_SVC_PANU; // Local role (ie service)
+static uint16_t service = BNEP_SVC_NAP; // Remote service
+
+static int detach = 1;
+static int persist;
+static int use_sdp = 1;
+static int use_cache;
+static int encrypt;
+static int master;
+static int search_duration = 10;
+
+static struct {
+ int valid;
+ char dst[40];
+ bdaddr_t bdaddr;
+} cache;
+
+static char netdev[16] = "bnep%d";
+
+static bdaddr_t src_addr = *BDADDR_ANY;
+static int src_dev = -1;
+
+volatile int terminate;
+
+enum {
+ NONE,
+ SHOW,
+ LISTEN,
+ CONNECT,
+ KILL
+} modes;
+
+static void run_devup(char *dev, char *dst)
+{
+ char *argv[4], prog[40];
+
+ sprintf(prog, "%s/%s", PAND_CONFIG_DIR, PAND_DEVUP_CMD);
+
+ if (access(prog, R_OK | X_OK))
+ return;
+
+ if (fork())
+ return;
+
+ argv[0] = prog;
+ argv[1] = dev;
+ argv[2] = dst;
+ argv[3] = NULL;
+ execv(prog, argv);
+ exit(1);
+}
+
+static int do_listen(void)
+{
+ struct l2cap_options l2o;
+ struct sockaddr_l2 l2a;
+ int sk, olen, lm;
+
+ if (use_sdp)
+ bnep_sdp_register(role);
+
+ // Create L2CAP socket and bind it to PSM BNEP
+ sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ if (sk < 0) {
+ syslog(LOG_ERR, "Cannot create L2CAP socket. %s(%d)",
+ strerror(errno), errno);
+ return -1;
+ }
+
+ l2a.l2_family = AF_BLUETOOTH;
+ l2a.l2_psm = htobs(BNEP_PSM);
+ l2a.l2_bdaddr = src_addr;
+
+ if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a))) {
+ syslog(LOG_ERR, "Bind failed. %s(%d)", strerror(errno), errno);
+ return -1;
+ }
+
+ /* Setup L2CAP options according to BNEP spec */
+ if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &olen) < 0) {
+ syslog(LOG_ERR, "Failed to get L2CAP options. %s(%d)",
+ strerror(errno), errno);
+ return -1;
+ }
+
+ l2o.imtu = l2o.omtu = BNEP_MTU;
+ if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) {
+ syslog(LOG_ERR, "Failed to set L2CAP options. %s(%d)",
+ strerror(errno), errno);
+ return -1;
+ }
+
+ /* Set link mode */
+ lm = 0;
+ if (master)
+ lm |= L2CAP_LM_MASTER;
+
+ if (encrypt)
+ lm |= L2CAP_LM_ENCRYPT;
+
+ if (lm && setsockopt(sk, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)) < 0) {
+ syslog(LOG_ERR, "Failed to set link mode. %s(%d)", strerror(errno), errno);
+ return -1;
+ }
+
+ listen(sk, 10);
+
+ while (!terminate) {
+ int alen = sizeof(l2a);
+ int nsk;
+ nsk = accept(sk, (struct sockaddr *) &l2a, &alen);
+ if (nsk < 0) {
+ syslog(LOG_ERR, "Accept failed. %s(%d)", strerror(errno), errno);
+ continue;
+ }
+
+ switch (fork()) {
+ case 0:
+ break;
+ case -1:
+ syslog(LOG_ERR, "Fork failed. %s(%d)", strerror(errno), errno);
+ default:
+ close(nsk);
+ continue;
+ }
+
+ if (!bnep_accept_connection(nsk, role, netdev)) {
+ char str[40];
+ ba2str(&l2a.l2_bdaddr, str);
+
+ syslog(LOG_INFO, "New connection from %s %s", str, netdev);
+
+ run_devup(netdev, str);
+ } else {
+ syslog(LOG_ERR, "Connection failed. %s(%d)",
+ strerror(errno), errno);
+ }
+
+ close(nsk);
+ exit(0);
+ }
+
+ if (use_sdp)
+ bnep_sdp_unregister();
+ return 0;
+}
+
+/* Wait for disconnect or error condition on the socket */
+static int w4_hup(int sk)
+{
+ struct pollfd pf;
+ int n;
+
+ while (!terminate) {
+ pf.fd = sk;
+ pf.events = POLLERR | POLLHUP;
+ n = poll(&pf, 1, -1);
+ if (n < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ syslog(LOG_ERR, "Poll failed. %s(%d)",
+ strerror(errno), errno);
+ return 1;
+ }
+
+ if (n) {
+ int err = 0, olen = sizeof(err);
+ getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &olen);
+ syslog(LOG_INFO, "%s disconnected%s%s", netdev,
+ err ? " : " : "", err ? strerror(err) : "");
+
+ close(sk);
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/* Connect and initiate BNEP session
+ * Returns:
+ * -1 - critical error (exit persist mode)
+ * 1 - non critical error
+ * 0 - success
+ */
+static int create_connection(char *dst, bdaddr_t *bdaddr)
+{
+ struct l2cap_options l2o;
+ struct sockaddr_l2 l2a;
+ int sk, olen, r = 0;
+
+ syslog(LOG_INFO, "Connecting to %s", dst);
+
+ sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ if (sk < 0) {
+ syslog(LOG_ERR, "Cannot create L2CAP socket. %s(%d)",
+ strerror(errno), errno);
+ return -1;
+ }
+
+ /* Setup L2CAP options according to BNEP spec */
+ getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &olen);
+ l2o.imtu = l2o.omtu = BNEP_MTU;
+ setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o));
+
+ l2a.l2_family = AF_BLUETOOTH;
+
+ /* Set local address */
+ l2a.l2_psm = 0;
+ l2a.l2_bdaddr = src_addr;
+
+ if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)))
+ syslog(LOG_ERR, "Bind failed. %s(%d)",
+ strerror(errno), errno);
+
+ l2a.l2_psm = htobs(BNEP_PSM);
+ l2a.l2_bdaddr = *bdaddr;
+
+ if (!connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) &&
+ !bnep_create_connection(sk, role, service, netdev)) {
+
+ syslog(LOG_INFO, "%s connected", netdev);
+
+ run_devup(netdev, dst);
+
+ if (persist)
+ w4_hup(sk);
+
+ r = 0;
+ } else {
+ syslog(LOG_ERR, "Connect to %s failed. %s(%d)",
+ dst, strerror(errno), errno);
+ r = 1;
+ }
+
+ close(sk);
+
+ if (use_cache) {
+ if (!r) {
+ /* Succesesful connection, validate cache */
+ strcpy(cache.dst, dst);
+ bacpy(&cache.bdaddr, bdaddr);
+ cache.valid = use_cache;
+ } else
+ cache.valid--;
+ }
+
+ return r;
+}
+
+/* Search and connect
+ * Returns:
+ * -1 - critical error (exit persist mode)
+ * 1 - non critical error
+ * 0 - success
+ */
+static int do_connect(void)
+{
+ inquiry_info *ii;
+ int reconnect = 0;
+ int i, n, r = 0;
+
+ do {
+ if (reconnect)
+ sleep(persist);
+ reconnect = 1;
+
+ if (cache.valid > 0) {
+ /* Use cached bdaddr */
+ r = create_connection(cache.dst, &cache.bdaddr);
+ if (r < 0) {
+ terminate = 1;
+ break;
+ }
+ continue;
+ }
+
+ syslog(LOG_INFO, "Inquiring");
+
+ /* FIXME: Should we use non general LAP here ? */
+
+ ii = NULL;
+ n = hci_inquiry(src_dev, search_duration, 10, NULL, &ii, 0);
+ if (n < 0) {
+ syslog(LOG_ERR, "Inquiry failed. %s(%d)", strerror(errno), errno);
+ continue;
+ }
+
+ for (i = 0; i < n; i++) {
+ char dst[40];
+ ba2str(&ii[i].bdaddr, dst);
+
+ if (use_sdp) {
+ syslog(LOG_INFO, "Searching for %s on %s",
+ bnep_svc2str(service), dst);
+
+ if (bnep_sdp_search(&src_addr, &ii[i].bdaddr, service) <= 0)
+ continue;
+ }
+
+ r = create_connection(dst, &ii[i].bdaddr);
+ if (r < 0) {
+ terminate = 1;
+ break;
+ }
+ }
+ free(ii);
+ } while (!terminate && persist);
+
+ return r;
+}
+
+static void do_show(void)
+{
+ bnep_show_connections();
+}
+
+static void do_kill(char *dst)
+{
+ if (dst)
+ bnep_kill_connection((void *) strtoba(dst));
+ else
+ bnep_kill_all_connections();
+}
+
+void sig_hup(int sig)
+{
+ return;
+}
+
+void sig_term(int sig)
+{
+ terminate = 1;
+}
+
+static struct option main_lopts[] = {
+ { "help", 0, 0, 'h' },
+ { "listen", 0, 0, 's' },
+ { "connect", 1, 0, 'c' },
+ { "search", 2, 0, 'Q' },
+ { "kill", 1, 0, 'k' },
+ { "killall", 0, 0, 'K' },
+ { "role", 1, 0, 'r' },
+ { "service", 1, 0, 'd' },
+ { "device", 1, 0, 'i' },
+ { "source", 1, 0, 'S' },
+ { "nosdp", 0, 0, 'D' },
+ { "list", 0, 0, 'l' },
+ { "show", 0, 0, 'l' },
+ { "nodetach", 0, 0, 'n' },
+ { "persist", 2, 0, 'p' },
+ { "encrypt", 0, 0, 'E' },
+ { "master", 0, 0, 'M' },
+ { "cache", 0, 0, 'C' },
+ { 0, 0, 0, 0 }
+};
+
+static char main_sopts[] = "hsc:k:Kr:i:S:lnp::DQ::EMC::";
+
+static char main_help[] =
+ "PAN daemon version " VERSION " \n"
+ "Usage:\n"
+ "\tpand <options>\n"
+ "Options:\n"
+ "\t--show --list -l Show active PAN connections\n"
+ "\t--listen -s Listen for PAN connections\n"
+ "\t--connect -c <bdaddr> Create PAN connection\n"
+ "\t--search -Q[duration] Search and connect\n"
+ "\t--kill -k <bdaddr> Kill PAN connection\n"
+ "\t--killall -K Kill all PAN connections\n"
+ "\t--role -r <role> Local PAN role (PANU, NAP, GN)\n"
+ "\t--service -d <role> Remote PAN service (PANU, NAP, GN)\n"
+ "\t--device -i <name> Network interface name\n"
+ "\t--source -S <bdaddr> Source bdaddr\n"
+ "\t--nosdp -D Disable SDP\n"
+ "\t--encrypt -E Enable encryption\n"
+ "\t--master -M Become the master of a piconet\n"
+ "\t--nodetach -n Do not become a daemon\n"
+ "\t--persist -p[interval] Persist mode\n"
+ "\t--cache -C[valid] Cache addresses\n";
+
+int main(int argc, char **argv)
+{
+ char *dst = NULL, *src = NULL;
+ struct sigaction sa;
+ int mode = NONE;
+ int opt;
+
+ while ((opt=getopt_long(argc, argv, main_sopts, main_lopts, NULL)) != -1) {
+ switch(opt) {
+ case 'l':
+ mode = SHOW;
+ detach = 0;
+ break;
+
+ case 's':
+ mode = LISTEN;
+ break;
+
+ case 'c':
+ mode = CONNECT;
+ dst = strdup(optarg);
+ break;
+
+ case 'Q':
+ mode = CONNECT;
+ dst = NULL;
+ if (optarg)
+ search_duration = atoi(optarg);
+ break;
+
+ case 'k':
+ mode = KILL;
+ detach = 0;
+ dst = strdup(optarg);
+ break;
+
+ case 'K':
+ mode = KILL;
+ detach = 0;
+ dst = NULL;
+ break;
+
+ case 'S':
+ src = strdup(optarg);
+ break;
+
+ case 'r':
+ bnep_str2svc(optarg, &role);
+ break;
+
+ case 'd':
+ bnep_str2svc(optarg, &service);
+ break;
+
+ case 'D':
+ use_sdp = 0;
+ break;
+
+ case 'E':
+ encrypt = 1;
+ break;
+
+ case 'M':
+ master = 1;
+ break;
+
+ case 'i':
+ strcpy(netdev, optarg);
+ break;
+
+ case 'n':
+ detach = 0;
+ break;
+
+ case 'p':
+ if (optarg)
+ persist = atoi(optarg);
+ else
+ persist = 5;
+ break;
+
+ case 'C':
+ if (optarg)
+ use_cache = atoi(optarg);
+ else
+ use_cache = 2;
+ break;
+
+ case 'h':
+ default:
+ printf(main_help);
+ exit(0);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ optind = 0;
+
+ if (bnep_init())
+ return -1;
+
+ /* Check non daemon modes first */
+ switch (mode) {
+ case SHOW:
+ do_show();
+ return 0;
+
+ case KILL:
+ do_kill(dst);
+ return 0;
+
+ case NONE:
+ printf(main_help);
+ return 0;
+ }
+
+ /* Initialize signals */
+ 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_hup;
+ sigaction(SIGHUP, &sa, NULL);
+
+ sa.sa_handler = sig_term;
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+
+ if (detach) {
+ if (fork()) exit(0);
+
+ /* Direct stdin,stdout,stderr to '/dev/null' */
+ {
+ int fd = open("/dev/null", O_RDWR);
+ dup2(fd, 0); dup2(fd, 1); dup2(fd, 2);
+ close(fd);
+ }
+
+ setsid();
+ chdir("/");
+ }
+
+ openlog("pand", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
+ syslog(LOG_INFO, "PAN daemon ver %s", VERSION);
+
+ if (src) {
+ src_dev = hci_devid(src);
+ if (src_dev < 0 || hci_devba(src_dev, &src_addr) < 0) {
+ syslog(LOG_ERR, "Invalid source. %s(%d)", strerror(errno), errno);
+ return -1;
+ }
+ }
+
+ if (dst) {
+ /* Disable cache invalidation */
+ use_cache = 0;
+
+ strncpy(cache.dst, dst, sizeof(cache.dst) - 1);
+ str2ba(dst, &cache.bdaddr);
+ cache.valid = 1;
+ }
+
+ switch (mode) {
+ case CONNECT:
+ do_connect();
+ break;
+
+ case LISTEN:
+ do_listen();
+ break;
+ }
+
+ return 0;
+}