/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (C) 2013 - 2014 Red Hat, Inc. */ #include "nm-default.h" #include "nm-bluez-manager.h" #include #include #include #include #include "nm-glib-aux/nm-dbus-aux.h" #include "nm-glib-aux/nm-c-list.h" #include "nm-dbus-manager.h" #include "devices/nm-device-factory.h" #include "devices/nm-device-bridge.h" #include "nm-setting-bluetooth.h" #include "settings/nm-settings.h" #include "nm-bluez-common.h" #include "nm-device-bt.h" #include "nm-manager.h" #include "nm-bluez5-dun.h" #include "nm-core-internal.h" #include "platform/nm-platform.h" #include "nm-std-aux/nm-dbus-compat.h" /*****************************************************************************/ #if WITH_BLUEZ5_DUN #define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_DUN #else #define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_NONE #endif #define _NM_BT_CAPABILITY_SUPPORTED (NM_BT_CAPABILITY_NAP | _NM_BT_CAPABILITY_SUPPORTED_DUN) typedef struct { const char * bdaddr; CList lst_head; NMBluetoothCapabilities bt_type : 8; char bdaddr_data[]; } ConnDataHead; typedef struct { NMSettingsConnection *sett_conn; ConnDataHead * cdata_hd; CList lst; } ConnDataElem; typedef struct { GCancellable * ext_cancellable; GCancellable * int_cancellable; NMBtVTableRegisterCallback callback; gpointer callback_user_data; gulong ext_cancelled_id; } NetworkServerRegisterReqData; typedef struct { GCancellable * ext_cancellable; GCancellable * int_cancellable; NMBluezManagerConnectCb callback; gpointer callback_user_data; char * device_name; gulong ext_cancelled_id; guint timeout_id; guint timeout_wait_connect_id; } DeviceConnectReqData; typedef struct { const char *object_path; NMBluezManager *self; /* Fields name with "d_" prefix are purely cached values from BlueZ's * ObjectManager D-Bus interface. There is no logic whatsoever about * them. */ CList process_change_lst; struct { char *address; } d_adapter; struct { char *address; char *name; char *adapter; } d_device; struct { char *interface; } d_network; struct { CList lst; char * adapter_address; NMDevice * device_br; NetworkServerRegisterReqData *r_req_data; } x_network_server; struct { NMSettingsConnection *panu_connection; NMDeviceBt * device_bt; DeviceConnectReqData *c_req_data; NMBluez5DunContext * connect_dun_context; gulong device_bt_signal_id; } x_device; /* indicate whether the D-Bus object has the particular D-Bus interface. */ bool d_has_adapter_iface : 1; bool d_has_device_iface : 1; bool d_has_network_iface : 1; bool d_has_network_server_iface : 1; /* cached D-Bus properties for Device1 ("d_device*"). */ NMBluetoothCapabilities d_device_capabilities : 6; bool d_device_connected : 1; bool d_device_paired : 1; /* cached D-Bus properties for Network1 ("d_network*"). */ bool d_network_connected : 1; /* cached D-Bus properties for Adapter1 ("d_adapter*"). */ bool d_adapter_powered : 1; /* properties related to device ("x_device*"). */ NMBluetoothCapabilities x_device_connect_bt_type : 6; bool x_device_is_usable : 1; bool x_device_is_connected : 1; bool x_device_panu_connection_allow_create : 1; /* flag to remember last time when we checked wether the object * was a suitable adapter that is usable to a device. */ bool was_usable_adapter_for_device_before : 1; char _object_path_intern[]; } BzDBusObj; typedef struct { NMManager * manager; NMSettings *settings; GDBusConnection *dbus_connection; NMBtVTableNetworkServer vtable_network_server; GCancellable *name_owner_get_cancellable; GCancellable *get_managed_objects_cancellable; GHashTable *bzobjs; char *name_owner; GHashTable *conn_data_heads; GHashTable *conn_data_elems; CList network_server_lst_head; CList process_change_lst_head; guint name_owner_changed_id; guint managed_objects_changed_id; guint properties_changed_id; guint process_change_idle_id; bool settings_registered : 1; } NMBluezManagerPrivate; struct _NMBluezManager { NMDeviceFactory parent; NMBluezManagerPrivate _priv; }; struct _NMBluezManagerClass { NMDeviceFactoryClass parent; }; G_DEFINE_TYPE(NMBluezManager, nm_bluez_manager, NM_TYPE_DEVICE_FACTORY); #define NM_BLUEZ_MANAGER_GET_PRIVATE(self) \ _NM_GET_PRIVATE(self, NMBluezManager, NM_IS_BLUEZ_MANAGER) /*****************************************************************************/ NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES( NM_LINK_TYPE_BNEP) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_BLUETOOTH_SETTING_NAME)) G_MODULE_EXPORT NMDeviceFactory * nm_device_factory_create(GError **error) { return g_object_new(NM_TYPE_BLUEZ_MANAGER, NULL); } /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_BT #define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "bluez", __VA_ARGS__) /*****************************************************************************/ static NMBluetoothCapabilities convert_uuids_to_capabilities(const char *const *strv) { NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE; if (strv) { for (; strv[0]; strv++) { gs_free char *s_part1 = NULL; const char * str = strv[0]; const char * s; s = strchr(str, '-'); if (!s) continue; s_part1 = g_strndup(str, s - str); switch (_nm_utils_ascii_str_to_int64(s_part1, 16, 0, G_MAXINT, -1)) { case 0x1103: capabilities |= NM_BT_CAPABILITY_DUN; break; case 0x1116: capabilities |= NM_BT_CAPABILITY_NAP; break; default: break; } } } return capabilities; } /*****************************************************************************/ static void _cleanup_for_name_owner(NMBluezManager *self); static void _connect_disconnect(NMBluezManager *self, BzDBusObj *bzobj, const char *reason); static gboolean _bzobjs_network_server_is_usable(const BzDBusObj *bzobj, gboolean require_powered); static gboolean _bzobjs_is_dead(const BzDBusObj *bzobj); static gboolean _bzobjs_device_is_usable(const BzDBusObj *bzobj, BzDBusObj ** out_adapter_bzobj, gboolean * out_create_panu_connection); static gboolean _bzobjs_adapter_is_usable_for_device(const BzDBusObj *bzobj); static ConnDataHead * _conn_track_find_head(NMBluezManager *self, NMBluetoothCapabilities bt_type, const char *bdaddr); static void _process_change_idle_schedule(NMBluezManager *self, BzDBusObj *bzobj); static void _network_server_unregister_bridge(NMBluezManager *self, BzDBusObj *bzobj, const char *reason); static gboolean _connect_timeout_wait_connected_cb(gpointer user_data); /*****************************************************************************/ static void _dbus_call_complete_cb_nop(GObject *source_object, GAsyncResult *res, gpointer user_data) { /* we don't do anything at all. The only reason to register this * callback is so that GDBusConnection keeps the cancellable alive * long enough until the call completes. * * Note that this cancellable in turn is registered via * nm_shutdown_wait_obj_register_*(), to block shutdown until * we are done. */ } /*****************************************************************************/ static void _network_server_register_req_data_complete(NetworkServerRegisterReqData *r_req_data, GError *error) { nm_clear_g_signal_handler(r_req_data->ext_cancellable, &r_req_data->ext_cancelled_id); nm_clear_g_cancellable(&r_req_data->int_cancellable); if (r_req_data->callback) { gs_free_error GError *error_cancelled = NULL; if (g_cancellable_set_error_if_cancelled(r_req_data->ext_cancellable, &error_cancelled)) error = error_cancelled; r_req_data->callback(error, r_req_data->callback_user_data); } g_object_unref(r_req_data->ext_cancellable); nm_g_slice_free(r_req_data); } static void _device_connect_req_data_complete(DeviceConnectReqData *c_req_data, NMBluezManager * self, const char * device_name, GError * error) { nm_assert((!!device_name) != (!!error)); nm_clear_g_signal_handler(c_req_data->ext_cancellable, &c_req_data->ext_cancelled_id); nm_clear_g_cancellable(&c_req_data->int_cancellable); nm_clear_g_source(&c_req_data->timeout_id); nm_clear_g_source(&c_req_data->timeout_wait_connect_id); if (c_req_data->callback) { gs_free_error GError *error_cancelled = NULL; if (g_cancellable_set_error_if_cancelled(c_req_data->ext_cancellable, &error_cancelled)) { error = error_cancelled; device_name = NULL; } c_req_data->callback(self, TRUE, device_name, error, c_req_data->callback_user_data); } g_object_unref(c_req_data->ext_cancellable); nm_clear_g_free(&c_req_data->device_name); nm_g_slice_free(c_req_data); } /*****************************************************************************/ static BzDBusObj * _bz_dbus_obj_new(NMBluezManager *self, const char *object_path) { BzDBusObj *bzobj; gsize l; nm_assert(NM_IS_BLUEZ_MANAGER(self)); l = strlen(object_path) + 1; bzobj = g_malloc(sizeof(BzDBusObj) + l); *bzobj = (BzDBusObj){ .object_path = bzobj->_object_path_intern, .self = self, .x_network_server.lst = C_LIST_INIT(bzobj->x_network_server.lst), .process_change_lst = C_LIST_INIT(bzobj->process_change_lst), .x_device_panu_connection_allow_create = TRUE, }; memcpy(bzobj->_object_path_intern, object_path, l); return bzobj; } static void _bz_dbus_obj_free(BzDBusObj *bzobj) { nm_assert(bzobj); nm_assert(NM_IS_BLUEZ_MANAGER(bzobj->self)); nm_assert(!bzobj->x_network_server.device_br); nm_assert(!bzobj->x_network_server.r_req_data); nm_assert(!bzobj->x_device.c_req_data); c_list_unlink_stale(&bzobj->process_change_lst); c_list_unlink_stale(&bzobj->x_network_server.lst); g_free(bzobj->x_network_server.adapter_address); g_free(bzobj->d_adapter.address); g_free(bzobj->d_network.interface); g_free(bzobj->d_device.address); g_free(bzobj->d_device.name); g_free(bzobj->d_device.adapter); g_free(bzobj); } /*****************************************************************************/ static const char * _bzobj_to_string(const BzDBusObj *bzobj, char *buf, gsize len) { char * buf0 = buf; const char *prefix = ""; gboolean device_is_usable; gboolean create_panu_connection = FALSE; gboolean network_server_is_usable; char sbuf_cap[100]; if (len > 0) buf[0] = '\0'; if (bzobj->d_has_adapter_iface) { nm_utils_strbuf_append_str(&buf, &len, prefix); prefix = ", "; nm_utils_strbuf_append_str(&buf, &len, "Adapter1 {"); if (bzobj->d_adapter.address) { nm_utils_strbuf_append(&buf, &len, " d.address: \"%s\"", bzobj->d_adapter.address); if (bzobj->d_adapter_powered) nm_utils_strbuf_append_str(&buf, &len, ","); } if (bzobj->d_adapter_powered) nm_utils_strbuf_append(&buf, &len, " d.powered: 1"); nm_utils_strbuf_append_str(&buf, &len, " }"); } if (bzobj->d_has_device_iface) { const char *prefix1 = ""; nm_utils_strbuf_append_str(&buf, &len, prefix); prefix = ", "; nm_utils_strbuf_append_str(&buf, &len, "Device1 {"); if (bzobj->d_device.address) { nm_utils_strbuf_append(&buf, &len, "%s d.address: \"%s\"", prefix1, bzobj->d_device.address); prefix1 = ","; } if (bzobj->d_device.name) { nm_utils_strbuf_append(&buf, &len, "%s d.name: \"%s\"", prefix1, bzobj->d_device.name); prefix1 = ","; } if (bzobj->d_device.adapter) { nm_utils_strbuf_append(&buf, &len, "%s d.adapter: \"%s\"", prefix1, bzobj->d_device.adapter); prefix1 = ","; } if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { nm_utils_strbuf_append(&buf, &len, "%s d.capabilities: \"%s\"", prefix1, nm_bluetooth_capability_to_string(bzobj->d_device_capabilities, sbuf_cap, sizeof(sbuf_cap))); prefix1 = ","; } if (bzobj->d_device_connected) { nm_utils_strbuf_append(&buf, &len, "%s d.connected: 1", prefix1); prefix1 = ","; } if (bzobj->d_device_paired) { nm_utils_strbuf_append(&buf, &len, "%s d.paired: 1", prefix1); prefix1 = ","; } nm_utils_strbuf_append_str(&buf, &len, " }"); } network_server_is_usable = _bzobjs_network_server_is_usable(bzobj, TRUE); if (bzobj->d_has_network_server_iface || network_server_is_usable != (!c_list_is_empty(&bzobj->x_network_server.lst)) || !c_list_is_empty(&bzobj->x_network_server.lst) || !nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address) || bzobj->x_network_server.device_br || bzobj->x_network_server.r_req_data) { nm_utils_strbuf_append_str(&buf, &len, prefix); prefix = ", "; nm_utils_strbuf_append(&buf, &len, "NetworkServer1 { "); if (!bzobj->d_has_network_server_iface) nm_utils_strbuf_append(&buf, &len, " has-d-iface: 0, "); if (network_server_is_usable != (!c_list_is_empty(&bzobj->x_network_server.lst))) nm_utils_strbuf_append(&buf, &len, "usable: %d, used: %d", !!network_server_is_usable, !network_server_is_usable); else if (network_server_is_usable) nm_utils_strbuf_append(&buf, &len, "used: 1"); else nm_utils_strbuf_append(&buf, &len, "usable: 0"); if (!nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address)) { if (bzobj->x_network_server.adapter_address) nm_utils_strbuf_append(&buf, &len, ", adapter-address: \"%s\"", bzobj->x_network_server.adapter_address); else nm_utils_strbuf_append(&buf, &len, ", adapter-address: "); } if (bzobj->x_network_server.device_br) nm_utils_strbuf_append(&buf, &len, ", bridge-device: 1"); if (bzobj->x_network_server.r_req_data) nm_utils_strbuf_append(&buf, &len, ", register-in-progress: 1"); nm_utils_strbuf_append_str(&buf, &len, " }"); } device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, &create_panu_connection); if (bzobj->d_has_network_iface || bzobj->d_network.interface || bzobj->d_network_connected || create_panu_connection || bzobj->x_device.panu_connection || device_is_usable != bzobj->x_device_is_usable || bzobj->x_device.device_bt || bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE || bzobj->x_device.connect_dun_context || bzobj->x_device.c_req_data || bzobj->x_device_is_connected != bzobj->d_network_connected) { nm_utils_strbuf_append_str(&buf, &len, prefix); prefix = ", "; nm_utils_strbuf_append_str(&buf, &len, "Network1 {"); if (bzobj->d_network.interface) nm_utils_strbuf_append(&buf, &len, " d.interface: \"%s\", ", bzobj->d_network.interface); if (bzobj->d_network_connected) nm_utils_strbuf_append(&buf, &len, " d.connected: %d, ", !!bzobj->d_network_connected); if (!bzobj->d_has_network_iface) nm_utils_strbuf_append(&buf, &len, " has-d-iface: 0, "); if (device_is_usable != bzobj->x_device_is_usable) nm_utils_strbuf_append(&buf, &len, " usable: %d, used: %d", !!device_is_usable, !device_is_usable); else if (device_is_usable) nm_utils_strbuf_append(&buf, &len, " used: 1"); else nm_utils_strbuf_append(&buf, &len, " usable: 0"); if (create_panu_connection) nm_utils_strbuf_append(&buf, &len, ", create-panu-connection: 1"); if (bzobj->x_device.panu_connection) nm_utils_strbuf_append(&buf, &len, ", has-panu-connection: 1"); if (bzobj->x_device.device_bt) nm_utils_strbuf_append(&buf, &len, ", has-device: 1"); if (bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE || bzobj->x_device.connect_dun_context) { nm_utils_strbuf_append( &buf, &len, ", connect: %s%s", nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type, sbuf_cap, sizeof(sbuf_cap)), bzobj->x_device.connect_dun_context ? ",with-dun-context" : ""); } if (bzobj->x_device.c_req_data) nm_utils_strbuf_append(&buf, &len, ", connecting: 1"); if (bzobj->x_device_is_connected != bzobj->d_network_connected) nm_utils_strbuf_append(&buf, &len, ", connected: %d", !!bzobj->x_device_is_connected); nm_utils_strbuf_append_str(&buf, &len, " }"); } if (_bzobjs_is_dead(bzobj)) { nm_utils_strbuf_append_str(&buf, &len, prefix); prefix = ", "; nm_utils_strbuf_append_str(&buf, &len, "dead: 1"); } if (!c_list_is_empty(&bzobj->process_change_lst)) { nm_utils_strbuf_append_str(&buf, &len, prefix); prefix = ", "; nm_utils_strbuf_append(&buf, &len, "change-pending-on-idle: 1"); } if (_bzobjs_adapter_is_usable_for_device(bzobj) != bzobj->was_usable_adapter_for_device_before) { nm_utils_strbuf_append_str(&buf, &len, prefix); prefix = ", "; nm_utils_strbuf_append(&buf, &len, "change-usable-adapter-for-device: 1"); } return buf0; } #define _LOG_bzobj(bzobj, context) \ G_STMT_START \ { \ const BzDBusObj *const _bzobj = (bzobj); \ char _buf[500]; \ \ _LOGT("change %-21s %s : { %s }", \ (context), \ _bzobj->object_path, \ _bzobj_to_string(_bzobj, _buf, sizeof(_buf))); \ } \ G_STMT_END static gboolean _bzobjs_is_dead(const BzDBusObj *bzobj) { return !bzobj->d_has_adapter_iface && !bzobj->d_has_device_iface && !bzobj->d_has_network_iface && !bzobj->d_has_network_server_iface && c_list_is_empty(&bzobj->process_change_lst); } static BzDBusObj * _bzobjs_get(NMBluezManager *self, const char *object_path) { return g_hash_table_lookup(NM_BLUEZ_MANAGER_GET_PRIVATE(self)->bzobjs, &object_path); } static BzDBusObj * _bzobjs_add(NMBluezManager *self, const char *object_path) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); BzDBusObj * bzobj; bzobj = _bz_dbus_obj_new(self, object_path); if (!g_hash_table_add(priv->bzobjs, bzobj)) nm_assert_not_reached(); return bzobj; } static void _bzobjs_del(BzDBusObj *bzobj) { nm_assert(bzobj); nm_assert(bzobj == _bzobjs_get(bzobj->self, bzobj->object_path)); if (!g_hash_table_remove(NM_BLUEZ_MANAGER_GET_PRIVATE(bzobj->self)->bzobjs, bzobj)) nm_assert_not_reached(); } static void _bzobjs_del_if_dead(BzDBusObj *bzobj) { if (_bzobjs_is_dead(bzobj)) _bzobjs_del(bzobj); } static BzDBusObj * _bzobjs_init(NMBluezManager *self, BzDBusObj **inout, const char *object_path) { nm_assert(NM_IS_BLUEZ_MANAGER(self)); nm_assert(object_path); nm_assert(inout); if (!*inout) { *inout = _bzobjs_get(self, object_path); if (!*inout) *inout = _bzobjs_add(self, object_path); } nm_assert(nm_streq((*inout)->object_path, object_path)); nm_assert(*inout == _bzobjs_get(self, object_path)); return *inout; } static gboolean _bzobjs_adapter_is_usable_for_device(const BzDBusObj *bzobj) { return bzobj->d_has_adapter_iface && bzobj->d_adapter.address && bzobj->d_adapter_powered; } static gboolean _bzobjs_device_is_usable(const BzDBusObj *bzobj, BzDBusObj ** out_adapter_bzobj, gboolean * out_create_panu_connection) { NMBluezManager * self; NMBluezManagerPrivate *priv; gboolean usable_dun = FALSE; gboolean usable_nap = FALSE; BzDBusObj * bzobj_adapter; gboolean create_panu_connection = FALSE; if (!bzobj->d_has_device_iface || !NM_FLAGS_ANY((NMBluetoothCapabilities) bzobj->d_device_capabilities, _NM_BT_CAPABILITY_SUPPORTED) || !bzobj->d_device.name || !bzobj->d_device.address || !bzobj->d_device_paired || !bzobj->d_device.adapter) goto out_unusable; self = bzobj->self; priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); if (!priv->settings_registered) goto out_unusable; bzobj_adapter = _bzobjs_get(self, bzobj->d_device.adapter); if (!bzobj_adapter || !_bzobjs_adapter_is_usable_for_device(bzobj_adapter)) goto out_unusable; #if WITH_BLUEZ5_DUN if (NM_FLAGS_HAS(bzobj->d_device_capabilities, NM_BT_CAPABILITY_DUN)) { if (_conn_track_find_head(self, NM_BT_CAPABILITY_DUN, bzobj->d_device.address)) usable_dun = TRUE; } #endif if (NM_FLAGS_HAS(bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP)) { if (!bzobj->d_has_network_iface) usable_nap = FALSE; else if (_conn_track_find_head(self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)) usable_nap = TRUE; else if (bzobj->x_device_panu_connection_allow_create) { /* We didn't yet try to create a connection. Presume we are going to create * it when the time comes... */ usable_nap = TRUE; create_panu_connection = TRUE; } } if (!usable_dun && !usable_nap) { if (bzobj->x_device.device_bt && nm_device_get_state(NM_DEVICE(bzobj->x_device.device_bt)) > NM_DEVICE_STATE_DISCONNECTED) { /* The device is still activated... the absence of a profile does not * render it unusable (yet). But since there is no more profile, the * device is probably about to disconnect. */ } else goto out_unusable; } NM_SET_OUT(out_create_panu_connection, create_panu_connection); NM_SET_OUT(out_adapter_bzobj, bzobj_adapter); return TRUE; out_unusable: NM_SET_OUT(out_create_panu_connection, FALSE); NM_SET_OUT(out_adapter_bzobj, NULL); return FALSE; } static gboolean _bzobjs_device_is_connected(const BzDBusObj *bzobj) { nm_assert(_bzobjs_device_is_usable(bzobj, NULL, NULL)); if (!bzobj->d_has_device_iface || !bzobj->d_device_connected) return FALSE; if (bzobj->d_has_network_iface && bzobj->d_network_connected) return TRUE; if (bzobj->x_device.connect_dun_context) { /* As long as we have a dun-context, we consider it connected. * * We require NMDeviceBt to try to connect to the modem, and if that fails, * it will disconnect. */ return TRUE; } return FALSE; } static gboolean _bzobjs_network_server_is_usable(const BzDBusObj *bzobj, gboolean require_powered) { return bzobj->d_has_network_server_iface && bzobj->d_has_adapter_iface && bzobj->d_adapter.address && (!require_powered || bzobj->d_adapter_powered); } /*****************************************************************************/ static ConnDataHead * _conn_data_head_new(NMBluetoothCapabilities bt_type, const char *bdaddr) { ConnDataHead *cdata_hd; gsize l; nm_assert(NM_IN_SET(bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP)); nm_assert(bdaddr); l = strlen(bdaddr) + 1; cdata_hd = g_malloc(sizeof(ConnDataHead) + l); *cdata_hd = (ConnDataHead){ .bdaddr = cdata_hd->bdaddr_data, .lst_head = C_LIST_INIT(cdata_hd->lst_head), .bt_type = bt_type, }; memcpy(cdata_hd->bdaddr_data, bdaddr, l); nm_assert(cdata_hd->bt_type == bt_type); return cdata_hd; } static guint _conn_data_head_hash(gconstpointer ptr) { const ConnDataHead *cdata_hd = ptr; NMHashState h; nm_hash_init(&h, 520317467u); nm_hash_update_val(&h, (NMBluetoothCapabilities) cdata_hd->bt_type); nm_hash_update_str(&h, cdata_hd->bdaddr); return nm_hash_complete(&h); } static gboolean _conn_data_head_equal(gconstpointer a, gconstpointer b) { const ConnDataHead *cdata_hd_a = a; const ConnDataHead *cdata_hd_b = b; return cdata_hd_a->bt_type == cdata_hd_b->bt_type && nm_streq(cdata_hd_a->bdaddr, cdata_hd_b->bdaddr); } static ConnDataHead * _conn_track_find_head(NMBluezManager *self, NMBluetoothCapabilities bt_type, const char *bdaddr) { ConnDataHead cdata_hd = { .bt_type = bt_type, .bdaddr = bdaddr, }; return g_hash_table_lookup(NM_BLUEZ_MANAGER_GET_PRIVATE(self)->conn_data_heads, &cdata_hd); } static ConnDataElem * _conn_track_find_elem(NMBluezManager *self, NMSettingsConnection *sett_conn) { G_STATIC_ASSERT(G_STRUCT_OFFSET(ConnDataElem, sett_conn) == 0); return g_hash_table_lookup(NM_BLUEZ_MANAGER_GET_PRIVATE(self)->conn_data_elems, &sett_conn); } static gboolean _conn_track_is_relevant_connection(NMConnection * connection, NMBluetoothCapabilities *out_bt_type, const char ** out_bdaddr) { NMSettingBluetooth * s_bt; NMBluetoothCapabilities bt_type; const char * bdaddr; const char * b_type; s_bt = nm_connection_get_setting_bluetooth(connection); if (!s_bt) return FALSE; if (!nm_connection_is_type(connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) return FALSE; bdaddr = nm_setting_bluetooth_get_bdaddr(s_bt); if (!bdaddr) return FALSE; b_type = nm_setting_bluetooth_get_connection_type(s_bt); if (nm_streq(b_type, NM_SETTING_BLUETOOTH_TYPE_DUN)) bt_type = NM_BT_CAPABILITY_DUN; else if (nm_streq(b_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) bt_type = NM_BT_CAPABILITY_NAP; else return FALSE; NM_SET_OUT(out_bt_type, bt_type); NM_SET_OUT(out_bdaddr, bdaddr); return TRUE; } static gboolean _conn_track_is_relevant_sett_conn(NMSettingsConnection * sett_conn, NMBluetoothCapabilities *out_bt_type, const char ** out_bdaddr) { NMConnection *connection; connection = nm_settings_connection_get_connection(sett_conn); if (!connection) return FALSE; return _conn_track_is_relevant_connection(connection, out_bt_type, out_bdaddr); } static gboolean _conn_track_is_relevant_for_sett_conn(NMSettingsConnection * sett_conn, NMBluetoothCapabilities bt_type, const char * bdaddr) { NMBluetoothCapabilities x_bt_type; const char * x_bdaddr; return bdaddr && _conn_track_is_relevant_sett_conn(sett_conn, &x_bt_type, &x_bdaddr) && x_bt_type == bt_type && nm_streq(x_bdaddr, bdaddr); } static void _conn_track_schedule_notify(NMBluezManager * self, NMBluetoothCapabilities bt_type, const char * bdaddr) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); GHashTableIter iter; BzDBusObj * bzobj; g_hash_table_iter_init(&iter, priv->bzobjs); while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj, NULL)) { gboolean device_is_usable; device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, NULL); if (bzobj->x_device_is_usable != device_is_usable) _process_change_idle_schedule(self, bzobj); } } static void _conn_track_update(NMBluezManager * self, NMSettingsConnection *sett_conn, gboolean track, gboolean * out_changed, gboolean * out_changed_usable, ConnDataElem ** out_conn_data_elem) { NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); ConnDataHead * cdata_hd; ConnDataElem * cdata_el; ConnDataElem * cdata_el_remove = NULL; NMBluetoothCapabilities bt_type; const char * bdaddr; gboolean changed = FALSE; gboolean changed_usable = FALSE; char sbuf_cap[100]; nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn)); cdata_el = _conn_track_find_elem(self, sett_conn); if (track) track = _conn_track_is_relevant_sett_conn(sett_conn, &bt_type, &bdaddr); if (!track) { cdata_el_remove = g_steal_pointer(&cdata_el); goto out_remove; } if (cdata_el) { cdata_hd = cdata_el->cdata_hd; if (cdata_hd->bt_type != bt_type || !nm_streq(cdata_hd->bdaddr, bdaddr)) cdata_el_remove = g_steal_pointer(&cdata_el); } if (!cdata_el) { _LOGT("connection: track for %s, %s: %s (%s)", nm_bluetooth_capability_to_string(bt_type, sbuf_cap, sizeof(sbuf_cap)), bdaddr, nm_settings_connection_get_uuid(sett_conn), nm_settings_connection_get_id(sett_conn)); changed = TRUE; cdata_hd = _conn_track_find_head(self, bt_type, bdaddr); if (!cdata_hd) { changed_usable = TRUE; cdata_hd = _conn_data_head_new(bt_type, bdaddr); if (!g_hash_table_add(priv->conn_data_heads, cdata_hd)) nm_assert_not_reached(); _conn_track_schedule_notify(self, bt_type, bdaddr); } cdata_el = g_slice_new(ConnDataElem); cdata_el->sett_conn = sett_conn; cdata_el->cdata_hd = cdata_hd; c_list_link_tail(&cdata_hd->lst_head, &cdata_el->lst); if (!g_hash_table_add(priv->conn_data_elems, cdata_el)) nm_assert_not_reached(); } out_remove: if (cdata_el_remove) { GHashTableIter iter; BzDBusObj * bzobj; _LOGT("connection: untrack for %s, %s: %s (%s)", nm_bluetooth_capability_to_string(cdata_el_remove->cdata_hd->bt_type, sbuf_cap, sizeof(sbuf_cap)), cdata_el_remove->cdata_hd->bdaddr, nm_settings_connection_get_uuid(sett_conn), nm_settings_connection_get_id(sett_conn)); g_hash_table_iter_init(&iter, priv->bzobjs); while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj, NULL)) { if (bzobj->x_device.panu_connection == sett_conn) bzobj->x_device.panu_connection = NULL; } changed = TRUE; cdata_hd = cdata_el_remove->cdata_hd; c_list_unlink_stale(&cdata_el_remove->lst); if (!g_hash_table_remove(priv->conn_data_elems, cdata_el_remove)) nm_assert_not_reached(); if (c_list_is_empty(&cdata_hd->lst_head)) { changed_usable = TRUE; _conn_track_schedule_notify(self, cdata_hd->bt_type, cdata_hd->bdaddr); if (!g_hash_table_remove(priv->conn_data_heads, cdata_hd)) nm_assert_not_reached(); } } NM_SET_OUT(out_changed, changed); NM_SET_OUT(out_changed_usable, changed_usable); NM_SET_OUT(out_conn_data_elem, cdata_el); } /*****************************************************************************/ static void cp_connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, NMBluezManager *self) { _conn_track_update(self, sett_conn, TRUE, NULL, NULL, NULL); } static void cp_connection_updated(NMSettings * settings, NMSettingsConnection *sett_conn, guint update_reason_u, NMBluezManager * self) { _conn_track_update(self, sett_conn, TRUE, NULL, NULL, NULL); } static void cp_connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, NMBluezManager *self) { _conn_track_update(self, sett_conn, FALSE, NULL, NULL, NULL); } /*****************************************************************************/ static NMBluezManager * _network_server_get_bluez_manager(const NMBtVTableNetworkServer *vtable_network_server) { NMBluezManager *self; self = (NMBluezManager *) (((char *) vtable_network_server) - G_STRUCT_OFFSET(NMBluezManager, _priv.vtable_network_server)); g_return_val_if_fail(NM_IS_BLUEZ_MANAGER(self), NULL); return self; } static BzDBusObj * _network_server_find_has_device(NMBluezManagerPrivate *priv, NMDevice *device) { BzDBusObj *bzobj; c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) { if (bzobj->x_network_server.device_br == device) return bzobj; } return NULL; } static BzDBusObj * _network_server_find_available(NMBluezManagerPrivate *priv, const char * addr, NMDevice * device_accept_busy) { BzDBusObj *bzobj; c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) { if (bzobj->x_network_server.device_br) { if (bzobj->x_network_server.device_br != device_accept_busy) continue; } if (addr && !nm_streq(addr, bzobj->d_adapter.address)) continue; nm_assert(!bzobj->x_network_server.r_req_data); return bzobj; } return NULL; } static gboolean _network_server_vt_is_available(const NMBtVTableNetworkServer *vtable, const char * addr, NMDevice * device_accept_busy) { NMBluezManager * self = _network_server_get_bluez_manager(vtable); NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); return !!_network_server_find_available(priv, addr, device_accept_busy); } static void _network_server_register_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; BzDBusObj * bzobj; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); if (!ret && nm_utils_error_is_cancelled(error)) return; bzobj = user_data; if (!ret) { _LOGT("NAP: [%s]: registering failed: %s", bzobj->object_path, error->message); } else _LOGT("NAP: [%s]: registration successful", bzobj->object_path); g_clear_object(&bzobj->x_network_server.r_req_data->int_cancellable); _network_server_register_req_data_complete(g_steal_pointer(&bzobj->x_network_server.r_req_data), error); } static void _network_server_register_cancelled_cb(GCancellable *cancellable, BzDBusObj *bzobj) { _network_server_unregister_bridge(bzobj->self, bzobj, "registration cancelled"); } static gboolean _network_server_vt_register_bridge(const NMBtVTableNetworkServer *vtable, const char * addr, NMDevice * device, GCancellable * cancellable, NMBtVTableRegisterCallback callback, gpointer callback_user_data, GError ** error) { NMBluezManager * self = _network_server_get_bluez_manager(vtable); NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); NetworkServerRegisterReqData *r_req_data; BzDBusObj * bzobj; const char * ifname; g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); g_return_val_if_fail(G_IS_CANCELLABLE(cancellable), FALSE); nm_assert(!g_cancellable_is_cancelled(cancellable)); nm_assert(!_network_server_find_has_device(priv, device)); ifname = nm_device_get_iface(device); g_return_val_if_fail(ifname, FALSE); g_return_val_if_fail(ifname, FALSE); bzobj = _network_server_find_available(priv, addr, NULL); if (!bzobj) { /* The device checked that a network server is available, before * starting the activation, but for some reason it no longer is. * Indicate that the activation should not proceed. */ if (addr) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "adapter %s is not available for %s", addr, ifname); } else { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "no adapter available for %s", ifname); } return FALSE; } _LOGD("NAP: [%s]: registering \"%s\" on adapter %s", bzobj->object_path, ifname, bzobj->d_adapter.address); r_req_data = g_slice_new(NetworkServerRegisterReqData); *r_req_data = (NetworkServerRegisterReqData){ .int_cancellable = g_cancellable_new(), .ext_cancellable = g_object_ref(cancellable), .callback = callback, .callback_user_data = callback_user_data, .ext_cancelled_id = g_signal_connect(cancellable, "cancelled", G_CALLBACK(_network_server_register_cancelled_cb), bzobj), }; bzobj->x_network_server.device_br = g_object_ref(device); bzobj->x_network_server.r_req_data = r_req_data; g_dbus_connection_call(priv->dbus_connection, priv->name_owner, bzobj->object_path, NM_BLUEZ5_NETWORK_SERVER_INTERFACE, "Register", g_variant_new("(ss)", BLUETOOTH_CONNECT_NAP, ifname), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, bzobj->x_network_server.r_req_data->int_cancellable, _network_server_register_cb, bzobj); return TRUE; } static void _network_server_unregister_bridge_complete_on_idle_cb(gpointer user_data, GCancellable *cancellable) { gs_free_error GError * error = NULL; gs_free char * reason = NULL; NetworkServerRegisterReqData *r_req_data; nm_utils_user_data_unpack(user_data, &r_req_data, &reason); nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "registration was aborted due to %s", reason); _network_server_register_req_data_complete(r_req_data, error); } static void _network_server_unregister_bridge(NMBluezManager *self, BzDBusObj *bzobj, const char *reason) { NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); _nm_unused gs_unref_object NMDevice *device = NULL; NetworkServerRegisterReqData * r_req_data; nm_assert(NM_IS_DEVICE(bzobj->x_network_server.device_br)); _LOGD("NAP: [%s]: unregistering \"%s\" (%s)", bzobj->object_path, nm_device_get_iface(bzobj->x_network_server.device_br), reason); device = g_steal_pointer(&bzobj->x_network_server.device_br); r_req_data = g_steal_pointer(&bzobj->x_network_server.r_req_data); if (priv->name_owner) { gs_unref_object GCancellable *cancellable = NULL; cancellable = g_cancellable_new(); nm_shutdown_wait_obj_register_cancellable_full( cancellable, g_strdup_printf("bt-unregister-nap[%s]", bzobj->object_path), TRUE); g_dbus_connection_call(priv->dbus_connection, priv->name_owner, bzobj->object_path, NM_BLUEZ5_NETWORK_SERVER_INTERFACE, "Unregister", g_variant_new("(s)", BLUETOOTH_CONNECT_NAP), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, cancellable, _dbus_call_complete_cb_nop, NULL); } if (r_req_data) { nm_clear_g_cancellable(&r_req_data->int_cancellable); nm_utils_invoke_on_idle(r_req_data->ext_cancellable, _network_server_unregister_bridge_complete_on_idle_cb, nm_utils_user_data_pack(r_req_data, g_strdup(reason))); } _nm_device_bridge_notify_unregister_bt_nap(device, reason); } static gboolean _network_server_vt_unregister_bridge(const NMBtVTableNetworkServer *vtable, NMDevice *device) { NMBluezManager * self = _network_server_get_bluez_manager(vtable); NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); BzDBusObj * bzobj; g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); bzobj = _network_server_find_has_device(priv, device); if (bzobj) _network_server_unregister_bridge(self, bzobj, "disconnecting"); return TRUE; } static void _network_server_process_change(BzDBusObj *bzobj, gboolean *out_emit_device_availability_changed) { NMBluezManager * self = bzobj->self; NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); gboolean network_server_is_usable; gboolean emit_device_availability_changed = FALSE; network_server_is_usable = _bzobjs_network_server_is_usable(bzobj, TRUE); if (!network_server_is_usable) { if (!c_list_is_empty(&bzobj->x_network_server.lst)) { emit_device_availability_changed = TRUE; c_list_unlink(&bzobj->x_network_server.lst); } nm_clear_g_free(&bzobj->x_network_server.adapter_address); if (bzobj->x_network_server.device_br) { _network_server_unregister_bridge(self, bzobj, _bzobjs_network_server_is_usable(bzobj, FALSE) ? "adapter disabled" : "adapter disappeared"); } } else { if (!nm_streq0(bzobj->x_network_server.adapter_address, bzobj->d_adapter.address)) { emit_device_availability_changed = TRUE; g_free(bzobj->x_network_server.adapter_address); bzobj->x_network_server.adapter_address = g_strdup(bzobj->d_adapter.address); } if (c_list_is_empty(&bzobj->x_network_server.lst)) { emit_device_availability_changed = TRUE; c_list_link_tail(&priv->network_server_lst_head, &bzobj->x_network_server.lst); } } if (emit_device_availability_changed) NM_SET_OUT(out_emit_device_availability_changed, TRUE); } /*****************************************************************************/ static void _conn_create_panu_connection(NMBluezManager *self, BzDBusObj *bzobj) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); gs_unref_object NMConnection *connection = NULL; NMSettingsConnection * added; NMSetting * setting; gs_free char * id = NULL; char uuid[37]; gs_free_error GError *error = NULL; nm_utils_uuid_generate_buf(uuid); id = g_strdup_printf(_("%s Network"), bzobj->d_device.name); connection = nm_simple_connection_new(); setting = nm_setting_connection_new(); g_object_set(setting, NM_SETTING_CONNECTION_ID, id, NM_SETTING_CONNECTION_UUID, uuid, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME, NULL); nm_connection_add_setting(connection, setting); setting = nm_setting_bluetooth_new(); g_object_set(setting, NM_SETTING_BLUETOOTH_BDADDR, bzobj->d_device.address, NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU, NULL); nm_connection_add_setting(connection, setting); if (!nm_connection_normalize(connection, NULL, NULL, &error)) { _LOGE("connection: couldn't generate a connection for NAP device: %s", error->message); g_return_if_reached(); } nm_assert(_conn_track_is_relevant_connection(connection, NULL, NULL)); _LOGT("connection: create in-memory PANU connection %s (%s) for device \"%s\" (%s)", uuid, id, bzobj->d_device.name, bzobj->d_device.address); nm_settings_add_connection(priv->settings, connection, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, NM_SETTINGS_CONNECTION_ADD_REASON_NONE, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, &added, &error); if (!added) { _LOGW("connection: couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s", id, uuid, error->message); return; } if (!_conn_track_is_relevant_for_sett_conn(added, NM_BT_CAPABILITY_NAP, bzobj->d_device.address) || !_conn_track_find_elem(self, added) || bzobj->x_device.panu_connection) { _LOGE("connection: something went wrong creating PANU connection %s (%s) for device '%s'", uuid, id, bzobj->d_device.address); g_return_if_reached(); } bzobj->x_device.panu_connection = added; } /*****************************************************************************/ static void _device_state_changed_cb(NMDevice *device, guint new_state_u, guint old_state_u, guint reason_u, gpointer user_data) { BzDBusObj *bzobj = user_data; if (!_bzobjs_device_is_usable(bzobj, NULL, NULL)) { /* the device got unusable? Need to revisit it... */ _process_change_idle_schedule(bzobj->self, bzobj); } } static void _device_process_change(BzDBusObj *bzobj) { NMBluezManager *self = bzobj->self; gs_unref_object NMDeviceBt *device_added = NULL; gs_unref_object NMDeviceBt *device_deleted = NULL; gboolean device_is_usable; gboolean create_panu_connection = FALSE; device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, &create_panu_connection); if (create_panu_connection) { bzobj->x_device_panu_connection_allow_create = FALSE; _conn_create_panu_connection(self, bzobj); device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, NULL); } else { if (device_is_usable && bzobj->x_device_panu_connection_allow_create && NM_FLAGS_HAS(bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP) && _conn_track_find_head(self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)) { /* We have a useable device and also a panu-connection. We block future attemps * to generate a connection. */ bzobj->x_device_panu_connection_allow_create = FALSE; } if (bzobj->x_device.panu_connection) { if (!NM_FLAGS_HAS(nm_settings_connection_get_flags(bzobj->x_device.panu_connection), NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { /* the connection that we generated earlier still exists, but it's not longer the same * as it was when we created it. Forget about it, so that we don't delete the profile later... */ bzobj->x_device.panu_connection = NULL; } else { if (!device_is_usable || !_conn_track_is_relevant_for_sett_conn(bzobj->x_device.panu_connection, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)) { _LOGT("connection: delete in-memory PANU connection %s (%s) as device %s", nm_settings_connection_get_uuid(bzobj->x_device.panu_connection), nm_settings_connection_get_id(bzobj->x_device.panu_connection), !device_is_usable ? "is now unusable" : "no longer matches"); bzobj->x_device_panu_connection_allow_create = TRUE; nm_settings_connection_delete(g_steal_pointer(&bzobj->x_device.panu_connection), FALSE); } } } } bzobj->x_device_is_connected = device_is_usable && _bzobjs_device_is_connected(bzobj); bzobj->x_device_is_usable = device_is_usable; if (bzobj->x_device.device_bt) { const char *device_to_delete_msg; if (!device_is_usable) device_to_delete_msg = "device became unusable"; else if (!_nm_device_bt_for_same_device(bzobj->x_device.device_bt, bzobj->object_path, bzobj->d_device.address, NULL, bzobj->d_device_capabilities)) device_to_delete_msg = "device is no longer compatible"; else device_to_delete_msg = NULL; if (device_to_delete_msg) { nm_clear_g_signal_handler(bzobj->x_device.device_bt, &bzobj->x_device.device_bt_signal_id); device_deleted = g_steal_pointer(&bzobj->x_device.device_bt); _LOGD("[%s]: drop device because %s", bzobj->object_path, device_to_delete_msg); _connect_disconnect(self, bzobj, device_to_delete_msg); } } if (device_is_usable) { if (!bzobj->x_device.device_bt) { bzobj->x_device.device_bt = nm_device_bt_new(self, bzobj->object_path, bzobj->d_device.address, bzobj->d_device.name, bzobj->d_device_capabilities); device_added = g_object_ref(bzobj->x_device.device_bt); bzobj->x_device.device_bt_signal_id = g_signal_connect(device_added, NM_DEVICE_STATE_CHANGED, G_CALLBACK(_device_state_changed_cb), bzobj); } else _nm_device_bt_notify_set_name(bzobj->x_device.device_bt, bzobj->d_device.name); _nm_device_bt_notify_set_connected(bzobj->x_device.device_bt, bzobj->x_device_is_connected); } if (bzobj->x_device.c_req_data && !bzobj->x_device.c_req_data->int_cancellable && bzobj->x_device_is_connected) { gs_free char *device_name = g_steal_pointer(&bzobj->x_device.c_req_data->device_name); _device_connect_req_data_complete(g_steal_pointer(&bzobj->x_device.c_req_data), self, device_name, NULL); } if (device_added) g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, device_added); if (device_deleted) _nm_device_bt_notify_removed(device_deleted); } /*****************************************************************************/ static void _process_change_idle_all(NMBluezManager *self, gboolean *out_emit_device_availability_changed) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); BzDBusObj * bzobj; while ( (bzobj = c_list_first_entry(&priv->process_change_lst_head, BzDBusObj, process_change_lst))) { c_list_unlink(&bzobj->process_change_lst); _LOG_bzobj(bzobj, "before-processing"); _device_process_change(bzobj); _network_server_process_change(bzobj, out_emit_device_availability_changed); _LOG_bzobj(bzobj, "after-processing"); _bzobjs_del_if_dead(bzobj); } nm_clear_g_source(&priv->process_change_idle_id); } static gboolean _process_change_idle_cb(gpointer user_data) { NMBluezManager * self = user_data; NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); gboolean emit_device_availability_changed = FALSE; _process_change_idle_all(self, &emit_device_availability_changed); if (emit_device_availability_changed) nm_manager_notify_device_availability_maybe_changed(priv->manager); return G_SOURCE_CONTINUE; } static void _process_change_idle_schedule(NMBluezManager *self, BzDBusObj *bzobj) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); nm_c_list_move_tail(&priv->process_change_lst_head, &bzobj->process_change_lst); if (priv->process_change_idle_id == 0) priv->process_change_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE + 1, _process_change_idle_cb, self, NULL); } static void _dbus_process_changes(NMBluezManager *self, BzDBusObj *bzobj, const char *log_reason) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); gboolean network_server_is_usable; gboolean adapter_is_usable_for_device; gboolean device_is_usable; gboolean changes = FALSE; gboolean recheck_devices_for_adapter = FALSE; nm_assert(bzobj); _LOG_bzobj(bzobj, log_reason); device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, NULL); if (bzobj->x_device_is_usable != device_is_usable) changes = TRUE; else if (bzobj->x_device.device_bt) { if (!device_is_usable) changes = TRUE; else { if (bzobj->x_device_is_connected != _bzobjs_device_is_connected(bzobj) || !_nm_device_bt_for_same_device(bzobj->x_device.device_bt, bzobj->object_path, bzobj->d_device.address, bzobj->d_device.name, bzobj->d_device_capabilities)) changes = TRUE; } } adapter_is_usable_for_device = _bzobjs_adapter_is_usable_for_device(bzobj); if (adapter_is_usable_for_device != bzobj->was_usable_adapter_for_device_before) { /* this function does not modify bzobj in any other cases except here. * Usually changes are processed delayed, in the idle handler. * * But the bzobj->was_usable_adapter_for_device_before only exists to know whether * we need to re-check device availability. It is correct to set the flag * here, right before we checked. */ bzobj->was_usable_adapter_for_device_before = adapter_is_usable_for_device; recheck_devices_for_adapter = TRUE; changes = TRUE; } if (!changes) { network_server_is_usable = _bzobjs_network_server_is_usable(bzobj, TRUE); if (network_server_is_usable != (!c_list_is_empty(&bzobj->x_network_server.lst))) changes = TRUE; else if (bzobj->x_network_server.device_br && !network_server_is_usable) changes = TRUE; else if (!nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address)) changes = TRUE; } if (changes) _process_change_idle_schedule(self, bzobj); if (recheck_devices_for_adapter) { GHashTableIter iter; BzDBusObj * bzobj2; /* we got a change to the availability of an adapter. We might need to recheck * all devices that use this adapter... */ g_hash_table_iter_init(&iter, priv->bzobjs); while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj2, NULL)) { if (bzobj2 == bzobj) continue; if (!nm_streq0(bzobj2->d_device.adapter, bzobj->object_path)) continue; if (c_list_is_empty(&bzobj2->process_change_lst)) _dbus_process_changes(self, bzobj2, "adapter-changed"); else nm_c_list_move_tail(&priv->process_change_lst_head, &bzobj2->process_change_lst); } } _bzobjs_del_if_dead(bzobj); } /*****************************************************************************/ #define ALL_RELEVANT_INTERFACE_NAMES \ NM_MAKE_STRV(NM_BLUEZ5_ADAPTER_INTERFACE, \ NM_BLUEZ5_DEVICE_INTERFACE, \ NM_BLUEZ5_NETWORK_INTERFACE, \ NM_BLUEZ5_NETWORK_SERVER_INTERFACE) static gboolean _dbus_handle_properties_changed(NMBluezManager * self, const char * object_path, const char * interface_name, GVariant * changed_properties, const char *const *invalidated_properties, BzDBusObj ** inout_bzobj) { BzDBusObj * bzobj = NULL; gboolean changed = FALSE; const char * property_name; GVariant * property_value; GVariantIter iter_prop; gsize i; if (!invalidated_properties) invalidated_properties = NM_PTRARRAY_EMPTY(const char *); nm_assert(g_variant_is_of_type(changed_properties, G_VARIANT_TYPE("a{sv}"))); if (inout_bzobj) { bzobj = *inout_bzobj; nm_assert(!bzobj || nm_streq(object_path, bzobj->object_path)); } if (changed_properties) g_variant_iter_init(&iter_prop, changed_properties); if (nm_streq(interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) { _bzobjs_init(self, &bzobj, object_path); if (!bzobj->d_has_adapter_iface) { changed = TRUE; bzobj->d_has_adapter_iface = TRUE; } while (changed_properties && g_variant_iter_next(&iter_prop, "{&sv}", &property_name, &property_value)) { _nm_unused gs_unref_variant GVariant *property_value_free = property_value; if (nm_streq(property_name, "Address")) { gs_free char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) ? nm_utils_hwaddr_canonical(g_variant_get_string(property_value, NULL), ETH_ALEN) : NULL; if (!nm_streq0(bzobj->d_adapter.address, s)) { changed = TRUE; nm_clear_g_free(&bzobj->d_adapter.address); bzobj->d_adapter.address = g_steal_pointer(&s); } continue; } if (nm_streq(property_name, "Powered")) { bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) && g_variant_get_boolean(property_value); if (bzobj->d_adapter_powered != v) { changed = TRUE; bzobj->d_adapter_powered = v; } continue; } } for (i = 0; (property_name = invalidated_properties[i]); i++) { if (nm_streq(property_name, "Address")) { if (bzobj->d_adapter.address) { changed = TRUE; nm_clear_g_free(&bzobj->d_adapter.address); } continue; } if (nm_streq(property_name, "Powered")) { if (bzobj->d_adapter_powered) { changed = TRUE; bzobj->d_adapter_powered = FALSE; } continue; } } } else if (nm_streq(interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) { _bzobjs_init(self, &bzobj, object_path); if (!bzobj->d_has_device_iface) { changed = TRUE; bzobj->d_has_device_iface = TRUE; } while (changed_properties && g_variant_iter_next(&iter_prop, "{&sv}", &property_name, &property_value)) { _nm_unused gs_unref_variant GVariant *property_value_free = property_value; if (nm_streq(property_name, "Address")) { gs_free char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) ? nm_utils_hwaddr_canonical(g_variant_get_string(property_value, NULL), ETH_ALEN) : NULL; if (!nm_streq0(bzobj->d_device.address, s)) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.address); bzobj->d_device.address = g_steal_pointer(&s); } continue; } if (nm_streq(property_name, "Name")) { const char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) ? g_variant_get_string(property_value, NULL) : NULL; if (!nm_streq0(bzobj->d_device.name, s)) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.name); bzobj->d_device.name = g_strdup(s); } continue; } if (nm_streq(property_name, "Adapter")) { const char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_OBJECT_PATH) ? g_variant_get_string(property_value, NULL) : NULL; if (!nm_streq0(bzobj->d_device.adapter, s)) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.adapter); bzobj->d_device.adapter = g_strdup(s); } continue; } if (nm_streq(property_name, "UUIDs")) { NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE; if (g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING_ARRAY)) { gs_free const char **s = g_variant_get_strv(property_value, NULL); capabilities = convert_uuids_to_capabilities(s); } if (bzobj->d_device_capabilities != capabilities) { changed = TRUE; bzobj->d_device_capabilities = capabilities; nm_assert(bzobj->d_device_capabilities == capabilities); } continue; } if (nm_streq(property_name, "Connected")) { bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) && g_variant_get_boolean(property_value); if (bzobj->d_device_connected != v) { changed = TRUE; bzobj->d_device_connected = v; } continue; } if (nm_streq(property_name, "Paired")) { bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) && g_variant_get_boolean(property_value); if (bzobj->d_device_paired != v) { changed = TRUE; bzobj->d_device_paired = v; } continue; } } for (i = 0; (property_name = invalidated_properties[i]); i++) { if (nm_streq(property_name, "Address")) { if (bzobj->d_device.address) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.address); } continue; } if (nm_streq(property_name, "Name")) { if (bzobj->d_device.name) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.name); } continue; } if (nm_streq(property_name, "Adapter")) { if (bzobj->d_device.adapter) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.adapter); } continue; } if (nm_streq(property_name, "UUIDs")) { if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { changed = TRUE; bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE; } continue; } if (nm_streq(property_name, "Connected")) { if (bzobj->d_device_connected) { changed = TRUE; bzobj->d_device_connected = FALSE; } continue; } if (nm_streq(property_name, "Paired")) { if (bzobj->d_device_paired) { changed = TRUE; bzobj->d_device_paired = FALSE; } continue; } } } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) { _bzobjs_init(self, &bzobj, object_path); if (!bzobj->d_has_network_iface) { changed = TRUE; bzobj->d_has_network_iface = TRUE; } while (changed_properties && g_variant_iter_next(&iter_prop, "{&sv}", &property_name, &property_value)) { _nm_unused gs_unref_variant GVariant *property_value_free = property_value; if (nm_streq(property_name, "Interface")) { const char *s = g_variant_is_of_type(property_value, G_VARIANT_TYPE_STRING) ? g_variant_get_string(property_value, NULL) : NULL; if (!nm_streq0(bzobj->d_network.interface, s)) { changed = TRUE; nm_clear_g_free(&bzobj->d_network.interface); bzobj->d_network.interface = g_strdup(s); } continue; } if (nm_streq(property_name, "Connected")) { bool v = g_variant_is_of_type(property_value, G_VARIANT_TYPE_BOOLEAN) && g_variant_get_boolean(property_value); if (bzobj->d_network_connected != v) { changed = TRUE; bzobj->d_network_connected = v; } continue; } } for (i = 0; (property_name = invalidated_properties[i]); i++) { if (nm_streq(property_name, "Interface")) { if (bzobj->d_network.interface) { changed = TRUE; nm_clear_g_free(&bzobj->d_network.interface); } continue; } if (nm_streq(property_name, "Connected")) { if (bzobj->d_network_connected) { changed = TRUE; bzobj->d_network_connected = FALSE; } continue; } } } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) { _bzobjs_init(self, &bzobj, object_path); if (!bzobj->d_has_network_server_iface) { changed = TRUE; bzobj->d_has_network_server_iface = TRUE; } } nm_assert(!changed || bzobj); if (inout_bzobj) *inout_bzobj = bzobj; return changed; } static void _dbus_handle_interface_added(NMBluezManager *self, const char * object_path, GVariant * ifaces, gboolean initial_get_managed_objects) { BzDBusObj * bzobj = NULL; gboolean changed = FALSE; const char * interface_name; GVariant * changed_properties; GVariantIter iter_ifaces; nm_assert(g_variant_is_of_type(ifaces, G_VARIANT_TYPE("a{sa{sv}}"))); g_variant_iter_init(&iter_ifaces, ifaces); while (g_variant_iter_next(&iter_ifaces, "{&s@a{sv}}", &interface_name, &changed_properties)) { _nm_unused gs_unref_variant GVariant *changed_properties_free = changed_properties; if (_dbus_handle_properties_changed(self, object_path, interface_name, changed_properties, NULL, &bzobj)) changed = TRUE; } if (changed) { _dbus_process_changes(self, bzobj, initial_get_managed_objects ? "dbus-init" : "dbus-iface-added"); } } static gboolean _dbus_handle_interface_removed(NMBluezManager * self, const char * object_path, BzDBusObj ** inout_bzobj, const char *const *removed_interfaces) { gboolean changed = FALSE; BzDBusObj *bzobj; gsize i; if (inout_bzobj && *inout_bzobj) { bzobj = *inout_bzobj; nm_assert(bzobj == _bzobjs_get(self, object_path)); } else { bzobj = _bzobjs_get(self, object_path); if (!bzobj) return FALSE; NM_SET_OUT(inout_bzobj, bzobj); } for (i = 0; removed_interfaces[i]; i++) { const char *interface_name = removed_interfaces[i]; if (nm_streq(interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) { if (bzobj->d_has_adapter_iface) { changed = TRUE; bzobj->d_has_adapter_iface = FALSE; } if (bzobj->d_adapter.address) { changed = TRUE; nm_clear_g_free(&bzobj->d_adapter.address); } if (bzobj->d_adapter_powered) { changed = TRUE; bzobj->d_adapter_powered = FALSE; } } else if (nm_streq(interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) { if (bzobj->d_has_device_iface) { changed = TRUE; bzobj->d_has_device_iface = FALSE; } if (bzobj->d_device.address) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.address); } if (bzobj->d_device.name) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.name); } if (bzobj->d_device.adapter) { changed = TRUE; nm_clear_g_free(&bzobj->d_device.adapter); } if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) { changed = TRUE; bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE; } if (bzobj->d_device_connected) { changed = TRUE; bzobj->d_device_connected = FALSE; } if (bzobj->d_device_paired) { changed = TRUE; bzobj->d_device_paired = FALSE; } } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) { if (bzobj->d_has_network_iface) { changed = TRUE; bzobj->d_has_network_iface = FALSE; } if (bzobj->d_network.interface) { changed = TRUE; nm_clear_g_free(&bzobj->d_network.interface); } if (bzobj->d_network_connected) { changed = TRUE; bzobj->d_network_connected = FALSE; } } else if (nm_streq(interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) { if (bzobj->d_has_network_server_iface) { changed = TRUE; bzobj->d_has_network_server_iface = FALSE; } } } return changed; } static void _dbus_managed_objects_changed_cb(GDBusConnection *connection, const char * sender_name, const char * arg_object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMBluezManager * self = user_data; NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); BzDBusObj * bzobj = NULL; gboolean changed; nm_assert(nm_streq0(interface_name, DBUS_INTERFACE_OBJECT_MANAGER)); if (priv->get_managed_objects_cancellable) { /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } if (nm_streq(signal_name, "InterfacesAdded")) { gs_unref_variant GVariant *interfaces_and_properties = NULL; const char * object_path; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oa{sa{sv}})"))) return; g_variant_get(parameters, "(&o@a{sa{sv}})", &object_path, &interfaces_and_properties); _dbus_handle_interface_added(self, object_path, interfaces_and_properties, FALSE); return; } if (nm_streq(signal_name, "InterfacesRemoved")) { gs_free const char **interfaces = NULL; const char * object_path; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oas)"))) return; g_variant_get(parameters, "(&o^a&s)", &object_path, &interfaces); changed = _dbus_handle_interface_removed(self, object_path, &bzobj, interfaces); if (changed) _dbus_process_changes(self, bzobj, "dbus-iface-removed"); return; } } static void _dbus_properties_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * signal_interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMBluezManager * self = user_data; NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); const char * interface_name; gs_unref_variant GVariant *changed_properties = NULL; gs_free const char ** invalidated_properties = NULL; BzDBusObj * bzobj = NULL; if (priv->get_managed_objects_cancellable) { /* we still wait for the initial GetManagedObjects(). Ignore the event. */ return; } if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) return; g_variant_get(parameters, "(&s@a{sv}^a&s)", &interface_name, &changed_properties, &invalidated_properties); if (_dbus_handle_properties_changed(self, object_path, interface_name, changed_properties, invalidated_properties, &bzobj)) _dbus_process_changes(self, bzobj, "dbus-property-changed"); } static void _dbus_get_managed_objects_cb(GVariant *result, GError *error, gpointer user_data) { NMBluezManager * self; NMBluezManagerPrivate *priv; GVariantIter iter; const char * object_path; GVariant * ifaces; if (!result && nm_utils_error_is_cancelled(error)) return; self = user_data; priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); g_clear_object(&priv->get_managed_objects_cancellable); if (!result) { _LOGT("initial GetManagedObjects() call failed: %s", error->message); _cleanup_for_name_owner(self); return; } _LOGT("initial GetManagedObjects call succeeded"); g_variant_iter_init(&iter, result); while (g_variant_iter_next(&iter, "{&o@a{sa{sv}}}", &object_path, &ifaces)) { _nm_unused gs_unref_variant GVariant *ifaces_free = ifaces; _dbus_handle_interface_added(self, object_path, ifaces, TRUE); } } /*****************************************************************************/ static void _cleanup_for_name_owner(NMBluezManager *self) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); gboolean emit_device_availability_changed = FALSE; GHashTableIter iter; BzDBusObj * bzobj; gboolean first = TRUE; nm_clear_g_cancellable(&priv->get_managed_objects_cancellable); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->managed_objects_changed_id); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->properties_changed_id); nm_clear_g_free(&priv->name_owner); g_hash_table_iter_init(&iter, priv->bzobjs); while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj, NULL)) { if (first) { first = FALSE; _LOGT("drop all objects form D-Bus cache..."); } _dbus_handle_interface_removed(self, bzobj->object_path, &bzobj, ALL_RELEVANT_INTERFACE_NAMES); nm_c_list_move_tail(&priv->process_change_lst_head, &bzobj->process_change_lst); } _process_change_idle_all(self, &emit_device_availability_changed); nm_assert(g_hash_table_size(priv->bzobjs) == 0); if (emit_device_availability_changed) nm_manager_notify_device_availability_maybe_changed(priv->manager); } static void name_owner_changed(NMBluezManager *self, const char *owner) { _nm_unused gs_unref_object NMBluezManager *self_keep_alive = g_object_ref(self); NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); owner = nm_str_not_empty(owner); if (!owner) _LOGT("D-Bus name for bluez has no owner"); else _LOGT("D-Bus name for bluez has owner %s", owner); nm_clear_g_cancellable(&priv->name_owner_get_cancellable); if (nm_streq0(priv->name_owner, owner)) return; _cleanup_for_name_owner(self); if (!owner) return; priv->name_owner = g_strdup(owner); priv->get_managed_objects_cancellable = g_cancellable_new(); priv->managed_objects_changed_id = nm_dbus_connection_signal_subscribe_object_manager(priv->dbus_connection, priv->name_owner, NM_BLUEZ_MANAGER_PATH, NULL, _dbus_managed_objects_changed_cb, self, NULL); priv->properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, priv->name_owner, NULL, NULL, _dbus_properties_changed_cb, self, NULL); nm_dbus_connection_call_get_managed_objects(priv->dbus_connection, priv->name_owner, NM_BLUEZ_MANAGER_PATH, G_DBUS_CALL_FLAGS_NO_AUTO_START, 20000, priv->get_managed_objects_cancellable, _dbus_get_managed_objects_cb, self); } static void name_owner_changed_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMBluezManager *self = user_data; const char * new_owner; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) return; g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); name_owner_changed(self, new_owner); } static void name_owner_get_cb(const char *name_owner, GError *error, gpointer user_data) { if (name_owner || !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) name_owner_changed(user_data, name_owner); } /*****************************************************************************/ static void _cleanup_all(NMBluezManager *self) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); priv->settings_registered = FALSE; g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_added, self); g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_updated, self); g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_removed, self); g_hash_table_remove_all(priv->conn_data_elems); g_hash_table_remove_all(priv->conn_data_heads); _cleanup_for_name_owner(self); nm_clear_g_cancellable(&priv->name_owner_get_cancellable); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); } static void start(NMDeviceFactory *factory) { NMBluezManager * self; NMBluezManagerPrivate * priv; NMSettingsConnection *const *sett_conns; guint n_sett_conns; guint i; g_return_if_fail(NM_IS_BLUEZ_MANAGER(factory)); self = NM_BLUEZ_MANAGER(factory); priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); _cleanup_all(self); if (!priv->dbus_connection) { _LOGI("no D-Bus connection available"); return; } g_signal_connect(priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED, G_CALLBACK(cp_connection_added), self); g_signal_connect(priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK(cp_connection_updated), self); g_signal_connect(priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, G_CALLBACK(cp_connection_removed), self); priv->settings_registered = TRUE; sett_conns = nm_settings_get_connections(priv->settings, &n_sett_conns); for (i = 0; i < n_sett_conns; i++) _conn_track_update(self, sett_conns[i], TRUE, NULL, NULL, NULL); priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, NM_BLUEZ_SERVICE, name_owner_changed_cb, self, NULL); priv->name_owner_get_cancellable = g_cancellable_new(); nm_dbus_connection_call_get_name_owner(priv->dbus_connection, NM_BLUEZ_SERVICE, 10000, priv->name_owner_get_cancellable, name_owner_get_cb, self); } /*****************************************************************************/ static void _connect_returned(NMBluezManager * self, BzDBusObj * bzobj, NMBluetoothCapabilities bt_type, const char * device_name, NMBluez5DunContext * dun_context, GError * error) { char sbuf_cap[100]; if (error) { nm_assert(!device_name); nm_assert(!dun_context); _LOGI("%s [%s]: connect failed: %s", nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type, sbuf_cap, sizeof(sbuf_cap)), bzobj->object_path, error->message); _device_connect_req_data_complete(g_steal_pointer(&bzobj->x_device.c_req_data), self, NULL, error); _connect_disconnect(self, bzobj, "cleanup after connect failure"); return; } nm_assert(bzobj->x_device_connect_bt_type == bt_type); nm_assert(device_name); nm_assert((bt_type == NM_BT_CAPABILITY_DUN) == (!!dun_context)); nm_assert(bzobj->x_device.c_req_data); g_clear_object(&bzobj->x_device.c_req_data->int_cancellable); bzobj->x_device.connect_dun_context = dun_context; _LOGD("%s [%s]: connect successful to device %s", nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type, sbuf_cap, sizeof(sbuf_cap)), bzobj->object_path, device_name); /* we already have another over-all timer running. But after we connected the device, * we still need to wait for bluez to acknowledge the connected state (via D-Bus, for NAP). * For DUN profiles we likely are already fully connected by now. * * Anyway, schedule another timeout that is possibly shorter than the overall, original * timeout. Now this should go down fast. */ bzobj->x_device.c_req_data->timeout_wait_connect_id = g_timeout_add(5000, _connect_timeout_wait_connected_cb, bzobj), bzobj->x_device.c_req_data->device_name = g_strdup(device_name); if (_bzobjs_device_is_usable(bzobj, NULL, NULL) && _bzobjs_device_is_connected(bzobj)) { /* We are now connected. Schedule the task that completes the state. */ _process_change_idle_schedule(self, bzobj); } } #if WITH_BLUEZ5_DUN static void _connect_dun_notify_tty_hangup_cb(NMBluez5DunContext *context, gpointer user_data) { BzDBusObj *bzobj = user_data; _connect_disconnect(bzobj->self, bzobj, "DUN connection hung up"); } static void _connect_dun_step2_cb(NMBluez5DunContext *context, const char * rfcomm_dev, GError * error, gpointer user_data) { BzDBusObj *bzobj; if (nm_utils_error_is_cancelled(error)) return; bzobj = user_data; if (rfcomm_dev) { /* We want to early notify about the rfcomm path. That is because we might still delay * to signal full activation longer (asynchronously). But the earliest time the callback * is invoked with the rfcomm path, we just created the device synchronously. * * By already notifying the caller about the path early, it avoids a race where ModemManager * would find the modem before the bluetooth code considers the profile fully activated. */ nm_assert(!error); nm_assert(bzobj->x_device.c_req_data); if (!g_cancellable_is_cancelled(bzobj->x_device.c_req_data->ext_cancellable)) bzobj->x_device.c_req_data->callback(bzobj->self, FALSE, rfcomm_dev, NULL, bzobj->x_device.c_req_data->callback_user_data); if (!context) { /* No context set. This means, we just got notified about the rfcomm path and need to wait * longer, for the next callback. */ return; } } _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, rfcomm_dev, context, error); } static void _connect_dun_step1_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; BzDBusObj * bzobj_adapter; BzDBusObj * bzobj; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); if (!ret && nm_utils_error_is_cancelled(error)) return; bzobj = user_data; if (error) { _LOGT("DUN: [%s]: bluetooth device connect failed: %s", bzobj->object_path, error->message); /* we actually ignore this error. Let's try, maybe we still can connect via DUN. */ g_clear_error(&error); } else _LOGT("DUN: [%s]: bluetooth device connected successfully", bzobj->object_path); if (!_bzobjs_device_is_usable(bzobj, &bzobj_adapter, NULL)) { nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "device %s is not usable for DUN after connect", bzobj->object_path); _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error); return; } if (!nm_bluez5_dun_connect(bzobj_adapter->d_adapter.address, bzobj->d_device.address, bzobj->x_device.c_req_data->int_cancellable, _connect_dun_step2_cb, bzobj, _connect_dun_notify_tty_hangup_cb, bzobj, &error)) { _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error); return; } } #endif static void _connect_nap_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; const char * network_iface_name = NULL; gs_free_error GError *error = NULL; BzDBusObj * bzobj; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); if (!ret && nm_utils_error_is_cancelled(error)) return; if (ret) g_variant_get(ret, "(&s)", &network_iface_name); bzobj = user_data; _connect_returned(bzobj->self, bzobj, NM_BT_CAPABILITY_NAP, network_iface_name, NULL, error); } static void _connect_cancelled_cb(GCancellable *cancellable, BzDBusObj *bzobj) { _connect_disconnect(bzobj->self, bzobj, "connect cancelled"); } static gboolean _connect_timeout_wait_connected_cb(gpointer user_data) { BzDBusObj *bzobj = user_data; bzobj->x_device.c_req_data->timeout_wait_connect_id = 0; _connect_disconnect(bzobj->self, bzobj, "timeout waiting for connected"); return G_SOURCE_REMOVE; } static gboolean _connect_timeout_cb(gpointer user_data) { BzDBusObj *bzobj = user_data; bzobj->x_device.c_req_data->timeout_id = 0; _connect_disconnect(bzobj->self, bzobj, "timeout connecting"); return G_SOURCE_REMOVE; } static void _connect_disconnect(NMBluezManager *self, BzDBusObj *bzobj, const char *reason) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); DeviceConnectReqData * c_req_data; char sbuf_cap[100]; gboolean bt_type; if (bzobj->x_device_connect_bt_type == NM_BT_CAPABILITY_NONE) { nm_assert(!bzobj->x_device.c_req_data); return; } bt_type = bzobj->x_device_connect_bt_type; nm_assert(NM_IN_SET(bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP)); bzobj->x_device_connect_bt_type = NM_BT_CAPABILITY_NONE; c_req_data = g_steal_pointer(&bzobj->x_device.c_req_data); _LOGD("%s [%s]: disconnect due to %s", nm_bluetooth_capability_to_string(bt_type, sbuf_cap, sizeof(sbuf_cap)), bzobj->object_path, reason); if (c_req_data) nm_clear_g_cancellable(&c_req_data->int_cancellable); if (bt_type == NM_BT_CAPABILITY_DUN) { /* For DUN devices, we also called org.bluez.Device1.Connect() (because in order * for nm_bluez5_dun_connect() to succeed, we need to be already connected *why??). * * But upon disconnect we don't call Disconnect() because we don't know whether somebody * else also uses the bluetooth device for other purposes. During disconnect we only * terminate the DUN connection, but don't disconnect entirely. I think that's the * best we can do. */ #if WITH_BLUEZ5_DUN nm_clear_pointer(&bzobj->x_device.connect_dun_context, nm_bluez5_dun_disconnect); #else nm_assert_not_reached(); #endif } else { if (priv->name_owner) { gs_unref_object GCancellable *cancellable = NULL; cancellable = g_cancellable_new(); nm_shutdown_wait_obj_register_cancellable_full( cancellable, g_strdup_printf("bt-disconnect-nap[%s]", bzobj->object_path), TRUE); g_dbus_connection_call(priv->dbus_connection, priv->name_owner, bzobj->object_path, NM_BLUEZ5_NETWORK_INTERFACE, "Disconnect", g_variant_new("()"), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, cancellable, _dbus_call_complete_cb_nop, NULL); } } if (c_req_data) { gs_free_error GError *error = NULL; nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "connect aborted due to %s", reason); _device_connect_req_data_complete(c_req_data, self, NULL, error); } } gboolean nm_bluez_manager_connect(NMBluezManager * self, const char * object_path, NMBluetoothCapabilities connection_bt_type, int timeout_msec, GCancellable * cancellable, NMBluezManagerConnectCb callback, gpointer callback_user_data, GError ** error) { gs_unref_object GCancellable *int_cancellable = NULL; DeviceConnectReqData * c_req_data; NMBluezManagerPrivate * priv; BzDBusObj * bzobj; char sbuf_cap[100]; g_return_val_if_fail(NM_IS_BLUEZ_MANAGER(self), FALSE); g_return_val_if_fail(NM_IN_SET(connection_bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP), FALSE); g_return_val_if_fail(callback, FALSE); nm_assert(timeout_msec > 0); priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); bzobj = _bzobjs_get(self, object_path); if (!bzobj) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "device %s does not exist", object_path); return FALSE; } if (!_bzobjs_device_is_usable(bzobj, NULL, NULL)) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "device %s is not usable", object_path); return FALSE; } if (!NM_FLAGS_ALL(bzobj->d_device_capabilities, connection_bt_type)) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "device %s has not the required capabilities", object_path); return FALSE; } #if !WITH_BLUEZ5_DUN if (connection_bt_type == NM_BT_CAPABILITY_DUN) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "DUN is not supported"); return FALSE; } #endif _connect_disconnect(self, bzobj, "new activation"); _LOGD("%s [%s]: connecting...", nm_bluetooth_capability_to_string(connection_bt_type, sbuf_cap, sizeof(sbuf_cap)), bzobj->object_path); int_cancellable = g_cancellable_new(); #if WITH_BLUEZ5_DUN if (connection_bt_type == NM_BT_CAPABILITY_DUN) { g_dbus_connection_call(priv->dbus_connection, priv->name_owner, bzobj->object_path, NM_BLUEZ5_DEVICE_INTERFACE, "Connect", NULL, NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, timeout_msec, int_cancellable, _connect_dun_step1_cb, bzobj); } else #endif { nm_assert(connection_bt_type == NM_BT_CAPABILITY_NAP); g_dbus_connection_call(priv->dbus_connection, priv->name_owner, bzobj->object_path, NM_BLUEZ5_NETWORK_INTERFACE, "Connect", g_variant_new("(s)", BLUETOOTH_CONNECT_NAP), G_VARIANT_TYPE("(s)"), G_DBUS_CALL_FLAGS_NO_AUTO_START, timeout_msec, int_cancellable, _connect_nap_cb, bzobj); } c_req_data = g_slice_new(DeviceConnectReqData); *c_req_data = (DeviceConnectReqData){ .int_cancellable = g_steal_pointer(&int_cancellable), .ext_cancellable = g_object_ref(cancellable), .callback = callback, .callback_user_data = callback_user_data, .ext_cancelled_id = g_signal_connect(cancellable, "cancelled", G_CALLBACK(_connect_cancelled_cb), bzobj), .timeout_id = g_timeout_add(timeout_msec, _connect_timeout_cb, bzobj), }; bzobj->x_device_connect_bt_type = connection_bt_type; bzobj->x_device.c_req_data = c_req_data; return TRUE; } void nm_bluez_manager_disconnect(NMBluezManager *self, const char *object_path) { BzDBusObj *bzobj; g_return_if_fail(NM_IS_BLUEZ_MANAGER(self)); g_return_if_fail(object_path); bzobj = _bzobjs_get(self, object_path); if (!bzobj) return; _connect_disconnect(self, bzobj, "disconnected by user"); } /*****************************************************************************/ static NMDevice * create_device(NMDeviceFactory * factory, const char * iface, const NMPlatformLink *plink, NMConnection * connection, gboolean * out_ignore) { *out_ignore = TRUE; g_return_val_if_fail(plink->type == NM_LINK_TYPE_BNEP, NULL); return NULL; } static gboolean match_connection(NMDeviceFactory *factory, NMConnection *connection) { const char *type = nm_connection_get_connection_type(connection); nm_assert(nm_streq(type, NM_SETTING_BLUETOOTH_SETTING_NAME)); if (_nm_connection_get_setting_bluetooth_for_nap(connection)) return FALSE; /* handled by the bridge factory */ return TRUE; } /*****************************************************************************/ static void nm_bluez_manager_init(NMBluezManager *self) { NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); priv->vtable_network_server = (NMBtVTableNetworkServer){ .is_available = _network_server_vt_is_available, .register_bridge = _network_server_vt_register_bridge, .unregister_bridge = _network_server_vt_unregister_bridge, }; c_list_init(&priv->network_server_lst_head); c_list_init(&priv->process_change_lst_head); priv->conn_data_heads = g_hash_table_new_full(_conn_data_head_hash, _conn_data_head_equal, g_free, NULL); priv->conn_data_elems = g_hash_table_new_full(nm_pdirect_hash, nm_pdirect_equal, nm_g_slice_free_fcn(ConnDataElem), NULL); priv->bzobjs = g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _bz_dbus_obj_free, NULL); priv->manager = g_object_ref(NM_MANAGER_GET); priv->settings = g_object_ref(NM_SETTINGS_GET); priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); g_atomic_pointer_compare_and_exchange(&nm_bt_vtable_network_server, NULL, &priv->vtable_network_server); } static void dispose(GObject *object) { NMBluezManager * self = NM_BLUEZ_MANAGER(object); NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self); /* FIXME(shutdown): we need a nm_device_factory_stop() hook to first unregister all * BzDBusObj instances and do necessary cleanup actions (like disconnecting devices * or deleting panu_connection). */ nm_assert(c_list_is_empty(&priv->network_server_lst_head)); nm_assert(c_list_is_empty(&priv->process_change_lst_head)); nm_assert(priv->process_change_idle_id == 0); g_atomic_pointer_compare_and_exchange(&nm_bt_vtable_network_server, &priv->vtable_network_server, NULL); _cleanup_all(self); G_OBJECT_CLASS(nm_bluez_manager_parent_class)->dispose(object); g_clear_object(&priv->settings); g_clear_object(&priv->manager); g_clear_object(&priv->dbus_connection); nm_clear_pointer(&priv->bzobjs, g_hash_table_destroy); } static void nm_bluez_manager_class_init(NMBluezManagerClass *klass) { GObjectClass * object_class = G_OBJECT_CLASS(klass); NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); object_class->dispose = dispose; factory_class->get_supported_types = get_supported_types; factory_class->create_device = create_device; factory_class->match_connection = match_connection; factory_class->start = start; }