diff options
Diffstat (limited to 'src/devices/bluetooth/nm-bluez5-dun.c')
-rw-r--r-- | src/devices/bluetooth/nm-bluez5-dun.c | 910 |
1 files changed, 686 insertions, 224 deletions
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c index 1338bcb811..3038e2635f 100644 --- a/src/devices/bluetooth/nm-bluez5-dun.c +++ b/src/devices/bluetooth/nm-bluez5-dun.c @@ -19,145 +19,407 @@ #include "nm-bt-error.h" #include "NetworkManagerUtils.h" +#define RFCOMM_FMT "/dev/rfcomm%d" + +/*****************************************************************************/ + +typedef struct { + GCancellable *cancellable; + NMBluez5DunConnectCb callback; + gpointer callback_user_data; + + sdp_session_t *sdp_session; + + GError *rfcomm_sdp_search_error; + + gint64 connect_open_tty_started_at; + + gulong cancelled_id; + + guint source_id; + + guint8 sdp_session_try_count; +} ConnectData; + struct _NMBluez5DunContext { + const char *dst_str; + + ConnectData *cdat; + + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb; + gpointer notify_tty_hangup_user_data; + + char *rfcomm_tty_path; + + int rfcomm_sock_fd; + int rfcomm_tty_fd; + int rfcomm_tty_no; + int rfcomm_channel; + + guint rfcomm_tty_poll_id; + bdaddr_t src; bdaddr_t dst; - char *src_str; - char *dst_str; - int rfcomm_channel; - int rfcomm_fd; - int rfcomm_tty_fd; - int rfcomm_id; - NMBluez5DunFunc callback; - gpointer user_data; - sdp_session_t *sdp_session; - guint sdp_watch_id; + + char src_str[]; }; -static void -dun_connect (NMBluez5DunContext *context) +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_BT +#define _NMLOG_PREFIX_NAME "bluez" +#define _NMLOG(level, context, ...) \ + G_STMT_START { \ + if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \ + const NMBluez5DunContext *const _context = (context); \ + \ + _nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \ + "%s: DUN[%s] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + _context->src_str \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } G_STMT_END + +/*****************************************************************************/ + +static void _context_invoke_callback_success (NMBluez5DunContext *context); +static void _context_invoke_callback_fail_and_free (NMBluez5DunContext *context, + GError *error); +static void _context_free (NMBluez5DunContext *context); +static int _connect_open_tty (NMBluez5DunContext *context); +static gboolean _connect_sdp_session_start (NMBluez5DunContext *context, + GError **error); + +/*****************************************************************************/ + +NM_AUTO_DEFINE_FCN0 (NMBluez5DunContext *, _nm_auto_free_context, _context_free) +#define nm_auto_free_context nm_auto(_nm_auto_free_context) + +/*****************************************************************************/ + +const char * +nm_bluez5_dun_context_get_adapter (const NMBluez5DunContext *context) { - struct sockaddr_rc sa; - int devid, try = 30; - char tty[100]; - const int ttylen = sizeof (tty) - 1; - GError *error = NULL; - int errsv; + return context->src_str; +} - struct rfcomm_dev_req req = { - .flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP), - .dev_id = -1, - .channel = context->rfcomm_channel - }; +const char * +nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context) +{ + return context->dst_str; +} - context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC, BTPROTO_RFCOMM); - if (context->rfcomm_fd < 0) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to create RFCOMM socket: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; - } +const char * +nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context) +{ + return context->rfcomm_tty_path; +} - /* Connect to the remote device */ - sa.rc_family = AF_BLUETOOTH; - sa.rc_channel = 0; - memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN); - if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to bind socket: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; +/*****************************************************************************/ + +static gboolean +_rfcomm_tty_poll_cb (GIOChannel *stream, + GIOCondition condition, + gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + + _LOGD (context, "receive %s%s%s signal on rfcomm file descriptor", + NM_FLAGS_HAS (condition, G_IO_ERR) ? "ERR" : "", + NM_FLAGS_ALL (condition, G_IO_HUP | G_IO_ERR) ? "," : "", + NM_FLAGS_HAS (condition, G_IO_HUP) ? "HUP" : ""); + + context->rfcomm_tty_poll_id = 0; + context->notify_tty_hangup_cb (context, + context->notify_tty_hangup_user_data); + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_open_tty_retry_cb (gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + int r; + + r = _connect_open_tty (context); + if (r >= 0) + return G_SOURCE_REMOVE; + + if (nm_utils_get_monotonic_timestamp_ns () > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NS_PER_MSEC)) { + gs_free_error GError *error = NULL; + + context->cdat->source_id = 0; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "give up waiting to open %s device: %s (%d)", + context->rfcomm_tty_path, + nm_strerror_native (r), + -r); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } - sa.rc_channel = context->rfcomm_channel; - memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN); - if (connect (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof (sa)) ) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to connect to remote device: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; + return G_SOURCE_CONTINUE; +} + +static int +_connect_open_tty (NMBluez5DunContext *context) +{ + nm_auto_unref_io_channel GIOChannel *io_channel = NULL; + int fd; + int errsv; + + fd = open (context->rfcomm_tty_path, O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (fd < 0) { + errsv = NM_ERRNO_NATIVE (errno); + + if (context->cdat->source_id == 0) { + _LOGD (context, "failed opening tty "RFCOMM_FMT": %s (%d). Start polling...", + context->rfcomm_tty_no, + nm_strerror_native (errsv), + errsv); + context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_ns (); + context->cdat->source_id = g_timeout_add (100, + _connect_open_tty_retry_cb, + context); + } + return -errsv; } - nm_log_dbg (LOGD_BT, "(%s): connected to %s on channel %d", - context->src_str, context->dst_str, context->rfcomm_channel); + context->rfcomm_tty_fd = fd; + + io_channel = g_io_channel_unix_new (context->rfcomm_tty_fd); + context->rfcomm_tty_poll_id = g_io_add_watch (io_channel, + G_IO_ERR | G_IO_HUP, + _rfcomm_tty_poll_cb, + context); + + _context_invoke_callback_success (context); + return 0; +} + +static void +_connect_create_rfcomm (NMBluez5DunContext *context) +{ + gs_free_error GError *error = NULL; + struct rfcomm_dev_req req; + int devid; + int errsv; + int r; + + _LOGD (context, "connected to %s on channel %d", + context->dst_str, context->rfcomm_channel); /* Create an RFCOMM kernel device for the DUN channel */ + memset (&req, 0, sizeof (req)); + req.dev_id = -1; + req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP); + req.channel = context->rfcomm_channel; memcpy (&req.src, &context->src, ETH_ALEN); memcpy (&req.dst, &context->dst, ETH_ALEN); - devid = ioctl (context->rfcomm_fd, RFCOMMCREATEDEV, &req); + devid = ioctl (context->rfcomm_sock_fd, RFCOMMCREATEDEV, &req); if (devid < 0) { - errsv = errno; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to create rfcomm device: (%d) %s", - errsv, nm_strerror_native (errsv)); - goto done; + errsv = NM_ERRNO_NATIVE (errno); + if (errsv == EBADFD) { + /* hm. We use a non-blocking socket to connect. Above getsockopt(SOL_SOCKET,SO_ERROR) indicated + * success, but still now we fail with EBADFD. I think that is a bug and we should get the + * failure during connect(). + * + * Anyway, craft a less confusing error message than + * "failed to create rfcomm device: File descriptor in bad state (77)". */ + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "unknown failure to connect to DUN device"); + } else { + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to create rfcomm device: %s (%d)", + nm_strerror_native (errsv), errsv); + } + _context_invoke_callback_fail_and_free (context, error); + return; } - context->rfcomm_id = devid; - snprintf (tty, ttylen, "/dev/rfcomm%d", devid); - while ((context->rfcomm_tty_fd = open (tty, O_RDONLY | O_NOCTTY | O_CLOEXEC)) < 0 && try--) { - if (try) { - g_usleep (100 * 1000); - continue; - } + context->rfcomm_tty_no = devid; + context->rfcomm_tty_path = g_strdup_printf (RFCOMM_FMT, devid); + + r = _connect_open_tty (context); + if (r < 0) { + /* we created the rfcomm device, but cannot yet open it. That means, we are + * not yet fully connected. However, we notify the caller about "what we learned + * so far". Note that this happens synchronously. + * + * The purpose is that once we proceed synchrnously, modem-manager races with + * the detection of the modem. We want to notify the caller first about the + * device name. */ + context->cdat->callback (NULL, + context->rfcomm_tty_path, + NULL, + context->cdat->callback_user_data); + } +} - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to find rfcomm device: %s", - tty); - break; +static gboolean +_connect_socket_connect_cb (GIOChannel *stream, + GIOCondition condition, + gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + gs_free GError *error = NULL; + int errsv = 0; + socklen_t slen = sizeof(errsv); + int r; + + context->cdat->source_id = 0; + + r = getsockopt (context->rfcomm_sock_fd, SOL_SOCKET, SO_ERROR, &errsv, &slen); + + if (r < 0) { + errsv = errno; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to complete connecting RFCOMM socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } -done: - context->callback (context, tty, error, context->user_data); + if (errsv != 0) { + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect RFCOMM socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; + } + + _connect_create_rfcomm (context); + return G_SOURCE_REMOVE; } static void -sdp_search_cleanup (NMBluez5DunContext *context) +_connect_socket_connect (NMBluez5DunContext *context) { - if (context->sdp_session) { - sdp_close (context->sdp_session); - context->sdp_session = NULL; + gs_free_error GError *error = NULL; + struct sockaddr_rc sa; + int errsv; + + context->rfcomm_sock_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_RFCOMM); + if (context->rfcomm_sock_fd < 0) { + errsv = errno; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to create RFCOMM socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return; + } + + /* Connect to the remote device */ + memset (&sa, 0, sizeof (sa)); + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = 0; + memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN); + if (bind (context->rfcomm_sock_fd, + (struct sockaddr *) &sa, + sizeof(sa)) != 0) { + errsv = errno; + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to bind socket: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return; + } + + memset (&sa, 0, sizeof (sa)); + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = context->rfcomm_channel; + memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN); + if (connect (context->rfcomm_sock_fd, + (struct sockaddr *) &sa, + sizeof (sa)) != 0) { + nm_auto_unref_io_channel GIOChannel *io_channel = NULL; + + errsv = errno; + if (errsv != EINPROGRESS) { + g_set_error (&error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect to remote device: %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return; + } + + _LOGD (context, "connecting to %s on channel %d...", + context->dst_str, + context->rfcomm_channel); + + io_channel = g_io_channel_unix_new (context->rfcomm_sock_fd); + context->cdat->source_id = g_io_add_watch (io_channel, + G_IO_OUT, + _connect_socket_connect_cb, + context); + return; } - nm_clear_g_source (&context->sdp_watch_id); + _connect_create_rfcomm (context); } static void -sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data) +_connect_sdp_search_cb (uint8_t type, + uint16_t status, + uint8_t *rsp, + size_t size, + void *user_data) { NMBluez5DunContext *context = user_data; - int scanned, seqlen = 0, bytesleft = size; + int scanned; + int seqlen = 0; + int bytesleft = size; uint8_t dataType; int channel = -1; - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished with type=%d status=%d", - context->src_str, context->dst_str, status, type); + if ( context->cdat->rfcomm_sdp_search_error + || context->rfcomm_channel >= 0) + return; + + _LOGD (context, "SDP search finished with type=%d status=%d", + status, type); /* SDP response received */ - if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { - GError *error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Did not get a Service Discovery response"); - context->callback (context, NULL, error, context->user_data); - goto done; + if ( status + || type != SDP_SVC_SEARCH_ATTR_RSP) { + g_set_error (&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "did not get a Service Discovery response"); + return; } scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen); - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP sequence type scanned=%d length=%d", - context->src_str, context->dst_str, scanned, seqlen); + _LOGD (context, "SDP sequence type scanned=%d length=%d", + scanned, seqlen); scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen); - if (!scanned || !seqlen) { + if ( !scanned + || !seqlen) { /* Short read or unknown sequence type */ - GError *error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Improper Service Discovery response"); - context->callback (context, NULL, error, context->user_data); - goto done; + g_set_error (&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "improper Service Discovery response"); + return; } rsp += scanned; @@ -181,90 +443,153 @@ sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t siz channel = sdp_get_proto_port (protos, RFCOMM_UUID); sdp_list_free (protos, NULL); - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP channel=%d", - context->src_str, context->dst_str, channel); + _LOGD (context, "SDP channel=%d", + channel); } sdp_record_free (rec); scanned += recsize; rsp += recsize; bytesleft -= recsize; - } while ((scanned < (ssize_t) size) && (bytesleft > 0) && (channel < 0)); - -done: - if (channel != -1) { - context->rfcomm_channel = channel; - dun_connect (context); + } while ( scanned < (ssize_t) size + && bytesleft > 0 + && channel < 0); + + if (channel == -1) { + g_set_error (&context->cdat->rfcomm_sdp_search_error, + NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "did not receive rfcomm-channel"); + return; } - sdp_search_cleanup (context); + context->rfcomm_channel = channel; } static gboolean -sdp_search_process_cb (GIOChannel *channel, GIOCondition condition, gpointer user_data) +_connect_sdp_search_io_cb (GIOChannel *io_channel, + GIOCondition condition, + gpointer user_data) { NMBluez5DunContext *context = user_data; - - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search progressed with condition=%d", - context->src_str, context->dst_str, condition); + gs_free GError *error = NULL; + int errsv; if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { - GError *error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Service Discovery interrupted"); - context->callback (context, NULL, error, context->user_data); - sdp_search_cleanup (context); - return FALSE; + _LOGD (context, "SDP search returned with invalid IO condition 0x%x", + (guint) condition); + error = g_error_new (NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "Service Discovery interrupted"); + context->cdat->source_id = 0; + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } - if (sdp_process (context->sdp_session) < 0) { - nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished", - context->src_str, context->dst_str); + if (sdp_process (context->cdat->sdp_session) == 0) { + _LOGD (context, "SDP search still not finished"); + return G_SOURCE_CONTINUE; + } - /* Search finished successfully. */ - return FALSE; + context->cdat->source_id = 0; + + if ( context->rfcomm_channel < 0 + && !context->cdat->rfcomm_sdp_search_error) { + errsv = sdp_get_error (context->cdat->sdp_session); + _LOGD (context, "SDP search failed: %s (%d)", + nm_strerror_native (errsv), errsv); + error = g_error_new (NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "Service Discovery failed with %s (%d)", + nm_strerror_native (errsv), errsv); + _context_invoke_callback_fail_and_free (context, error); + return G_SOURCE_REMOVE; } - /* Search progressed successfully. */ - return TRUE; + if (context->cdat->rfcomm_sdp_search_error) { + _LOGD (context, "SDP search failed to complete: %s", context->cdat->rfcomm_sdp_search_error->message); + _context_invoke_callback_fail_and_free (context, context->cdat->rfcomm_sdp_search_error); + return G_SOURCE_REMOVE; + } + + nm_clear_pointer (&context->cdat->sdp_session, sdp_close); + + _connect_socket_connect (context); + + return G_SOURCE_REMOVE; } static gboolean -sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_data) +_connect_sdp_session_start_on_idle_cb (gpointer user_data) { NMBluez5DunContext *context = user_data; - sdp_list_t *search, *attrs; + gs_free_error GError *error = NULL; + + context->cdat->source_id = 0; + + _LOGD (context, "retry starting sdp-session..."); + + if (!_connect_sdp_session_start (context, &error)) + _context_invoke_callback_fail_and_free (context, error); + + return G_SOURCE_REMOVE; +} + +static gboolean +_connect_sdp_io_cb (GIOChannel *io_channel, + GIOCondition condition, + gpointer user_data) +{ + NMBluez5DunContext *context = user_data; + sdp_list_t *search; + sdp_list_t *attrs; uuid_t svclass; uint16_t attr; - int fd, fd_err = 0; - int err; + int fd; + int errsv; + int fd_err = 0; + int r; socklen_t len = sizeof (fd_err); - GError *error = NULL; + gs_free_error GError *error = NULL; + + context->cdat->source_id = 0; - context->sdp_watch_id = 0; + fd = g_io_channel_unix_get_fd (io_channel); + + _LOGD (context, "sdp-session ready to connect with fd=%d", fd); - fd = g_io_channel_unix_get_fd (channel); if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) { - err = errno; - nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d", - context->src_str, context->dst_str, err); - } else { - err = fd_err; - nm_log_dbg (LOGD_BT, "(%s -> %s): SO_ERROR error=%d", - context->src_str, context->dst_str, fd_err); + errsv = NM_ERRNO_NATIVE (errno); + error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "error for getsockopt on Service Discovery socket: %s (%d)", + nm_strerror_native (errsv), errsv); + goto done; } - if (err != 0) { + if (fd_err != 0) { + errsv = nm_errno_native (fd_err); + + if ( NM_IN_SET (errsv, ECONNREFUSED, EHOSTDOWN) + && --context->cdat->sdp_session_try_count > 0) { + /* *sigh* */ + _LOGD (context, "sdp-session failed with %s (%d). Retry in a bit", nm_strerror_native (errsv), errsv); + nm_clear_g_source (&context->cdat->source_id); + context->cdat->source_id = g_timeout_add (1000, + _connect_sdp_session_start_on_idle_cb, + context); + return G_SOURCE_REMOVE; + } + error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Error on Service Discovery socket: (%d) %s", - err, nm_strerror_native (err)); + "error on Service Discovery socket: %s (%d)", + nm_strerror_native (errsv), errsv); goto done; } - if (sdp_set_notify (context->sdp_session, sdp_search_completed_cb, context) < 0) { + if (sdp_set_notify (context->cdat->sdp_session, _connect_sdp_search_cb, context) < 0) { /* Should not be reached, only can fail if we passed bad sdp_session. */ error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Could not request Service Discovery notification"); + "could not set Service Discovery notification"); goto done; } @@ -273,124 +598,261 @@ 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 (context->sdp_session, search, SDP_ATTR_REQ_INDIVIDUAL, attrs)) { - /* Set callback responsible for update the internal SDP transaction */ - 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); - } else { - err = sdp_get_error (context->sdp_session); - error = g_error_new (NM_BT_ERROR, - NM_BT_ERROR_DUN_CONNECT_FAILED, - "Error starting Service Discovery: (%d) %s", - err, nm_strerror_native (err)); - } + r = sdp_service_search_attr_async (context->cdat->sdp_session, + search, + SDP_ATTR_REQ_INDIVIDUAL, + attrs); sdp_list_free (attrs, NULL); sdp_list_free (search, NULL); -done: - if (error) { - context->callback (context, NULL, error, context->user_data); - sdp_search_cleanup (context); + if (r < 0) { + errsv = nm_errno_native (sdp_get_error (context->cdat->sdp_session)); + error = g_error_new (NM_BT_ERROR, + NM_BT_ERROR_DUN_CONNECT_FAILED, + "error starting Service Discovery: %s (%d)", + nm_strerror_native (errsv), errsv); + goto done; } + /* Set callback responsible for update the internal SDP transaction */ + context->cdat->source_id = g_io_add_watch (io_channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + _connect_sdp_search_io_cb, + context); + +done: + if (error) + _context_invoke_callback_fail_and_free (context, error); return G_SOURCE_REMOVE; } -NMBluez5DunContext * -nm_bluez5_dun_new (const char *adapter, - const char *remote) +/*****************************************************************************/ +static void +_connect_cancelled_cb (GCancellable *cancellable, + NMBluez5DunContext *context) { - NMBluez5DunContext *context; - - context = g_slice_new0 (NMBluez5DunContext); - str2ba (adapter, &context->src); - str2ba (remote, &context->dst); - context->src_str = g_strdup (adapter); - context->dst_str = g_strdup (remote); - context->rfcomm_channel = -1; - context->rfcomm_id = -1; - context->rfcomm_fd = -1; - return context; + gs_free_error GError *error = NULL; + + if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) + g_return_if_reached (); + + _context_invoke_callback_fail_and_free (context, error); } -void -nm_bluez5_dun_connect (NMBluez5DunContext *context, - NMBluez5DunFunc callback, - gpointer user_data) +static gboolean +_connect_sdp_session_start (NMBluez5DunContext *context, + GError **error) { - GIOChannel *channel; + nm_auto_unref_io_channel GIOChannel *io_channel = NULL; - context->callback = callback; - context->user_data = user_data; + nm_assert (context->cdat); - if (context->rfcomm_channel != -1) { - nm_log_dbg (LOGD_BT, "(%s): channel number on device %s cached: %d", - context->src_str, context->dst_str, context->rfcomm_channel); - /* FIXME: don't invoke the callback synchronously. */ - dun_connect (context); - return; + nm_clear_g_source (&context->cdat->source_id); + nm_clear_pointer (&context->cdat->sdp_session, sdp_close); + + context->cdat->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING); + if (!context->cdat->sdp_session) { + int errsv = nm_errno_native (errno); + + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "failed to connect to the SDP server: %s (%d)", + nm_strerror_native (errsv), errsv); + return FALSE; } - nm_log_dbg (LOGD_BT, "(%s): starting channel number discovery for device %s", - context->src_str, context->dst_str); + io_channel = g_io_channel_unix_new (sdp_get_socket (context->cdat->sdp_session)); + context->cdat->source_id = g_io_add_watch (io_channel, + G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + _connect_sdp_io_cb, + context); + return TRUE; +} - context->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING); - if (!context->sdp_session) { - int err = errno; - GError *error; +/*****************************************************************************/ + +gboolean +nm_bluez5_dun_connect (const char *adapter, + const char *remote, + GCancellable *cancellable, + NMBluez5DunConnectCb callback, + gpointer callback_user_data, + NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb, + gpointer notify_tty_hangup_user_data, + GError **error) +{ + nm_auto_free_context NMBluez5DunContext *context = NULL; + ConnectData *cdat; + gsize src_l; + gsize dst_l; + + g_return_val_if_fail (adapter, FALSE); + g_return_val_if_fail (remote, FALSE); + g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (callback, FALSE); + g_return_val_if_fail (notify_tty_hangup_cb, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + nm_assert (!g_cancellable_is_cancelled (cancellable)); + + src_l = strlen (adapter) + 1; + dst_l = strlen (remote) + 1; + + cdat = g_slice_new (ConnectData); + *cdat = (ConnectData) { + .callback = callback, + .callback_user_data = callback_user_data, + .cancellable = g_object_ref (cancellable), + .sdp_session_try_count = 5, + }; - error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, - "Failed to connect to the SDP server: (%d) %s", - err, nm_strerror_native (err)); - /* FIXME: don't invoke the callback synchronously. */ - context->callback (context, NULL, error, context->user_data); - return; + context = g_malloc (sizeof (NMBluez5DunContext) + src_l + dst_l); + *context = (NMBluez5DunContext) { + .cdat = cdat, + .notify_tty_hangup_cb = notify_tty_hangup_cb, + .notify_tty_hangup_user_data = notify_tty_hangup_user_data, + .rfcomm_tty_no = -1, + .rfcomm_sock_fd = -1, + .rfcomm_tty_fd = -1, + .rfcomm_channel = -1, + }; + memcpy (&context->src_str[0], adapter, src_l); + context->dst_str = &context->src_str[src_l]; + memcpy ((char *) context->dst_str, remote, dst_l); + + if (str2ba (adapter, &context->src) < 0) { + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "invalid source"); + return FALSE; } - /* FIXME(shutdown): make connect cancellable. */ - 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); + if (str2ba (remote, &context->dst) < 0) { + g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED, + "invalid remote"); + return FALSE; + } + + context->cdat->cancelled_id = g_signal_connect (context->cdat->cancellable, + "cancelled", + G_CALLBACK (_connect_cancelled_cb), + context); + + if (!_connect_sdp_session_start (context, error)) + return FALSE; + + _LOGD (context, "starting channel number discovery for device %s", + context->dst_str); + + g_steal_pointer (&context); + return TRUE; } -/* Only clean up connection-related stuff to allow reconnect */ +/*****************************************************************************/ + void -nm_bluez5_dun_cleanup (NMBluez5DunContext *context) +nm_bluez5_dun_disconnect (NMBluez5DunContext *context) { - g_return_if_fail (context != NULL); + nm_assert (context); + nm_assert (!context->cdat); - sdp_search_cleanup (context); + _LOGD (context, "disconnecting DUN connection"); - if (context->rfcomm_fd >= 0) { - if (context->rfcomm_id >= 0) { - struct rfcomm_dev_req req = { 0 }; + _context_free (context); +} - req.dev_id = context->rfcomm_id; - (void) ioctl (context->rfcomm_fd, RFCOMMRELEASEDEV, &req); - context->rfcomm_id = -1; - } - nm_close (context->rfcomm_fd); - context->rfcomm_fd = -1; - } +/*****************************************************************************/ - nm_close (context->rfcomm_tty_fd); - context->rfcomm_tty_fd = -1; +static void +_context_cleanup_connect_data (NMBluez5DunContext *context) +{ + ConnectData *cdat; + + cdat = g_steal_pointer (&context->cdat); + if (!cdat) + return; + + nm_clear_g_signal_handler (cdat->cancellable, &cdat->cancelled_id); + + nm_clear_g_source (&cdat->source_id); + + nm_clear_pointer (&cdat->sdp_session, sdp_close); + + g_clear_object (&cdat->cancellable); + + g_clear_error (&cdat->rfcomm_sdp_search_error); + + nm_g_slice_free (cdat); } -void -nm_bluez5_dun_free (NMBluez5DunContext *context) +static void +_context_invoke_callback (NMBluez5DunContext *context, + GError *error) +{ + NMBluez5DunConnectCb callback; + gpointer callback_user_data; + + nm_assert (context); + nm_assert (context->cdat); + nm_assert (context->cdat->callback); + nm_assert (error || context->rfcomm_tty_path); + + if (!error) + _LOGD (context, "connected via \"%s\"", context->rfcomm_tty_path); + else if (nm_utils_error_is_cancelled (error, FALSE)) + _LOGD (context, "cancelled"); + else + _LOGD (context, "failed to connect: %s", error->message); + + callback = context->cdat->callback; + callback_user_data = context->cdat->callback_user_data; + + _context_cleanup_connect_data (context); + + callback (error ? NULL : context, + error ? NULL : context->rfcomm_tty_path, + error, + callback_user_data); +} + +static void +_context_invoke_callback_success (NMBluez5DunContext *context) { - g_return_if_fail (context != NULL); + nm_assert (context->rfcomm_tty_path); + _context_invoke_callback (context, NULL); +} + +static void +_context_invoke_callback_fail_and_free (NMBluez5DunContext *context, + GError *error) +{ + nm_assert (error); + _context_invoke_callback (context, error); + _context_free (context); +} + +static void +_context_free (NMBluez5DunContext *context) +{ + nm_assert (context); + + _context_cleanup_connect_data (context); + + nm_clear_g_source (&context->rfcomm_tty_poll_id); + + if (context->rfcomm_sock_fd >= 0) { + if (context->rfcomm_tty_no >= 0) { + struct rfcomm_dev_req req; + + memset (&req, 0, sizeof (struct rfcomm_dev_req)); + req.dev_id = context->rfcomm_tty_no; + context->rfcomm_tty_no = -1; + (void) ioctl (context->rfcomm_sock_fd, RFCOMMRELEASEDEV, &req); + } + nm_close (nm_steal_fd (&context->rfcomm_sock_fd)); + } - nm_bluez5_dun_cleanup (context); - g_clear_pointer (&context->src_str, g_free); - g_clear_pointer (&context->dst_str, g_free); - g_slice_free (NMBluez5DunContext, context); + if (context->rfcomm_tty_fd >= 0) + nm_close (nm_steal_fd (&context->rfcomm_tty_fd)); + nm_clear_g_free (&context->rfcomm_tty_path); + g_free (context); } |