NetworkManager/src/nm-device-bt.c
Dan Williams 3927ccd1c0 core: decouple initial IP configuration states from device state
Initial IP configuration can happen during ACTIVATED state if both
v4 and v6 are enabled, but one takes longer than the other.  Thus
various checks throughout the code for IP_CONFIG were incorrect
since they depended on IP configuration only happening during the
IP_CONFIG state.  Fix that by using a separate state for IP config
and using that state for various checks instead of the overall
device state.
2011-11-09 21:36:52 -06:00

1178 lines
36 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2009 - 2011 Red Hat, Inc.
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <net/ethernet.h>
#include <netinet/ether.h>
#include <glib/gi18n.h>
#include "nm-glib-compat.h"
#include "nm-bluez-common.h"
#include "nm-dbus-manager.h"
#include "nm-device-bt.h"
#include "nm-device-interface.h"
#include "nm-device-private.h"
#include "nm-logging.h"
#include "nm-marshal.h"
#include "ppp-manager/nm-ppp-manager.h"
#include "nm-properties-changed-signal.h"
#include "nm-setting-connection.h"
#include "nm-setting-bluetooth.h"
#include "nm-setting-cdma.h"
#include "nm-setting-gsm.h"
#include "nm-setting-serial.h"
#include "nm-setting-ppp.h"
#include "nm-device-bt-glue.h"
#include "NetworkManagerUtils.h"
#define BLUETOOTH_DUN_UUID "dun"
#define BLUETOOTH_NAP_UUID "nap"
G_DEFINE_TYPE (NMDeviceBt, nm_device_bt, NM_TYPE_DEVICE)
#define NM_DEVICE_BT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_BT, NMDeviceBtPrivate))
static gboolean modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *reason);
typedef struct {
char *bdaddr;
char *name;
guint32 capabilities;
gboolean connected;
gboolean have_iface;
DBusGProxy *type_proxy;
DBusGProxy *dev_proxy;
char *rfcomm_iface;
NMModem *modem;
guint32 timeout_id;
guint32 bt_type; /* BT type of the current connection */
} NMDeviceBtPrivate;
enum {
PROP_0,
PROP_HW_ADDRESS,
PROP_BT_NAME,
PROP_BT_CAPABILITIES,
LAST_PROP
};
enum {
PPP_STATS,
PROPERTIES_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef enum {
NM_BT_ERROR_CONNECTION_NOT_BT = 0,
NM_BT_ERROR_CONNECTION_INVALID,
NM_BT_ERROR_CONNECTION_INCOMPATIBLE,
} NMBtError;
#define NM_BT_ERROR (nm_bt_error_quark ())
#define NM_TYPE_BT_ERROR (nm_bt_error_get_type ())
static GQuark
nm_bt_error_quark (void)
{
static GQuark quark = 0;
if (!quark)
quark = g_quark_from_static_string ("nm-bt-error");
return quark;
}
/* This should really be standard. */
#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
static GType
nm_bt_error_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
/* Connection was not a BT connection. */
ENUM_ENTRY (NM_BT_ERROR_CONNECTION_NOT_BT, "ConnectionNotBt"),
/* Connection was not a valid BT connection. */
ENUM_ENTRY (NM_BT_ERROR_CONNECTION_INVALID, "ConnectionInvalid"),
/* Connection does not apply to this device. */
ENUM_ENTRY (NM_BT_ERROR_CONNECTION_INCOMPATIBLE, "ConnectionIncompatible"),
{ 0, 0, 0 }
};
etype = g_enum_register_static ("NMBtError", values);
}
return etype;
}
guint32 nm_device_bt_get_capabilities (NMDeviceBt *self)
{
g_return_val_if_fail (self != NULL, NM_BT_CAPABILITY_NONE);
g_return_val_if_fail (NM_IS_DEVICE_BT (self), NM_BT_CAPABILITY_NONE);
return NM_DEVICE_BT_GET_PRIVATE (self)->capabilities;
}
const char *nm_device_bt_get_hw_address (NMDeviceBt *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (NM_IS_DEVICE_BT (self), NULL);
return NM_DEVICE_BT_GET_PRIVATE (self)->bdaddr;
}
static guint32
get_connection_bt_type (NMConnection *connection)
{
NMSettingBluetooth *s_bt;
const char *bt_type;
s_bt = (NMSettingBluetooth *) nm_connection_get_setting (connection, NM_TYPE_SETTING_BLUETOOTH);
if (!s_bt)
return NM_BT_CAPABILITY_NONE;
bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
g_assert (bt_type);
if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN))
return NM_BT_CAPABILITY_DUN;
else if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU))
return NM_BT_CAPABILITY_NAP;
return NM_BT_CAPABILITY_NONE;
}
static NMConnection *
real_get_best_auto_connection (NMDevice *device,
GSList *connections,
char **specific_object)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
GSList *iter;
for (iter = connections; iter; iter = g_slist_next (iter)) {
NMConnection *connection = NM_CONNECTION (iter->data);
NMSettingConnection *s_con;
guint32 bt_type;
s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
g_assert (s_con);
if (!nm_setting_connection_get_autoconnect (s_con))
continue;
if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_BLUETOOTH_SETTING_NAME))
continue;
bt_type = get_connection_bt_type (connection);
if (!(bt_type & priv->capabilities))
continue;
return connection;
}
return NULL;
}
static gboolean
real_check_connection_compatible (NMDevice *device,
NMConnection *connection,
GError **error)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
NMSettingConnection *s_con;
NMSettingBluetooth *s_bt;
const GByteArray *array;
char *str;
int addr_match = FALSE;
guint32 bt_type;
s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION));
g_assert (s_con);
if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_BLUETOOTH_SETTING_NAME)) {
g_set_error (error,
NM_BT_ERROR, NM_BT_ERROR_CONNECTION_NOT_BT,
"The connection was not a Bluetooth connection.");
return FALSE;
}
s_bt = NM_SETTING_BLUETOOTH (nm_connection_get_setting (connection, NM_TYPE_SETTING_BLUETOOTH));
if (!s_bt) {
g_set_error (error,
NM_BT_ERROR, NM_BT_ERROR_CONNECTION_INVALID,
"The connection was not a valid Bluetooth connection.");
return FALSE;
}
array = nm_setting_bluetooth_get_bdaddr (s_bt);
if (!array || (array->len != ETH_ALEN)) {
g_set_error (error,
NM_BT_ERROR, NM_BT_ERROR_CONNECTION_INVALID,
"The connection did not contain a valid Bluetooth address.");
return FALSE;
}
bt_type = get_connection_bt_type (connection);
if (!(bt_type & priv->capabilities)) {
g_set_error (error,
NM_BT_ERROR, NM_BT_ERROR_CONNECTION_INCOMPATIBLE,
"The connection was not compatible with the device's capabilities.");
return FALSE;
}
str = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
array->data[0], array->data[1], array->data[2],
array->data[3], array->data[4], array->data[5]);
addr_match = !strcmp (priv->bdaddr, str);
g_free (str);
return addr_match;
}
static gboolean
real_complete_connection (NMDevice *device,
NMConnection *connection,
const char *specific_object,
const GSList *existing_connections,
GError **error)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
NMSettingBluetooth *s_bt;
const GByteArray *setting_bdaddr;
struct ether_addr *devaddr = ether_aton (priv->bdaddr);
const char *ctype;
gboolean is_dun = FALSE, is_pan = FALSE;
NMSettingGsm *s_gsm;
NMSettingCdma *s_cdma;
NMSettingSerial *s_serial;
NMSettingPPP *s_ppp;
const char *format = NULL, *preferred = NULL;
s_gsm = (NMSettingGsm *) nm_connection_get_setting (connection, NM_TYPE_SETTING_GSM);
s_cdma = (NMSettingCdma *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CDMA);
s_serial = (NMSettingSerial *) nm_connection_get_setting (connection, NM_TYPE_SETTING_SERIAL);
s_ppp = (NMSettingPPP *) nm_connection_get_setting (connection, NM_TYPE_SETTING_PPP);
s_bt = (NMSettingBluetooth *) nm_connection_get_setting (connection, NM_TYPE_SETTING_BLUETOOTH);
if (!s_bt) {
s_bt = (NMSettingBluetooth *) nm_setting_bluetooth_new ();
nm_connection_add_setting (connection, NM_SETTING (s_bt));
}
ctype = nm_setting_bluetooth_get_connection_type (s_bt);
if (ctype) {
if (!strcmp (ctype, NM_SETTING_BLUETOOTH_TYPE_DUN))
is_dun = TRUE;
else if (!strcmp (ctype, NM_SETTING_BLUETOOTH_TYPE_PANU))
is_pan = TRUE;
} else {
if (s_gsm || s_cdma)
is_dun = TRUE;
else if (priv->capabilities & NM_BT_CAPABILITY_NAP)
is_pan = TRUE;
}
if (is_pan) {
/* Make sure the device supports PAN */
if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) {
g_set_error_literal (error,
NM_SETTING_BLUETOOTH_ERROR,
NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY,
"PAN required but Bluetooth device does not support NAP");
return FALSE;
}
/* PAN can't use any DUN-related settings */
if (s_gsm || s_cdma || s_serial || s_ppp) {
g_set_error_literal (error,
NM_SETTING_BLUETOOTH_ERROR,
NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY,
"PAN incompatible with GSM, CDMA, or serial settings");
return FALSE;
}
g_object_set (G_OBJECT (s_bt),
NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU,
NULL);
format = _("PAN connection %d");
} else if (is_dun) {
/* Make sure the device supports PAN */
if (!(priv->capabilities & NM_BT_CAPABILITY_DUN)) {
g_set_error_literal (error,
NM_SETTING_BLUETOOTH_ERROR,
NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY,
"DUN required but Bluetooth device does not support DUN");
return FALSE;
}
/* Need at least a GSM or a CDMA setting */
if (!s_gsm && !s_cdma) {
g_set_error_literal (error,
NM_SETTING_BLUETOOTH_ERROR,
NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY,
"Setting requires DUN but no GSM or CDMA setting is present");
return FALSE;
}
g_object_set (G_OBJECT (s_bt),
NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_DUN,
NULL);
if (s_gsm) {
format = _("GSM connection %d");
if (!nm_setting_gsm_get_number (s_gsm))
g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL);
} else if (s_cdma) {
format = _("CDMA connection %d");
if (!nm_setting_cdma_get_number (s_cdma))
g_object_set (G_OBJECT (s_cdma), NM_SETTING_GSM_NUMBER, "#777", NULL);
} else
format = _("DUN connection %d");
} else {
g_set_error_literal (error,
NM_SETTING_BLUETOOTH_ERROR,
NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY,
"Unknown/unhandled Bluetooth connection type");
return FALSE;
}
nm_utils_complete_generic (connection,
NM_SETTING_BLUETOOTH_SETTING_NAME,
existing_connections,
format,
preferred,
is_dun ? FALSE : TRUE); /* No IPv6 yet for DUN */
setting_bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
if (setting_bdaddr) {
/* Make sure the setting BT Address (if any) matches the device's */
if (memcmp (setting_bdaddr->data, devaddr->ether_addr_octet, ETH_ALEN)) {
g_set_error_literal (error,
NM_SETTING_BLUETOOTH_ERROR,
NM_SETTING_BLUETOOTH_ERROR_INVALID_PROPERTY,
NM_SETTING_BLUETOOTH_BDADDR);
return FALSE;
}
} else {
GByteArray *bdaddr;
const guint8 null_mac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 };
/* Lock the connection to this device by default */
if (memcmp (devaddr->ether_addr_octet, null_mac, ETH_ALEN)) {
bdaddr = g_byte_array_sized_new (ETH_ALEN);
g_byte_array_append (bdaddr, devaddr->ether_addr_octet, ETH_ALEN);
g_object_set (G_OBJECT (s_bt), NM_SETTING_BLUETOOTH_BDADDR, bdaddr, NULL);
g_byte_array_free (bdaddr, TRUE);
}
}
return TRUE;
}
static guint32
real_get_generic_capabilities (NMDevice *dev)
{
return NM_DEVICE_CAP_NM_SUPPORTED;
}
/*****************************************************************************/
/* IP method PPP */
static void
ppp_stats (NMModem *modem,
guint32 in_bytes,
guint32 out_bytes,
gpointer user_data)
{
g_signal_emit (NM_DEVICE_BT (user_data), signals[PPP_STATS], 0, in_bytes, out_bytes);
}
static void
ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
switch (nm_device_interface_get_state (NM_DEVICE_INTERFACE (device))) {
case NM_DEVICE_STATE_PREPARE:
case NM_DEVICE_STATE_CONFIG:
case NM_DEVICE_STATE_NEED_AUTH:
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
break;
case NM_DEVICE_STATE_IP_CONFIG:
case NM_DEVICE_STATE_IP_CHECK:
case NM_DEVICE_STATE_SECONDARIES:
case NM_DEVICE_STATE_ACTIVATED:
if (nm_device_ip_config_should_fail (device, FALSE)) {
nm_device_state_changed (device,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
}
break;
default:
break;
}
}
static void
modem_auth_requested (NMModem *modem, gpointer user_data)
{
nm_device_state_changed (NM_DEVICE (user_data),
NM_DEVICE_STATE_NEED_AUTH,
NM_DEVICE_STATE_REASON_NONE);
}
static void
modem_auth_result (NMModem *modem, GError *error, gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
if (error) {
nm_device_state_changed (device,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_NO_SECRETS);
} else {
/* Otherwise, on success for GSM/CDMA secrets we need to schedule modem stage1 again */
g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
if (!modem_stage1 (NM_DEVICE_BT (device), priv->modem, &reason))
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
}
}
static void
modem_prepare_result (NMModem *modem,
gboolean success,
NMDeviceStateReason reason,
gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
NMDeviceState state;
state = nm_device_interface_get_state (NM_DEVICE_INTERFACE (device));
g_return_if_fail (state == NM_DEVICE_STATE_CONFIG || state == NM_DEVICE_STATE_NEED_AUTH);
if (success) {
NMActRequest *req;
NMActStageReturn ret;
NMDeviceStateReason stage2_reason = NM_DEVICE_STATE_REASON_NONE;
req = nm_device_get_act_request (device);
g_assert (req);
ret = nm_modem_act_stage2_config (modem, req, &stage2_reason);
switch (ret) {
case NM_ACT_STAGE_RETURN_POSTPONE:
break;
case NM_ACT_STAGE_RETURN_SUCCESS:
nm_device_activate_schedule_stage3_ip_config_start (device);
break;
case NM_ACT_STAGE_RETURN_FAILURE:
default:
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, stage2_reason);
break;
}
} else
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
}
static void
device_state_changed (NMDevice *device,
NMDeviceState new_state,
NMDeviceState old_state,
NMDeviceStateReason reason,
gpointer user_data)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
if (priv->modem)
nm_modem_device_state_changed (priv->modem, new_state, old_state, reason);
}
static void
modem_ip4_config_result (NMModem *self,
const char *iface,
NMIP4Config *config,
GError *error,
gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
g_return_if_fail (nm_device_activate_ip4_state_in_conf (device) == TRUE);
if (error) {
nm_log_warn (LOGD_MB | LOGD_IP4 | LOGD_BT,
"(%s): retrieving IP4 configuration failed: (%d) %s",
nm_device_get_ip_iface (device),
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
} else {
if (iface)
nm_device_set_ip_iface (device, iface);
nm_device_activate_schedule_ip4_config_result (device, config);
}
}
static gboolean
modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *reason)
{
NMActRequest *req;
NMActStageReturn ret;
g_return_val_if_fail (reason != NULL, FALSE);
req = nm_device_get_act_request (NM_DEVICE (self));
g_assert (req);
ret = nm_modem_act_stage1_prepare (modem, req, reason);
switch (ret) {
case NM_ACT_STAGE_RETURN_POSTPONE:
case NM_ACT_STAGE_RETURN_SUCCESS:
/* Success, wait for the 'prepare-result' signal */
return TRUE;
case NM_ACT_STAGE_RETURN_FAILURE:
default:
break;
}
return FALSE;
}
/*****************************************************************************/
gboolean
nm_device_bt_modem_added (NMDeviceBt *self,
NMModem *modem,
const char *driver)
{
NMDeviceBtPrivate *priv;
const char *modem_iface;
char *base;
NMDeviceState state;
NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (NM_IS_DEVICE_BT (self), FALSE);
g_return_val_if_fail (modem != NULL, FALSE);
g_return_val_if_fail (NM_IS_MODEM (modem), FALSE);
priv = NM_DEVICE_BT_GET_PRIVATE (self);
modem_iface = nm_modem_get_iface (modem);
g_return_val_if_fail (modem_iface != NULL, FALSE);
if (!priv->rfcomm_iface)
return FALSE;
base = g_path_get_basename (priv->rfcomm_iface);
if (strcmp (base, modem_iface)) {
g_free (base);
return FALSE;
}
g_free (base);
/* Got the modem */
if (priv->timeout_id) {
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
}
/* Can only accept the modem in stage2, but since the interface matched
* what we were expecting, don't let anything else claim the modem either.
*/
state = nm_device_interface_get_state (NM_DEVICE_INTERFACE (self));
if (state != NM_DEVICE_STATE_CONFIG) {
nm_log_warn (LOGD_BT | LOGD_MB,
"(%s): modem found but device not in correct state (%d)",
nm_device_get_iface (NM_DEVICE (self)),
nm_device_get_state (NM_DEVICE (self)));
return TRUE;
}
nm_log_info (LOGD_BT | LOGD_MB,
"Activation (%s/bluetooth) Stage 2 of 5 (Device Configure) modem found.",
nm_device_get_iface (NM_DEVICE (self)));
if (priv->modem) {
g_warn_if_reached ();
g_object_unref (priv->modem);
}
priv->modem = g_object_ref (modem);
g_signal_connect (modem, NM_MODEM_PPP_STATS, G_CALLBACK (ppp_stats), self);
g_signal_connect (modem, NM_MODEM_PPP_FAILED, G_CALLBACK (ppp_failed), self);
g_signal_connect (modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK (modem_prepare_result), self);
g_signal_connect (modem, NM_MODEM_IP4_CONFIG_RESULT, G_CALLBACK (modem_ip4_config_result), self);
g_signal_connect (modem, NM_MODEM_AUTH_REQUESTED, G_CALLBACK (modem_auth_requested), self);
g_signal_connect (modem, NM_MODEM_AUTH_RESULT, G_CALLBACK (modem_auth_result), self);
/* Kick off the modem connection */
if (!modem_stage1 (self, modem, &reason))
nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, reason);
return TRUE;
}
gboolean
nm_device_bt_modem_removed (NMDeviceBt *self, NMModem *modem)
{
NMDeviceBtPrivate *priv;
NMDeviceState state;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (NM_IS_DEVICE_BT (self), FALSE);
g_return_val_if_fail (modem != NULL, FALSE);
g_return_val_if_fail (NM_IS_MODEM (modem), FALSE);
priv = NM_DEVICE_BT_GET_PRIVATE (self);
if (modem != priv->modem)
return FALSE;
state = nm_device_get_state (NM_DEVICE (self));
nm_modem_device_state_changed (priv->modem,
NM_DEVICE_STATE_DISCONNECTED,
state,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
g_object_unref (priv->modem);
priv->modem = NULL;
return TRUE;
}
static gboolean
modem_find_timeout (gpointer user_data)
{
NMDeviceBt *self = NM_DEVICE_BT (user_data);
NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0;
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND);
return FALSE;
}
static void
check_connect_continue (NMDeviceBt *self)
{
NMDevice *device = NM_DEVICE (self);
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
gboolean pan = (priv->bt_type == NM_BT_CAPABILITY_NAP);
gboolean dun = (priv->bt_type == NM_BT_CAPABILITY_DUN);
if (!priv->connected || !priv->have_iface)
return;
nm_log_info (LOGD_BT, "Activation (%s %s/bluetooth) Stage 2 of 5 (Device Configure) "
"successful. Will connect via %s.",
nm_device_get_iface (device),
nm_device_get_ip_iface (device),
dun ? "DUN" : (pan ? "PAN" : "unknown"));
/* Kill the connect timeout since we're connected now */
if (priv->timeout_id) {
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
}
if (pan) {
/* Bluez says we're connected now. Start IP config. */
nm_device_activate_schedule_stage3_ip_config_start (device);
} else if (dun) {
/* Wait for ModemManager to find the modem */
priv->timeout_id = g_timeout_add_seconds (30, modem_find_timeout, self);
nm_log_info (LOGD_BT | LOGD_MB, "Activation (%s/bluetooth) Stage 2 of 5 (Device Configure) "
"waiting for modem to appear.",
nm_device_get_iface (device));
} else
g_assert_not_reached ();
}
static void
bluez_connect_cb (DBusGProxy *proxy,
DBusGProxyCall *call_id,
void *user_data)
{
NMDeviceBt *self = NM_DEVICE_BT (user_data);
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
GError *error = NULL;
char *device;
if (dbus_g_proxy_end_call (proxy, call_id, &error,
G_TYPE_STRING, &device,
G_TYPE_INVALID) == FALSE) {
nm_log_warn (LOGD_BT, "Error connecting with bluez: %s",
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_BT_FAILED);
return;
}
if (!device || !strlen (device)) {
nm_log_warn (LOGD_BT, "Invalid network device returned by bluez");
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_BT_FAILED);
}
if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
g_free (priv->rfcomm_iface);
priv->rfcomm_iface = device;
} else if (priv->bt_type == NM_BT_CAPABILITY_NAP) {
nm_device_set_ip_iface (NM_DEVICE (self), device);
g_free (device);
}
nm_log_dbg (LOGD_BT, "(%s): connect request successful",
nm_device_get_iface (NM_DEVICE (self)));
/* Stage 3 gets scheduled when Bluez says we're connected */
priv->have_iface = TRUE;
check_connect_continue (self);
}
static void
bluez_property_changed (DBusGProxy *proxy,
const char *property,
GValue *value,
gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
NMDeviceBt *self = NM_DEVICE_BT (user_data);
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
gboolean connected;
NMDeviceState state;
const char *prop_str = "(unknown)";
if (G_VALUE_HOLDS_STRING (value))
prop_str = g_value_get_string (value);
else if (G_VALUE_HOLDS_BOOLEAN (value))
prop_str = g_value_get_boolean (value) ? "true" : "false";
nm_log_dbg (LOGD_BT, "(%s): bluez property '%s' changed to '%s'",
nm_device_get_iface (device),
property,
prop_str);
if (strcmp (property, "Connected"))
return;
state = nm_device_get_state (device);
connected = g_value_get_boolean (value);
if (connected) {
if (state == NM_DEVICE_STATE_CONFIG) {
nm_log_dbg (LOGD_BT, "(%s): connected to the device",
nm_device_get_iface (device));
priv->connected = TRUE;
check_connect_continue (self);
}
} else {
gboolean fail = FALSE;
/* Bluez says we're disconnected from the device. Suck. */
if (nm_device_is_activating (device)) {
nm_log_info (LOGD_BT,
"Activation (%s/bluetooth): bluetooth link disconnected.",
nm_device_get_iface (device));
fail = TRUE;
} else if (state == NM_DEVICE_STATE_ACTIVATED) {
nm_log_info (LOGD_BT, "(%s): bluetooth link disconnected.",
nm_device_get_iface (device));
fail = TRUE;
}
if (fail) {
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CARRIER);
priv->connected = FALSE;
}
}
}
static gboolean
bt_connect_timeout (gpointer user_data)
{
NMDeviceBt *self = NM_DEVICE_BT (user_data);
nm_log_dbg (LOGD_BT, "(%s): initial connection timed out",
nm_device_get_iface (NM_DEVICE (self)));
NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0;
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_BT_FAILED);
return FALSE;
}
static NMActStageReturn
real_act_stage2_config (NMDevice *device, NMDeviceStateReason *reason)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
NMActRequest *req;
NMDBusManager *dbus_mgr;
DBusGConnection *g_connection;
gboolean dun = FALSE;
req = nm_device_get_act_request (device);
g_assert (req);
priv->bt_type = get_connection_bt_type (nm_act_request_get_connection (req));
if (priv->bt_type == NM_BT_CAPABILITY_NONE) {
// FIXME: set a reason code
return NM_ACT_STAGE_RETURN_FAILURE;
}
dbus_mgr = nm_dbus_manager_get ();
g_connection = nm_dbus_manager_get_connection (dbus_mgr);
g_object_unref (dbus_mgr);
if (priv->bt_type == NM_BT_CAPABILITY_DUN)
dun = TRUE;
else if (priv->bt_type == NM_BT_CAPABILITY_NAP)
dun = FALSE;
else
g_assert_not_reached ();
priv->dev_proxy = dbus_g_proxy_new_for_name (g_connection,
BLUEZ_SERVICE,
nm_device_get_udi (device),
BLUEZ_DEVICE_INTERFACE);
if (!priv->dev_proxy) {
// FIXME: set a reason code
return NM_ACT_STAGE_RETURN_FAILURE;
}
/* Watch for BT device property changes */
dbus_g_object_register_marshaller (_nm_marshal_VOID__STRING_BOXED,
G_TYPE_NONE,
G_TYPE_STRING, G_TYPE_VALUE,
G_TYPE_INVALID);
dbus_g_proxy_add_signal (priv->dev_proxy, "PropertyChanged",
G_TYPE_STRING, G_TYPE_VALUE, G_TYPE_INVALID);
dbus_g_proxy_connect_signal (priv->dev_proxy, "PropertyChanged",
G_CALLBACK (bluez_property_changed), device, NULL);
priv->type_proxy = dbus_g_proxy_new_for_name (g_connection,
BLUEZ_SERVICE,
nm_device_get_udi (device),
dun ? BLUEZ_SERIAL_INTERFACE : BLUEZ_NETWORK_INTERFACE);
if (!priv->type_proxy) {
// FIXME: set a reason code
return NM_ACT_STAGE_RETURN_FAILURE;
}
nm_log_dbg (LOGD_BT, "(%s): requesting connection to the device",
nm_device_get_iface (device));
/* Connect to the BT device */
dbus_g_proxy_begin_call_with_timeout (priv->type_proxy, "Connect",
bluez_connect_cb,
device,
NULL,
20000,
G_TYPE_STRING, dun ? BLUETOOTH_DUN_UUID : BLUETOOTH_NAP_UUID,
G_TYPE_INVALID);
if (priv->timeout_id)
g_source_remove (priv->timeout_id);
priv->timeout_id = g_timeout_add_seconds (30, bt_connect_timeout, device);
return NM_ACT_STAGE_RETURN_POSTPONE;
}
static NMActStageReturn
real_act_stage3_ip4_config_start (NMDevice *device,
NMIP4Config **out_config,
NMDeviceStateReason *reason)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
NMActStageReturn ret;
if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
ret = nm_modem_stage3_ip4_config_start (NM_DEVICE_BT_GET_PRIVATE (device)->modem,
device,
NM_DEVICE_CLASS (nm_device_bt_parent_class),
reason);
} else
ret = NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip4_config_start (device, out_config, reason);
return ret;
}
static void
real_deactivate (NMDevice *device)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
priv->have_iface = FALSE;
priv->connected = FALSE;
if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
if (priv->modem) {
nm_modem_deactivate (priv->modem, device);
/* Since we're killing the Modem object before it'll get the
* state change signal, simulate the state change here.
*/
nm_modem_device_state_changed (priv->modem,
NM_DEVICE_STATE_DISCONNECTED,
NM_DEVICE_STATE_ACTIVATED,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
g_object_unref (priv->modem);
priv->modem = NULL;
}
if (priv->type_proxy) {
/* Don't ever pass NULL through dbus; rfcomm_iface
* might happen to be NULL for some reason.
*/
if (priv->rfcomm_iface) {
dbus_g_proxy_call_no_reply (priv->type_proxy, "Disconnect",
G_TYPE_STRING, priv->rfcomm_iface,
G_TYPE_INVALID);
}
g_object_unref (priv->type_proxy);
priv->type_proxy = NULL;
}
} else if (priv->bt_type == NM_BT_CAPABILITY_NAP) {
if (priv->type_proxy) {
dbus_g_proxy_call_no_reply (priv->type_proxy, "Disconnect",
G_TYPE_INVALID);
g_object_unref (priv->type_proxy);
priv->type_proxy = NULL;
}
}
if (priv->dev_proxy) {
g_object_unref (priv->dev_proxy);
priv->dev_proxy = NULL;
}
if (priv->timeout_id) {
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
}
priv->bt_type = NM_BT_CAPABILITY_NONE;
g_free (priv->rfcomm_iface);
priv->rfcomm_iface = NULL;
if (NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate)
NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate (device);
}
/*****************************************************************************/
NMDevice *
nm_device_bt_new (const char *udi,
const char *bdaddr,
const char *name,
guint32 capabilities,
gboolean managed)
{
NMDevice *device;
g_return_val_if_fail (udi != NULL, NULL);
g_return_val_if_fail (bdaddr != NULL, NULL);
g_return_val_if_fail (name != NULL, NULL);
g_return_val_if_fail (capabilities != NM_BT_CAPABILITY_NONE, NULL);
device = (NMDevice *) g_object_new (NM_TYPE_DEVICE_BT,
NM_DEVICE_INTERFACE_UDI, udi,
NM_DEVICE_INTERFACE_IFACE, bdaddr,
NM_DEVICE_INTERFACE_DRIVER, "bluez",
NM_DEVICE_BT_HW_ADDRESS, bdaddr,
NM_DEVICE_BT_NAME, name,
NM_DEVICE_BT_CAPABILITIES, capabilities,
NM_DEVICE_INTERFACE_MANAGED, managed,
NM_DEVICE_INTERFACE_TYPE_DESC, "Bluetooth",
NM_DEVICE_INTERFACE_DEVICE_TYPE, NM_DEVICE_TYPE_BT,
NULL);
if (device)
g_signal_connect (device, "state-changed", G_CALLBACK (device_state_changed), device);
return device;
}
static void
nm_device_bt_init (NMDeviceBt *self)
{
}
static void
set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object);
switch (prop_id) {
case PROP_HW_ADDRESS:
/* Construct only */
priv->bdaddr = g_ascii_strup (g_value_get_string (value), -1);
break;
case PROP_BT_NAME:
/* Construct only */
priv->name = g_value_dup_string (value);
break;
case PROP_BT_CAPABILITIES:
/* Construct only */
priv->capabilities = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object);
switch (prop_id) {
case PROP_HW_ADDRESS:
g_value_set_string (value, priv->bdaddr);
break;
case PROP_BT_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_BT_CAPABILITIES:
g_value_set_uint (value, priv->capabilities);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
finalize (GObject *object)
{
NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object);
if (priv->timeout_id) {
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
}
if (priv->type_proxy)
g_object_unref (priv->type_proxy);
if (priv->dev_proxy)
g_object_unref (priv->dev_proxy);
if (priv->modem)
g_object_unref (priv->modem);
g_free (priv->rfcomm_iface);
g_free (priv->bdaddr);
g_free (priv->name);
G_OBJECT_CLASS (nm_device_bt_parent_class)->finalize (object);
}
static void
nm_device_bt_class_init (NMDeviceBtClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
g_type_class_add_private (object_class, sizeof (NMDeviceBtPrivate));
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
device_class->get_best_auto_connection = real_get_best_auto_connection;
device_class->get_generic_capabilities = real_get_generic_capabilities;
device_class->deactivate = real_deactivate;
device_class->act_stage2_config = real_act_stage2_config;
device_class->act_stage3_ip4_config_start = real_act_stage3_ip4_config_start;
device_class->check_connection_compatible = real_check_connection_compatible;
device_class->complete_connection = real_complete_connection;
/* Properties */
g_object_class_install_property
(object_class, PROP_HW_ADDRESS,
g_param_spec_string (NM_DEVICE_BT_HW_ADDRESS,
"Bluetooth address",
"Bluetooth address",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(object_class, PROP_BT_NAME,
g_param_spec_string (NM_DEVICE_BT_NAME,
"Bluetooth device name",
"Bluetooth device name",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property
(object_class, PROP_BT_CAPABILITIES,
g_param_spec_uint (NM_DEVICE_BT_CAPABILITIES,
"Bluetooth device capabilities",
"Bluetooth device capabilities",
NM_BT_CAPABILITY_NONE, G_MAXUINT, NM_BT_CAPABILITY_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/* Signals */
signals[PPP_STATS] =
g_signal_new ("ppp-stats",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMDeviceBtClass, ppp_stats),
NULL, NULL,
_nm_marshal_VOID__UINT_UINT,
G_TYPE_NONE, 2,
G_TYPE_UINT, G_TYPE_UINT);
signals[PROPERTIES_CHANGED] =
nm_properties_changed_signal_new (object_class,
G_STRUCT_OFFSET (NMDeviceBtClass, properties_changed));
dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (klass),
&dbus_glib_nm_device_bt_object_info);
dbus_g_error_domain_register (NM_BT_ERROR, NULL, NM_TYPE_BT_ERROR);
}