mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-01 17:18:09 +02:00
Most callers would pass FALSE to nm_utils_error_is_cancelled(). That's not very useful. Split the two functions and have nm_utils_error_is_cancelled() and nm_utils_error_is_cancelled_is_disposing().
865 lines
26 KiB
C
865 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2014 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "nm-default.h"
|
|
|
|
#include <sys/socket.h>
|
|
#include <bluetooth/sdp.h>
|
|
#include <bluetooth/sdp_lib.h>
|
|
#include <bluetooth/rfcomm.h>
|
|
#include <net/ethernet.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "nm-bluez5-dun.h"
|
|
#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;
|
|
|
|
GSource *source;
|
|
|
|
gint64 connect_open_tty_started_at;
|
|
|
|
gulong cancelled_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;
|
|
|
|
GSource *rfcomm_tty_poll_source;
|
|
|
|
int rfcomm_sock_fd;
|
|
int rfcomm_tty_fd;
|
|
int rfcomm_tty_no;
|
|
int rfcomm_channel;
|
|
|
|
bdaddr_t src;
|
|
bdaddr_t dst;
|
|
|
|
char src_str[];
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
#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)
|
|
{
|
|
return context->src_str;
|
|
}
|
|
|
|
const char *
|
|
nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context)
|
|
{
|
|
return context->dst_str;
|
|
}
|
|
|
|
const char *
|
|
nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context)
|
|
{
|
|
return context->rfcomm_tty_path;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
_rfcomm_tty_poll_cb (int fd,
|
|
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" : "");
|
|
|
|
nm_clear_g_source_inst (&context->rfcomm_tty_poll_source);
|
|
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_nsec () > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NSEC_PER_MSEC)) {
|
|
gs_free_error GError *error = NULL;
|
|
|
|
nm_clear_g_source_inst (&context->cdat->source);
|
|
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;
|
|
}
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
_connect_open_tty (NMBluez5DunContext *context)
|
|
{
|
|
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) {
|
|
_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_nsec ();
|
|
context->cdat->source = nm_g_timeout_source_new (100,
|
|
G_PRIORITY_DEFAULT,
|
|
_connect_open_tty_retry_cb,
|
|
context,
|
|
NULL);
|
|
g_source_attach (context->cdat->source, NULL);
|
|
}
|
|
return -errsv;
|
|
}
|
|
|
|
context->rfcomm_tty_fd = fd;
|
|
|
|
context->rfcomm_tty_poll_source = nm_g_unix_fd_source_new (context->rfcomm_tty_fd,
|
|
G_IO_ERR | G_IO_HUP,
|
|
G_PRIORITY_DEFAULT,
|
|
_rfcomm_tty_poll_cb,
|
|
context,
|
|
NULL);
|
|
g_source_attach (context->rfcomm_tty_poll_source, NULL);
|
|
|
|
_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_sock_fd, RFCOMMCREATEDEV, &req);
|
|
if (devid < 0) {
|
|
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_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);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_connect_socket_connect_cb (int fd,
|
|
GIOCondition condition,
|
|
gpointer user_data)
|
|
{
|
|
NMBluez5DunContext *context = user_data;
|
|
gs_free_error GError *error = NULL;
|
|
int errsv = 0;
|
|
socklen_t slen = sizeof(errsv);
|
|
int r;
|
|
|
|
nm_clear_g_source_inst (&context->cdat->source);
|
|
|
|
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;
|
|
}
|
|
|
|
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
|
|
_connect_socket_connect (NMBluez5DunContext *context)
|
|
{
|
|
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) {
|
|
|
|
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);
|
|
|
|
context->cdat->source = nm_g_unix_fd_source_new (context->rfcomm_sock_fd,
|
|
G_IO_OUT,
|
|
G_PRIORITY_DEFAULT,
|
|
_connect_socket_connect_cb,
|
|
context,
|
|
NULL);
|
|
g_source_attach (context->cdat->source, NULL);
|
|
return;
|
|
}
|
|
|
|
_connect_create_rfcomm (context);
|
|
}
|
|
|
|
static void
|
|
_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;
|
|
int seqlen = 0;
|
|
int bytesleft = size;
|
|
uint8_t dataType;
|
|
int channel = -1;
|
|
|
|
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) {
|
|
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);
|
|
|
|
_LOGD (context, "SDP sequence type scanned=%d length=%d",
|
|
scanned, seqlen);
|
|
|
|
scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen);
|
|
if ( !scanned
|
|
|| !seqlen) {
|
|
/* Short read or unknown sequence type */
|
|
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;
|
|
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);
|
|
|
|
_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);
|
|
|
|
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;
|
|
}
|
|
|
|
context->rfcomm_channel = channel;
|
|
}
|
|
|
|
static gboolean
|
|
_connect_sdp_search_io_cb (int fd,
|
|
GIOCondition condition,
|
|
gpointer user_data)
|
|
{
|
|
NMBluez5DunContext *context = user_data;
|
|
gs_free_error GError *error = NULL;
|
|
int errsv;
|
|
|
|
if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
|
|
_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");
|
|
nm_clear_g_source_inst (&context->cdat->source);
|
|
_context_invoke_callback_fail_and_free (context, error);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
if (sdp_process (context->cdat->sdp_session) == 0) {
|
|
_LOGD (context, "SDP search still not finished");
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
nm_clear_g_source_inst (&context->cdat->source);
|
|
|
|
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;
|
|
}
|
|
|
|
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
|
|
_connect_sdp_session_start_on_idle_cb (gpointer user_data)
|
|
{
|
|
NMBluez5DunContext *context = user_data;
|
|
gs_free_error GError *error = NULL;
|
|
|
|
nm_clear_g_source_inst (&context->cdat->source);
|
|
|
|
_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 (int fd,
|
|
GIOCondition condition,
|
|
gpointer user_data)
|
|
{
|
|
NMBluez5DunContext *context = user_data;
|
|
sdp_list_t *search;
|
|
sdp_list_t *attrs;
|
|
uuid_t svclass;
|
|
uint16_t attr;
|
|
int errsv;
|
|
int fd_err = 0;
|
|
int r;
|
|
socklen_t len = sizeof (fd_err);
|
|
gs_free_error GError *error = NULL;
|
|
|
|
nm_clear_g_source_inst (&context->cdat->source);
|
|
|
|
_LOGD (context, "sdp-session ready to connect with fd=%d", fd);
|
|
|
|
if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) {
|
|
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 (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_inst (&context->cdat->source);
|
|
context->cdat->source = nm_g_timeout_source_new (1000,
|
|
G_PRIORITY_DEFAULT,
|
|
_connect_sdp_session_start_on_idle_cb,
|
|
context,
|
|
NULL);
|
|
g_source_attach (context->cdat->source, NULL);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
|
|
"error on Service Discovery socket: %s (%d)",
|
|
nm_strerror_native (errsv), errsv);
|
|
goto done;
|
|
}
|
|
|
|
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 set Service Discovery notification");
|
|
goto done;
|
|
}
|
|
|
|
sdp_uuid16_create (&svclass, DIALUP_NET_SVCLASS_ID);
|
|
search = sdp_list_append (NULL, &svclass);
|
|
attr = SDP_ATTR_PROTO_DESC_LIST;
|
|
attrs = sdp_list_append (NULL, &attr);
|
|
|
|
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);
|
|
|
|
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 = nm_g_unix_fd_source_new (fd,
|
|
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
|
|
G_PRIORITY_DEFAULT,
|
|
_connect_sdp_search_io_cb,
|
|
context,
|
|
NULL);
|
|
g_source_attach (context->cdat->source, NULL);
|
|
|
|
done:
|
|
if (error)
|
|
_context_invoke_callback_fail_and_free (context, error);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
_connect_cancelled_cb (GCancellable *cancellable,
|
|
NMBluez5DunContext *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);
|
|
}
|
|
|
|
static gboolean
|
|
_connect_sdp_session_start (NMBluez5DunContext *context,
|
|
GError **error)
|
|
{
|
|
nm_assert (context->cdat);
|
|
|
|
nm_clear_g_source_inst (&context->cdat->source);
|
|
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;
|
|
}
|
|
|
|
context->cdat->source = nm_g_unix_fd_source_new (sdp_get_socket (context->cdat->sdp_session),
|
|
G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
|
|
G_PRIORITY_DEFAULT,
|
|
_connect_sdp_io_cb,
|
|
context,
|
|
NULL);
|
|
g_source_attach (context->cdat->source, NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
nm_bluez5_dun_disconnect (NMBluez5DunContext *context)
|
|
{
|
|
nm_assert (context);
|
|
nm_assert (!context->cdat);
|
|
|
|
_LOGD (context, "disconnecting DUN connection");
|
|
|
|
_context_free (context);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_inst (&cdat->source);
|
|
|
|
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);
|
|
}
|
|
|
|
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))
|
|
_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)
|
|
{
|
|
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_inst (&context->rfcomm_tty_poll_source);
|
|
|
|
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));
|
|
}
|
|
|
|
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);
|
|
}
|