diff options
author | Dan Williams <dcbw@redhat.com> | 2014-06-30 11:26:52 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2014-06-30 11:26:52 -0500 |
commit | 3fdf47697d57ea18e55444bd6098362c2445ac1b (patch) | |
tree | f734ca551f34220f5188c9de46181b0d905a1ae5 | |
parent | 435d2e927c63e1587890d4ba78080def8c8f46b2 (diff) | |
download | NetworkManager-dcbw/dun.tar.gz |
asdfasdfasdfdcbw/dun
-rw-r--r-- | src/devices/bluetooth/nm-bluez5-dun.c | 379 | ||||
-rw-r--r-- | src/devices/bluetooth/nm-bluez5-dun.h | 34 |
2 files changed, 260 insertions, 153 deletions
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c index 872f20c931..fbb877e662 100644 --- a/src/devices/bluetooth/nm-bluez5-dun.c +++ b/src/devices/bluetooth/nm-bluez5-dun.c @@ -34,20 +34,6 @@ #include "nm-logging.h" #include "NetworkManagerUtils.h" -void -nm_bluez5_dun_cleanup (int sk, int rfcomm_id) -{ - struct rfcomm_dev_req req = { 0 }; - - g_return_val_if_fail (sk >= 0, FALSE); - - if (rfcomm_id >= 0) { - req.dev_id = rfcomm_id; - ioctl (sk, RFCOMMRELEASEDEV, &req); - } - close (sk); -} - static void setba (bdaddr_t *dst, const guint8 src[ETH_ALEN]) { @@ -55,38 +41,141 @@ setba (bdaddr_t *dst, const guint8 src[ETH_ALEN]) register const guint8 *s = (const guint8 *) src; register int i; + /* BT MAC addresses get swapped when sending to the kernel */ memcpy (dst, src, ETH_ALEN); for (i = 0; i < 6; i++) d[i] = s[5-i]; } typedef struct { - sdp_session_t *session; - guint watch_id; - NMBluez5DunFindChannelFunc callback; - gpointer user_data; -} SdpInfo; + bdaddr_t src[ETH_ALEN]; + bdaddr_t dst[ETH_ALEN]; + int rfcomm_channel; + int rfcomm_fd; + char *rfcomm_dev; + int rfcomm_id; + + NMBluez5DunFunc callback; + gpointer user_data; + + sdp_session_t *sdp_session; + guint sdp_watch_id; +} NMBluez5DunContext; static void -sdp_search_cleanup (SdpInfo *info) +sdp_search_cleanup (NMBluez5DunContext *context) { - sdp_close (info->session); - if (info->watch_id) - g_source_remove (info->watch_id); - g_free (info); + if (context->sdp_session) { + sdp_close (context->sdp_session); + context->sdp_session = NULL; + } + + if (context->sdp_watch_id) { + g_source_remove (context->sdp_watch_id); + context->sdp_watch_id = 0; + } +} + +static void +sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data) +{ + NMBluez5DunContext *context = user_data; + sdp_list_t *recs = NULL; + int scanned, seqlen = 0, bytesleft = size; + uint8_t dataType; + int err = 0; + int channel = -1; + + if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { + err = -EPROTO; + goto done; + } + + scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen); + if (!scanned || !seqlen) + goto done; + + rsp += scanned; + bytesleft -= scanned; + do { + sdp_record_t *rec; + int recsize = 0; + sdp_list_t *protos; + + rec = sdp_extract_pdu (rsp, bytesleft, &recsize); + if (!rec) + break; + + if (!recsize) { + sdp_record_free (rec); + break; + } + + if (sdp_get_access_protos (rec, &protos) == 0) { + /* Extract the DUN channel number */ + channel = sdp_get_proto_port (protos, RFCOMM_UUID); + sdp_list_free (protos, NULL); + } + sdp_record_free (rec); + + scanned += recsize; + rsp += recsize; + bytesleft -= recsize; + } while ((scanned < (ssize_t) size) && (bytesleft > 0) && (channel < 0)); + +done: + cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session); + + if (ctxt->cb) + ctxt->cb(recs, err, ctxt->user_data); + + if (recs) + sdp_list_free(recs, (sdp_free_func_t) sdp_record_free); + + search_context_cleanup(ctxt); +} + +static gboolean +sdp_search_process_cb (GIOChannel *channel, GIOCondition condition, gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + int err = 0; + + if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + err = EIO; + goto failed; + } + + if (sdp_process (context->sdp_session) < 0) + goto failed; + + return TRUE; + +failed: + if (err) { + sdp_close(ctxt->session); + ctxt->session = NULL; + + if (ctxt->cb) + ctxt->cb(NULL, err, ctxt->user_data); + + search_context_cleanup(ctxt); + } + + return FALSE; } static gboolean sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_data) { - SdpInfo *info = user_data; + NMBluez5DunContext *context = user_data; sdp_list_t *search, *attrs; uuid_t svclass; uint16_t attr; int fd, err, fd_err = 0; socklen_t len = sizeof (fd_err); - info->watch_id = 0; + context->watch_id = 0; fd = g_io_channel_unix_get_fd (channel); if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) @@ -97,7 +186,7 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da if (err != 0) goto failed; - if (sdp_set_notify (info->session, search_completed_cb, ctxt) < 0) { + if (sdp_set_notify (context->sdp_session, sdp_search_completed_cb, ctxt) < 0) { err = -EIO; goto failed; } @@ -107,12 +196,12 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da attr = SDP_ATTR_PROTO_DESC_LIST; attrs = sdp_list_append (NULL, &attr); - if (!sdp_service_search_attr_async (info->session, search, SDP_ATTR_REQ_INDIVIDUAL, attrs)) { + if (!sdp_service_search_attr_async (context->sdp_session, search, SDP_ATTR_REQ_INDIVIDUAL, attrs)) { /* Set callback responsible for update the internal SDP transaction */ - info->watch_id = g_io_add_watch (channel, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - sdp_search_process_cb, - info); + context->sdp_watch_id = g_io_add_watch (channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + sdp_search_process_cb, + context); } done: @@ -121,93 +210,25 @@ done: return G_SOURCE_REMOVE; } -gconstpointer -nm_bluez5_dun_find_channel (const guint8 adapter[ETH_ALEN], - const guint8 remote[ETH_ALEN], - NMBluez5DunFindChannelFunc callback, - gpointer user_data, - GError **error) +static gboolean +dun_find_channel (NMBluez5DunContext *context, GError **error) { - SdpInfo *info; - sdp_session_t *s; - GError *error = NULL; - bdaddr_t src, dst; - GSimpleAsyncResult *simple; GIOChannel *channel; - setba (&src, adapter); - setba (&dst, remote); - s = sdp_connect (&src, &dst, SDP_NON_BLOCKING); - if (!s) { + context->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING); + if (!context->sdp_session) { g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, "(" MAC_FMT "): failed to connect to the SDP server: (%d) %s", - MAC_ARG (adapter), errno, strerror (errno)); - return NULL; - } - - info = g_slice_new0 (SdpInfo); - info->session = s; - info->callback = callback; - info->user_data = user_data; - channel = g_io_channel_unix_new (sdp_get_socket (s)); - info->watch_id = g_io_add_watch (channel, - G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - sdp_connect_watch, - info); - g_io_channel_unref (channel); - return info; -} - -static gboolean -dun_create_tty (int sk, - const guint8 adapter[ETH_ALEN], - const guint8 remote[ETH_ALEN], - int channel, - char **out_rfcomm_tty, - int *out_rfcomm_id, - GError **error) -{ - struct stat st; - int devid, try = 30; - char tty[100]; - const int ttylen = sizeof (tty) - 1; - - struct rfcomm_dev_req req = { - .flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP), - .dev_id = -1, - .channel = channel - }; - - g_return_val_if_fail (channel >= 0, FALSE); - g_return_val_if_fail (out_rfcomm_tty, FALSE); - g_return_val_if_fail (*out_rfcomm_tty == NULL, FALSE); - g_return_val_if_fail (out_rfcomm_id, FALSE); - - setba (&req.src, adapter); - setba (&req.dst, remote); - devid = ioctl (sk, RFCOMMCREATEDEV, &req); - if (devid < 0) { - g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "(" MAC_FMT "): failed to create rfcomm device: (%d) %s", - MAC_ARG (adapter), errno, strerror (errno)); - return FALSE; - } - - snprintf (tty, ttylen, "/dev/rfcomm%d", devid); - while (stat (tty, &st) < 0 && try--) { - snprintf (tty, ttylen, "/dev/rfcomm%d", devid); - if (try--) { - g_usleep (100 * 1000); - continue; - } - - g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "(" MAC_FMT "): failed to find rfcomm device", - MAC_ARG (adapter)); + MAC_ARG (context->src), errno, strerror (errno)); return FALSE; } - *out_rfcomm_tty = g_strdup (tty); + channel = g_io_channel_unix_new (sdp_get_socket (context->sdp_session)); + context->sdp_watch_id = g_io_add_watch (channel, + G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + sdp_connect_watch, + context); + g_io_channel_unref (channel); return TRUE; } @@ -238,55 +259,141 @@ dun_create_tty (int sk, return MAX (0, ch); } -gboolean -nm_bluez5_dun_connect (const guint8 adapter[ETH_ALEN], - const guint8 remote[ETH_ALEN], - int rfcomm_channel, - int *out_rfcomm_fd, - char **out_rfcomm_dev, - int *out_rfcomm_id, - GError **error) +static gboolean +dun_connect (NMBluez5DunContext *context, GError **error) { struct sockaddr_rc sa; - int sk; + struct stat st; + int devid, try = 30; + char tty[100]; + const int ttylen = sizeof (tty) - 1; - g_return_val_if_fail (rfcomm_channel >= 0), FALSE); + struct rfcomm_dev_req req = { + .flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP), + .dev_id = -1, + .channel = context->rfcomm_channel + }; - sk = socket (AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); - if (sk < 0) { + context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (context->rfcomm_fd < 0) { g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, "Failed to create RFCOMM socket: (%d) %s", errno, strerror (errno)); return FALSE; } + /* Connect to the remote device */ sa.rc_family = AF_BLUETOOTH; sa.rc_channel = 0; - setba (&sa.rc_bdaddr, adapter); - if (bind (sk, (struct sockaddr *) &sa, sizeof(sa))) { + memcpy (&sa.rc_bdaddr, context->src, ETH_ALEN); + if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) { nm_log_dbg (LOGD_BT, "(" MAC_FMT "): failed to bind socket: (%d) %s", - MAC_ARG (adapter), errno, strerror (errno)); + MAC_ARG (context->src), errno, strerror (errno)); } - sa.rc_channel = rfcomm_channel; - setba (&sa.rc_bdaddr, remote); + sa.rc_channel = context->rfcomm_channel; + memcpy (&sa.rc_bdaddr, context->dst, ETH_ALEN); if (connect (sk, (struct sockaddr *) &sa, sizeof (sa)) ) { g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, "Failed to connect to remote device: (%d) %s", errno, strerror (errno)); - goto error; + return FALSE; } nm_log_dbg (LOGD_BT, "(" MAC_FMT "): connected to " MAC_FMT "on channel %d", - MAC_ARG (adapter), MAC_ARG (remote), ch); + MAC_ARG (context->src), MAC_ARG (remote), ch); - if (!dun_create_tty (sk, adapter, remote, rfcomm_channel, out_rfcomm_dev, out_rfcomm_id, error)) - goto error; + /* Create an RFCOMM kernel device for the DUN channel */ + memcpy (&req.src, context->src, ETH_ALEN); + memcpy (&req.dst, context->dst, ETH_ALEN); + devid = ioctl (sk, RFCOMMCREATEDEV, &req); + if (devid < 0) { + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "(" MAC_FMT "): failed to create rfcomm device: (%d) %s", + MAC_ARG (context->src), errno, strerror (errno)); + return FALSE; + } + context->rfcomm_id = devid; - *out_rfcomm_fd = sk; + snprintf (tty, ttylen, "/dev/rfcomm%d", devid); + while (stat (tty, &st) < 0 && try--) { + snprintf (tty, ttylen, "/dev/rfcomm%d", devid); + if (try--) { + g_usleep (100 * 1000); + continue; + } + + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "(" MAC_FMT "): failed to find rfcomm device", + MAC_ARG (context->src)); + return FALSE; + } + + context->rfcomm_dev = g_strdup (tty); return TRUE; +} -error: - close (sk); - return FALSE; +NMBluez5DunContext * +nm_bluez5_dun_new (const guint8 adapter[ETH_ALEN], + const guint8 remote[ETH_ALEN], + int rfcomm_channel, + NMBluez5DunFunc callback, + gpointer user_data, + GError **error) +{ + NMBluez5DunContext *context; + + context = g_slice_new0 (NMBluez5DunContext); + setba (context->src, adapter); + setba (context->dest, remote); + context->channel = (rfcomm_channel >= 0) ? rfcomm_channel : -1; + context->rfcomm_id = -1; + context->rfcomm_fd = -1; + context->callback = callback; + context->user_data = user_data; + return context; +} + +gboolean +nm_bluez5_dun_connect (NMBluez5DunContext *context, GError **error) +{ + /* Find channel */ + if (context->rfcomm_channel < 0) + return dun_find_channel (context, error); + + /* Connect */ + return dun_connect (context, error); } + +/* Only clean up connection-related stuff to allow reconnect */ +void +nm_bluez5_dun_cleanup (NMBluez5DunContext *context) +{ + g_return_if_fail (context != NULL); + + sdp_search_cleanuP (context); + + g_clear_pointer (&context->rfcomm_dev, g_free); + + if (context->rfcomm_fd >= 0) { + if (context->rfcomm_id >= 0) { + struct rfcomm_dev_req req = { 0 }; + + req.dev_id = rfcomm_id; + ioctl (context->rfcomm_fd, RFCOMMRELEASEDEV, &req); + context->rfcomm_id = -1; + } + close (context->rfcomm_fd); + context->rfcomm_fd = -1; + } +} + +void +nm_bluez5_dun_free (NMBluez5DunContext *context) +{ + g_return_if_fail (context != NULL); + + nm_bluez5_dun_cleanup (context); + g_slice_free (NMBluez5DunContext, context); +} + diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h index f8c8ed7e2c..6bee6caf0b 100644 --- a/src/devices/bluetooth/nm-bluez5-dun.h +++ b/src/devices/bluetooth/nm-bluez5-dun.h @@ -25,26 +25,26 @@ typedef struct NMBluez5DunContext; -typedef void (*NMBluez5DunFindChannelFunc) (NMBluez5DunContext ctx, - int rfcomm_channel, - GError *error, - gpointer user_data); +typedef void (*NMBluez5DunFunc) (NMBluez5DunContext *context, + int rfcomm_channel, + const char *rfcomm_dev, + GError *error, + gpointer user_data); -NMBluez5DunContext *nm_bluez5_dun_find_channel (const guint8 adapter[ETH_ALEN], - const guint8 remote[ETH_ALEN], - NMBluez5DunFindChannelFunc callback, - gpointer user_data); +NMBluez5DunContext *nm_bluez5_dun_new (const guint8 adapter[ETH_ALEN], + const guint8 remote[ETH_ALEN], + int rfcomm_channel, + NMBluez5DunFunc callback, + gpointer user_data, + GError **error); -void nm_bluez5_dun_find_channel_cancel (gconstpointer id); +gboolean nm_bluez5_dun_connect (NMBluez5DunContext *context, GError **error); -gboolean nm_bluez5_dun_connect (const guint8 adapter[ETH_ALEN], - const guint8 remote[ETH_ALEN], - int rfcomm_channel, - int *out_rfcomm_fd, - char **out_rfcomm_dev, - int *out_rfcomm_id, - GError **error); +/* Clean up connection resources */ +void nm_bluez5_dun_cleanup (NMBluez5DunContext *context); -void nm_bluez5_dun_cleanup (int sk, int rfcomm_id); +/* Clean up and dispose all resources */ +void nm_bluez5_dun_free (NMBluez5DunContext *context); #endif /* _NM_BLUEZ5_UTILS_H_ */ + |