blob: f9b86884734906248de4b184f2f023bcc01e444f [file]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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:
*
* Copyright (C) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2011 Red Hat, Inc.
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2015 Azimut Electronics
* Copyright (C) 2011 - 2016 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-daemon-enums-types.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-3gpp-profile-manager.h"
#include "mm-iface-modem-cdma.h"
#include "mm-base-bearer.h"
#include "mm-base-modem.h"
#include "mm-log-object.h"
#include "mm-modem-helpers.h"
#include "mm-error-helpers.h"
#include "mm-bearer-stats.h"
#include "mm-dispatcher-connection.h"
#include "mm-auth-provider.h"
#include "mm-bind.h"
/* We require up to 20s to get a proper IP when using PPP */
#define BEARER_IP_TIMEOUT_DEFAULT 20
#define BEARER_DEFERRED_UNREGISTRATION_TIMEOUT 15
#define BEARER_STATS_UPDATE_TIMEOUT 30
/* Initial connectivity check after 30s, then each 5s */
#define BEARER_CONNECTION_MONITOR_INITIAL_TIMEOUT 30
#define BEARER_CONNECTION_MONITOR_TIMEOUT 5
static void log_object_iface_init (MMLogObjectInterface *iface);
static void bind_iface_init (MMBindInterface *iface);
G_DEFINE_TYPE_EXTENDED (MMBaseBearer, mm_base_bearer, MM_GDBUS_TYPE_BEARER_SKELETON, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_BIND, bind_iface_init))
typedef enum {
CONNECTION_FORBIDDEN_REASON_NONE,
CONNECTION_FORBIDDEN_REASON_UNREGISTERED,
CONNECTION_FORBIDDEN_REASON_ROAMING,
CONNECTION_FORBIDDEN_REASON_EMERGENCY_ONLY,
CONNECTION_FORBIDDEN_REASON_LAST
} ConnectionForbiddenReason;
enum {
PROP_0,
PROP_PATH,
PROP_CONNECTION,
PROP_BIND_TO,
PROP_MODEM,
PROP_STATUS,
PROP_CONFIG,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
struct _MMBaseBearerPrivate {
/* The connection to the system bus */
GDBusConnection *connection;
guint dbus_id;
/* The authorization provider */
MMAuthProvider *authp;
GCancellable *authp_cancellable;
/* The object this bearer is bound to */
GObject *bind_to;
/* The modem which owns this BEARER */
MMBaseModem *modem;
/* The path where the BEARER object is exported */
gchar *path;
/* Status of this bearer */
MMBearerStatus status;
/* Whether we must ignore all disconnection updates if they're
* detected by ModemManager itself. */
gboolean ignore_disconnection_reports;
/* Configuration of the bearer */
MMBearerProperties *config;
/* Cancellable for connect() */
GCancellable *connect_cancellable;
/* handler id for the disconnect + cancel connect request */
gulong disconnect_signal_handler;
/* Connection status monitoring */
guint connection_monitor_id;
/* Flag to specify whether connection monitoring is supported or not */
gboolean load_connection_status_unsupported;
/*-- 3GPP specific --*/
guint deferred_3gpp_unregistration_id;
/* Reason if 3GPP connection is forbidden */
ConnectionForbiddenReason reason_3gpp;
/* Handler ID for the registration state change signals */
guint id_3gpp_registration_change;
/*-- CDMA specific --*/
guint deferred_cdma_unregistration_id;
/* Reason if CDMA connection is forbidden */
ConnectionForbiddenReason reason_cdma;
/* Handler IDs for the registration state change signals */
guint id_cdma1x_registration_change;
guint id_evdo_registration_change;
/* The stats object to expose */
MMBearerStats *stats;
/* Handler id for the stats update timeout */
guint stats_update_id;
/* Timer to measure the duration of the connection */
GTimer *duration_timer;
/* Flag to specify whether reloading stats is supported or not */
gboolean reload_stats_supported;
};
/*****************************************************************************/
static const gchar *connection_forbidden_reason_str [CONNECTION_FORBIDDEN_REASON_LAST] = {
"none",
"Not registered in the network",
"Registered in roaming network, and roaming not allowed",
"Emergency services only",
};
/*****************************************************************************/
void
mm_base_bearer_export (MMBaseBearer *self)
{
gchar *path;
path = g_strdup_printf (MM_DBUS_BEARER_PREFIX "/%d", self->priv->dbus_id);
g_object_set (self,
MM_BASE_BEARER_PATH, path,
NULL);
g_free (path);
}
/*****************************************************************************/
static void
connection_monitor_stop (MMBaseBearer *self)
{
if (self->priv->connection_monitor_id) {
g_source_remove (self->priv->connection_monitor_id);
self->priv->connection_monitor_id = 0;
}
}
static void
load_connection_status_ready (MMBaseBearer *self,
GAsyncResult *res)
{
GError *error = NULL;
MMBearerConnectionStatus status;
status = MM_BASE_BEARER_GET_CLASS (self)->load_connection_status_finish (self, res, &error);
if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) {
/* Only warn if not reporting an "unsupported" error */
if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
mm_obj_warn (self, "checking if connected failed: %s", error->message);
g_error_free (error);
return;
}
/* If we're being told that connection monitoring is unsupported, just
* ignore the error and remove the timeout. */
mm_obj_dbg (self, "connection monitoring is unsupported by the device");
self->priv->load_connection_status_unsupported = TRUE;
connection_monitor_stop (self);
g_error_free (error);
return;
}
/* Report connection or disconnection */
g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED || status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
mm_obj_dbg (self, "connection status loaded: %s", mm_bearer_connection_status_get_string (status));
mm_base_bearer_report_connection_status (self, status);
}
static gboolean
connection_monitor_cb (MMBaseBearer *self)
{
/* If the implementation knows how to load connection status, run it */
if (self->priv->status == MM_BEARER_STATUS_CONNECTED)
MM_BASE_BEARER_GET_CLASS (self)->load_connection_status (
self,
(GAsyncReadyCallback)load_connection_status_ready,
NULL);
return G_SOURCE_CONTINUE;
}
static gboolean
initial_connection_monitor_cb (MMBaseBearer *self)
{
if (self->priv->status == MM_BEARER_STATUS_CONNECTED)
MM_BASE_BEARER_GET_CLASS (self)->load_connection_status (
self,
(GAsyncReadyCallback)load_connection_status_ready,
NULL);
/* Add new monitor timeout at a higher rate */
self->priv->connection_monitor_id = g_timeout_add_seconds (BEARER_CONNECTION_MONITOR_TIMEOUT,
(GSourceFunc) connection_monitor_cb,
self);
/* Remove the initial connection monitor timeout as we added a new one */
return G_SOURCE_REMOVE;
}
static void
connection_monitor_start (MMBaseBearer *self)
{
/* If not implemented, don't schedule anything */
if (!MM_BASE_BEARER_GET_CLASS (self)->load_connection_status ||
!MM_BASE_BEARER_GET_CLASS (self)->load_connection_status_finish)
return;
if (self->priv->load_connection_status_unsupported)
return;
/* Schedule initial check */
g_assert (!self->priv->connection_monitor_id);
self->priv->connection_monitor_id = g_timeout_add_seconds (BEARER_CONNECTION_MONITOR_INITIAL_TIMEOUT,
(GSourceFunc) initial_connection_monitor_cb,
self);
}
/*****************************************************************************/
static void
bearer_update_connection_error (MMBaseBearer *self,
const GError *connection_error)
{
g_autoptr(GVariant) tuple = NULL;
if (connection_error) {
g_autoptr(GError) normalized_error = NULL;
/* Never overwrite a connection error if it's already set */
tuple = mm_gdbus_bearer_dup_connection_error (MM_GDBUS_BEARER (self));
if (tuple)
return;
/*
* Limit the type of errors we can expose in the interface;
* e.g. we don't want QMI or MBIM specific errors reported.
*/
normalized_error = mm_normalize_error (connection_error);
tuple = mm_common_error_to_tuple (normalized_error);
}
mm_gdbus_bearer_set_connection_error (MM_GDBUS_BEARER (self), tuple);
}
/*****************************************************************************/
static void
bearer_update_interface_stats (MMBaseBearer *self)
{
mm_gdbus_bearer_set_stats (
MM_GDBUS_BEARER (self),
mm_bearer_stats_get_dictionary (self->priv->stats));
}
static void
bearer_reset_ongoing_interface_stats (MMBaseBearer *self)
{
mm_bearer_stats_set_duration (self->priv->stats, 0);
mm_bearer_stats_set_tx_bytes (self->priv->stats, 0);
mm_bearer_stats_set_rx_bytes (self->priv->stats, 0);
mm_bearer_stats_set_start_date (self->priv->stats, 0);
mm_bearer_stats_set_uplink_speed (self->priv->stats, 0);
mm_bearer_stats_set_downlink_speed (self->priv->stats, 0);
bearer_update_interface_stats (self);
}
static void
bearer_set_ongoing_interface_stats (MMBaseBearer *self,
guint duration,
guint64 rx_bytes,
guint64 tx_bytes)
{
guint n_updates = 0;
/* Make sure we don't reset to 0 these values if we had ever set them
* before. Just ignore the update if we're reported 0 */
if (duration) {
gint delta_duration;
delta_duration = duration - mm_bearer_stats_get_duration (self->priv->stats);
if (delta_duration > 0) {
mm_bearer_stats_set_duration (self->priv->stats, duration);
mm_bearer_stats_set_total_duration (self->priv->stats,
mm_bearer_stats_get_total_duration (self->priv->stats) + delta_duration);
n_updates++;
}
}
if (rx_bytes) {
gint64 delta_rx_bytes;
delta_rx_bytes = rx_bytes - mm_bearer_stats_get_rx_bytes (self->priv->stats);
if (delta_rx_bytes > 0) {
mm_bearer_stats_set_rx_bytes (self->priv->stats, rx_bytes);
mm_bearer_stats_set_total_rx_bytes (self->priv->stats,
mm_bearer_stats_get_total_rx_bytes (self->priv->stats) + delta_rx_bytes);
n_updates++;
}
}
if (tx_bytes) {
gint64 delta_tx_bytes;
delta_tx_bytes = tx_bytes - mm_bearer_stats_get_tx_bytes (self->priv->stats);
if (delta_tx_bytes > 0) {
mm_bearer_stats_set_tx_bytes (self->priv->stats, tx_bytes);
mm_bearer_stats_set_total_tx_bytes (self->priv->stats,
mm_bearer_stats_get_total_tx_bytes (self->priv->stats) + delta_tx_bytes);
n_updates++;
}
}
if (n_updates)
bearer_update_interface_stats (self);
}
static void
bearer_stats_stop (MMBaseBearer *self)
{
if (self->priv->duration_timer) {
bearer_set_ongoing_interface_stats (self,
(guint64) g_timer_elapsed (self->priv->duration_timer, NULL),
0,
0);
g_timer_destroy (self->priv->duration_timer);
self->priv->duration_timer = NULL;
}
if (self->priv->stats_update_id) {
g_source_remove (self->priv->stats_update_id);
self->priv->stats_update_id = 0;
}
}
static void
reload_stats_ready (MMBaseBearer *self,
GAsyncResult *res)
{
GError *error = NULL;
guint64 rx_bytes = 0;
guint64 tx_bytes = 0;
if (!MM_BASE_BEARER_GET_CLASS (self)->reload_stats_finish (self, &rx_bytes, &tx_bytes, res, &error)) {
mm_obj_warn (self, "reloading stats failed: %s", error->message);
g_error_free (error);
return;
}
/* We only update stats if they were retrieved properly */
bearer_set_ongoing_interface_stats (self,
(guint32) g_timer_elapsed (self->priv->duration_timer, NULL),
rx_bytes,
tx_bytes);
}
static gboolean
stats_update_cb (MMBaseBearer *self)
{
/* Ignore stats update if we're not connected */
if (self->priv->status != MM_BEARER_STATUS_CONNECTED)
return G_SOURCE_CONTINUE;
/* If the implementation knows how to update stat values, run it */
if (self->priv->reload_stats_supported) {
MM_BASE_BEARER_GET_CLASS (self)->reload_stats (
self,
(GAsyncReadyCallback)reload_stats_ready,
NULL);
return G_SOURCE_CONTINUE;
}
/* Otherwise, just update duration and we're done */
bearer_set_ongoing_interface_stats (self,
(guint32) g_timer_elapsed (self->priv->duration_timer, NULL),
0,
0);
return G_SOURCE_CONTINUE;
}
static void
bearer_stats_start (MMBaseBearer *self,
guint64 uplink_speed,
guint64 downlink_speed)
{
/* Start duration timer */
g_assert (!self->priv->duration_timer);
self->priv->duration_timer = g_timer_new ();
/* Schedule */
g_assert (!self->priv->stats_update_id);
self->priv->stats_update_id = g_timeout_add_seconds (BEARER_STATS_UPDATE_TIMEOUT,
(GSourceFunc) stats_update_cb,
self);
mm_bearer_stats_set_start_date (self->priv->stats, (guint64)(g_get_real_time() / G_USEC_PER_SEC));
mm_bearer_stats_set_uplink_speed (self->priv->stats, uplink_speed);
mm_bearer_stats_set_downlink_speed (self->priv->stats, downlink_speed);
bearer_update_interface_stats (self);
/* Load initial values */
stats_update_cb (self);
}
/*****************************************************************************/
void
mm_base_bearer_report_speeds (MMBaseBearer *self,
guint64 uplink_speed,
guint64 downlink_speed)
{
/* Ignore speeds update if we're not connected */
if (self->priv->status != MM_BEARER_STATUS_CONNECTED)
return;
mm_bearer_stats_set_uplink_speed (self->priv->stats, uplink_speed);
mm_bearer_stats_set_downlink_speed (self->priv->stats, downlink_speed);
bearer_update_interface_stats (self);
}
/*****************************************************************************/
static void
dispatcher_connection_run_ready (MMDispatcherConnection *dispatcher,
GAsyncResult *res,
MMBaseBearer *self)
{
g_autoptr(GError) error = NULL;
if (!mm_dispatcher_connection_run_finish (dispatcher, res, &error))
mm_obj_warn (self, "errors detected in dispatcher: %s", error->message);
g_object_unref (self);
}
static void
bearer_run_dispatcher_scripts (MMBaseBearer *self,
MMDispatcherConnectionEvent event)
{
MMDispatcherConnection *dispatcher;
const gchar *interface;
interface = mm_gdbus_bearer_get_interface (MM_GDBUS_BEARER (self));
if (!self->priv->modem || !self->priv->path || !interface)
return;
dispatcher = mm_dispatcher_connection_get ();
mm_dispatcher_connection_run (dispatcher,
g_dbus_object_get_object_path (G_DBUS_OBJECT (self->priv->modem)),
self->priv->path,
interface,
event,
NULL, /* cancellable */
(GAsyncReadyCallback)dispatcher_connection_run_ready,
g_object_ref (self));
}
/*****************************************************************************/
static void
bearer_reset_interface_status (MMBaseBearer *self)
{
mm_gdbus_bearer_set_profile_id (MM_GDBUS_BEARER (self), MM_3GPP_PROFILE_ID_UNKNOWN);
mm_gdbus_bearer_set_multiplexed (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_connected (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_disconnect_request (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_suspended (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_interface (MM_GDBUS_BEARER (self), NULL);
mm_gdbus_bearer_set_ip4_config (
MM_GDBUS_BEARER (self),
mm_bearer_ip_config_get_dictionary (NULL));
mm_gdbus_bearer_set_ip6_config (
MM_GDBUS_BEARER (self),
mm_bearer_ip_config_get_dictionary (NULL));
}
static void
bearer_update_status (MMBaseBearer *self,
MMBearerStatus status)
{
/* NOTE: we do allow status 'CONNECTED' here; it may happen if we go into
* DISCONNECTING and we cannot disconnect */
/* Do nothing if the status is the same */
if (self->priv->status == status)
return;
/* Update the property value */
self->priv->status = status;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
/* Ensure that we don't expose any connection related data in the
* interface when going into disconnected state. */
if (self->priv->status == MM_BEARER_STATUS_DISCONNECTED) {
g_autoptr(GString) report = NULL;
/* Report disconnection via dispatcher scripts, before resetting the interface */
bearer_run_dispatcher_scripts (self, MM_DISPATCHER_CONNECTION_EVENT_DISCONNECTED);
bearer_reset_interface_status (self);
/* Cleanup flag to ignore disconnection reports */
self->priv->ignore_disconnection_reports = FALSE;
/* Stop statistics */
bearer_stats_stop (self);
/* Stop connection monitoring */
connection_monitor_stop (self);
/* Build and log report */
report = g_string_new (NULL);
g_string_append_printf (report,
"connection #%u finished: duration %us",
mm_bearer_stats_get_attempts (self->priv->stats),
mm_bearer_stats_get_duration (self->priv->stats));
if (self->priv->reload_stats_supported)
g_string_append_printf (report,
", tx: %" G_GUINT64_FORMAT " bytes, rx: %" G_GUINT64_FORMAT " bytes",
mm_bearer_stats_get_tx_bytes (self->priv->stats),
mm_bearer_stats_get_rx_bytes (self->priv->stats));
mm_obj_msg (self, "%s", report->str);
}
}
static void
bearer_update_status_connected (MMBaseBearer *self,
const gchar *interface,
gboolean multiplexed,
gint profile_id,
MMBearerIpConfig *ipv4_config,
MMBearerIpConfig *ipv6_config,
guint64 uplink_speed,
guint64 downlink_speed)
{
mm_gdbus_bearer_set_profile_id (MM_GDBUS_BEARER (self), profile_id);
mm_gdbus_bearer_set_multiplexed (MM_GDBUS_BEARER (self), multiplexed);
mm_gdbus_bearer_set_connected (MM_GDBUS_BEARER (self), TRUE);
mm_gdbus_bearer_set_suspended (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_interface (MM_GDBUS_BEARER (self), interface);
mm_gdbus_bearer_set_ip4_config (
MM_GDBUS_BEARER (self),
mm_bearer_ip_config_get_dictionary (ipv4_config));
mm_gdbus_bearer_set_ip6_config (
MM_GDBUS_BEARER (self),
mm_bearer_ip_config_get_dictionary (ipv6_config));
/* If PPP is involved in the requested IP config, we must ignore
* all disconnection reports found via CGACT? polling or CGEV URCs.
* In this case, upper layers should always explicitly disconnect
* the bearer when ownership of the TTY is given back to MM. */
if ((ipv4_config && mm_bearer_ip_config_get_method (ipv4_config) == MM_BEARER_IP_METHOD_PPP) ||
(ipv6_config && mm_bearer_ip_config_get_method (ipv6_config) == MM_BEARER_IP_METHOD_PPP)) {
mm_obj_dbg (self, "PPP is required for connection, will ignore disconnection reports");
self->priv->ignore_disconnection_reports = TRUE;
}
/* Update the property value */
self->priv->status = MM_BEARER_STATUS_CONNECTED;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
/* Start statistics */
bearer_stats_start (self, uplink_speed, downlink_speed);
/* Start connection monitor, if supported */
connection_monitor_start (self);
/* Run dispatcher scripts */
bearer_run_dispatcher_scripts (self, MM_DISPATCHER_CONNECTION_EVENT_CONNECTED);
}
/*****************************************************************************/
static void
reset_deferred_unregistration (MMBaseBearer *self)
{
if (self->priv->deferred_cdma_unregistration_id) {
g_source_remove (self->priv->deferred_cdma_unregistration_id);
self->priv->deferred_cdma_unregistration_id = 0;
}
if (self->priv->deferred_3gpp_unregistration_id) {
g_source_remove (self->priv->deferred_3gpp_unregistration_id);
self->priv->deferred_3gpp_unregistration_id = 0;
}
}
static gboolean
deferred_3gpp_unregistration_cb (MMBaseBearer *self)
{
g_warn_if_fail (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_UNREGISTERED);
self->priv->deferred_3gpp_unregistration_id = 0;
mm_obj_dbg (self, "forcing bearer disconnection, not registered in 3GPP network");
mm_base_bearer_disconnect_force (self);
return G_SOURCE_REMOVE;
}
static void
modem_3gpp_registration_state_changed (MMIfaceModem3gpp *modem,
GParamSpec *pspec,
MMBaseBearer *self)
{
MMModem3gppRegistrationState state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
g_object_get (modem,
MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, &state,
NULL);
switch (state) {
case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE:
case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED:
case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN:
self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_UNREGISTERED;
break;
case MM_MODEM_3GPP_REGISTRATION_STATE_HOME:
case MM_MODEM_3GPP_REGISTRATION_STATE_HOME_SMS_ONLY:
case MM_MODEM_3GPP_REGISTRATION_STATE_HOME_CSFB_NOT_PREFERRED:
case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING:
case MM_MODEM_3GPP_REGISTRATION_STATE_ATTACHED_RLOS:
self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE;
break;
case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY:
case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED:
if (mm_bearer_properties_get_allow_roaming (mm_base_bearer_peek_config (self)))
self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE;
else
self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_ROAMING;
break;
case MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY:
self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_EMERGENCY_ONLY;
break;
default:
g_assert_not_reached ();
}
/* If no reason to disconnect, or if it's a mixed CDMA+LTE modem without a CDMA reason,
* just don't do anything. */
if (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_NONE ||
(mm_iface_modem_is_cdma (MM_IFACE_MODEM (modem)) &&
self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_NONE)) {
reset_deferred_unregistration (self);
return;
}
/* Modem is roaming and roaming not allowed, report right away */
if (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_ROAMING) {
mm_obj_dbg (self, "bearer not allowed to connect, registered in roaming 3GPP network");
reset_deferred_unregistration (self);
mm_base_bearer_disconnect_force (self);
return;
}
/* Modem is registered under emergency services only? */
if (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_EMERGENCY_ONLY) {
mm_obj_dbg (self, "bearer not allowed to connect, emergency services only");
reset_deferred_unregistration (self);
mm_base_bearer_disconnect_force (self);
return;
}
/* Modem reports being unregistered */
if (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_UNREGISTERED) {
/* If there is already a notification pending, just return */
if (self->priv->deferred_3gpp_unregistration_id)
return;
/* If the bearer is not connected, report right away */
if (self->priv->status != MM_BEARER_STATUS_CONNECTED) {
mm_obj_dbg (self, "bearer not allowed to connect, not registered in 3GPP network");
mm_base_bearer_disconnect_force (self);
return;
}
/* Otherwise, setup the new timeout */
mm_obj_dbg (self, "connected bearer not registered in 3GPP network");
self->priv->deferred_3gpp_unregistration_id =
g_timeout_add_seconds (BEARER_DEFERRED_UNREGISTRATION_TIMEOUT,
(GSourceFunc) deferred_3gpp_unregistration_cb,
self);
return;
}
g_assert_not_reached ();
}
static gboolean
deferred_cdma_unregistration_cb (MMBaseBearer *self)
{
g_warn_if_fail (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_UNREGISTERED);
self->priv->deferred_cdma_unregistration_id = 0;
mm_obj_dbg (self, "forcing bearer disconnection, not registered in CDMA network");
mm_base_bearer_disconnect_force (self);
return G_SOURCE_REMOVE;
}
static void
modem_cdma_registration_state_changed (MMIfaceModemCdma *modem,
GParamSpec *pspec,
MMBaseBearer *self)
{
MMModemCdmaRegistrationState cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
g_object_get (modem,
MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE, &cdma1x_state,
MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, &evdo_state,
NULL);
if (cdma1x_state == MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING ||
evdo_state == MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING) {
if (mm_bearer_properties_get_allow_roaming (mm_base_bearer_peek_config (self)))
self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE;
else
self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_ROAMING;
} else if (cdma1x_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN ||
evdo_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE;
} else {
self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_UNREGISTERED;
}
/* If no reason to disconnect, or if it's a mixed CDMA+LTE modem without a 3GPP reason,
* just don't do anything. */
if (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_NONE ||
(mm_iface_modem_is_3gpp (MM_IFACE_MODEM (modem)) &&
self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_NONE)) {
reset_deferred_unregistration (self);
return;
}
/* Modem is roaming and roaming not allowed, report right away */
if (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_ROAMING) {
mm_obj_dbg (self, "bearer not allowed to connect, registered in roaming CDMA network");
reset_deferred_unregistration (self);
mm_base_bearer_disconnect_force (self);
return;
}
/* Modem reports being unregistered */
if (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_UNREGISTERED) {
/* If there is already a notification pending, just return */
if (self->priv->deferred_cdma_unregistration_id)
return;
/* If the bearer is not connected, report right away */
if (self->priv->status != MM_BEARER_STATUS_CONNECTED) {
mm_obj_dbg (self, "bearer not allowed to connect, not registered in CDMA network");
mm_base_bearer_disconnect_force (self);
return;
}
/* Otherwise, setup the new timeout */
mm_obj_dbg (self, "connected bearer not registered in CDMA network");
self->priv->deferred_cdma_unregistration_id =
g_timeout_add_seconds (BEARER_DEFERRED_UNREGISTRATION_TIMEOUT,
(GSourceFunc) deferred_cdma_unregistration_cb,
self);
return;
}
g_assert_not_reached ();
}
static void
set_signal_handlers (MMBaseBearer *self)
{
g_assert (self->priv->modem != NULL);
g_assert (self->priv->config != NULL);
/* Don't set the 3GPP registration change signal handlers if they
* are already set. */
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self->priv->modem)) &&
!self->priv->id_3gpp_registration_change) {
self->priv->id_3gpp_registration_change =
g_signal_connect (self->priv->modem,
"notify::" MM_IFACE_MODEM_3GPP_REGISTRATION_STATE,
G_CALLBACK (modem_3gpp_registration_state_changed),
self);
modem_3gpp_registration_state_changed (MM_IFACE_MODEM_3GPP (self->priv->modem), NULL, self);
}
/* Don't set the CDMA1x/EV-DO registration change signal handlers if they
* are already set. */
if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self->priv->modem)) &&
!self->priv->id_cdma1x_registration_change &&
!self->priv->id_evdo_registration_change) {
self->priv->id_cdma1x_registration_change =
g_signal_connect (self->priv->modem,
"notify::" MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE,
G_CALLBACK (modem_cdma_registration_state_changed),
self);
self->priv->id_evdo_registration_change =
g_signal_connect (self->priv->modem,
"notify::" MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE,
G_CALLBACK (modem_cdma_registration_state_changed),
self);
modem_cdma_registration_state_changed (MM_IFACE_MODEM_CDMA (self->priv->modem), NULL, self);
}
}
static void
reset_signal_handlers (MMBaseBearer *self)
{
if (!self->priv->modem)
return;
if (self->priv->id_3gpp_registration_change) {
if (g_signal_handler_is_connected (self->priv->modem, self->priv->id_3gpp_registration_change))
g_signal_handler_disconnect (self->priv->modem, self->priv->id_3gpp_registration_change);
self->priv->id_3gpp_registration_change = 0;
}
if (self->priv->id_cdma1x_registration_change) {
if (g_signal_handler_is_connected (self->priv->modem, self->priv->id_cdma1x_registration_change))
g_signal_handler_disconnect (self->priv->modem, self->priv->id_cdma1x_registration_change);
self->priv->id_cdma1x_registration_change = 0;
}
if (self->priv->id_evdo_registration_change) {
if (g_signal_handler_is_connected (self->priv->modem, self->priv->id_evdo_registration_change))
g_signal_handler_disconnect (self->priv->modem, self->priv->id_evdo_registration_change);
self->priv->id_evdo_registration_change = 0;
}
}
/*****************************************************************************/
/* CONNECT */
gboolean
mm_base_bearer_connect_finish (MMBaseBearer *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
connect_succeeded (MMBaseBearer *self,
GTask *task)
{
MMBearerConnectResult *result;
result = g_task_get_task_data (task);
/* Update bearer and interface status */
bearer_update_status_connected (
self,
mm_port_get_device (mm_bearer_connect_result_peek_data (result)),
mm_bearer_connect_result_get_multiplexed (result),
mm_bearer_connect_result_get_profile_id (result),
mm_bearer_connect_result_peek_ipv4_config (result),
mm_bearer_connect_result_peek_ipv6_config (result),
mm_bearer_connect_result_get_uplink_speed (result),
mm_bearer_connect_result_get_downlink_speed (result));
g_clear_object (&self->priv->connect_cancellable);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
disconnect_after_cancel_ready (MMBaseBearer *self,
GAsyncResult *res)
{
g_autoptr(GError) error = NULL;
if (!MM_BASE_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error))
mm_obj_warn (self, "error disconnecting: %s; will assume disconnected anyway", error->message);
else
mm_obj_dbg (self, "disconnected bearer '%s'", self->priv->path);
/* Report disconnection to the bearer object using class method
* mm_bearer_report_connection_status. This gives subclass implementations a
* chance to correctly update their own connection state, in case this base
* class ignores a failed disconnection attempt.
*/
mm_base_bearer_report_connection_status (self, MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
}
static void
connect_failed (MMBaseBearer *self,
GTask *task,
GError *error)
{
/* Update failed attempts */
mm_bearer_stats_set_failed_attempts (self->priv->stats,
mm_bearer_stats_get_failed_attempts (self->priv->stats) + 1);
bearer_update_interface_stats (self);
/* Update reported connection error before the status update */
bearer_update_connection_error (self, error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
MM_BASE_BEARER_GET_CLASS (self)->disconnect (self,
(GAsyncReadyCallback)disconnect_after_cancel_ready,
NULL);
} else
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
g_clear_object (&self->priv->connect_cancellable);
g_task_return_error (task, error);
g_object_unref (task);
}
static gboolean
connect_check_cancel (MMBaseBearer *self,
GTask *task)
{
GError *error = NULL;
if (!g_cancellable_is_cancelled (self->priv->connect_cancellable))
return FALSE;
mm_obj_dbg (self, "connected, but need to disconnect");
error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED,
"Bearer got connected, but had to disconnect after cancellation request");
connect_failed (self, task, error);
return TRUE;
}
static void
reload_stats_supported_ready (MMBaseBearer *self,
GAsyncResult *res,
GTask *task)
{
if (MM_BASE_BEARER_GET_CLASS (self)->reload_stats_finish (self, NULL, NULL, res, NULL)) {
mm_obj_dbg (self, "reloading stats is supported by the device");
self->priv->reload_stats_supported = TRUE;
mm_gdbus_bearer_set_reload_stats_supported (MM_GDBUS_BEARER (self), self->priv->reload_stats_supported);
} else
mm_obj_dbg (self, "reloading stats is not supported by the device");
if (connect_check_cancel (self, task))
return;
connect_succeeded (self, task);
}
static void
connect_ready (MMBaseBearer *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
g_autoptr(MMBearerConnectResult) result = NULL;
/* NOTE: connect() implementations *MUST* handle cancellations themselves */
result = MM_BASE_BEARER_GET_CLASS (self)->connect_finish (self, res, &error);
if (!result) {
mm_obj_warn (self, "connection attempt #%u failed: %s",
mm_bearer_stats_get_attempts (self->priv->stats),
error->message);
/* process profile manager updates right away on error */
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self->priv->modem)))
mm_iface_modem_3gpp_profile_manager_update_ignore_stop (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self->priv->modem));
connect_failed (self, task, error);
return;
}
/* delay processing profile manager updates on success */
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self->priv->modem)))
mm_iface_modem_3gpp_profile_manager_update_ignore_stop_delayed (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self->priv->modem));
/* Handle cancellations detected after successful connection */
if (connect_check_cancel (self, task))
return;
mm_obj_dbg (self, "connected");
g_task_set_task_data (task, g_steal_pointer (&result), (GDestroyNotify)mm_bearer_connect_result_unref);
/* Check that reload statistics is supported by the device; we can only do this while
* connected. */
if (MM_BASE_BEARER_GET_CLASS (self)->reload_stats &&
MM_BASE_BEARER_GET_CLASS (self)->reload_stats_finish) {
MM_BASE_BEARER_GET_CLASS (self)->reload_stats (
self,
(GAsyncReadyCallback)reload_stats_supported_ready,
task);
return;
}
connect_succeeded (self, task);
}
void
mm_base_bearer_connect (MMBaseBearer *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
if (!MM_BASE_BEARER_GET_CLASS (self)->connect) {
g_assert (!MM_BASE_BEARER_GET_CLASS (self)->connect_finish);
g_task_report_new_error (
self,
callback,
user_data,
mm_base_bearer_connect,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Bearer doesn't allow explicit connection requests");
return;
}
/* If already connecting, return error, don't allow a second request. */
if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
g_task_report_new_error (
self,
callback,
user_data,
mm_base_bearer_connect,
MM_CORE_ERROR,
MM_CORE_ERROR_IN_PROGRESS,
"Bearer already being connected");
return;
}
/* If currently disconnecting, return error, previous operation should
* finish before allowing to connect again. */
if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING) {
g_task_report_new_error (
self,
callback,
user_data,
mm_base_bearer_connect,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Bearer currently being disconnected");
return;
}
/* Check 3GPP roaming allowance, *only* roaming related here */
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self->priv->modem)) &&
self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_ROAMING) {
g_task_report_new_error (
self,
callback,
user_data,
mm_base_bearer_connect,
MM_CORE_ERROR,
MM_CORE_ERROR_UNAUTHORIZED,
"Not allowed to connect bearer in 3GPP network: '%s'",
connection_forbidden_reason_str[self->priv->reason_3gpp]);
return;
}
/* Check CDMA roaming allowance, *only* roaming related here */
if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self->priv->modem)) &&
self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_ROAMING) {
g_task_report_new_error (
self,
callback,
user_data,
mm_base_bearer_connect,
MM_CORE_ERROR,
MM_CORE_ERROR_UNAUTHORIZED,
"Not allowed to connect bearer in CDMA network: '%s'",
connection_forbidden_reason_str[self->priv->reason_cdma]);
return;
}
task = g_task_new (self, NULL, callback, user_data);
/* If already connected, done */
if (self->priv->status == MM_BEARER_STATUS_CONNECTED) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
/* Update total attempts */
mm_bearer_stats_set_attempts (self->priv->stats,
mm_bearer_stats_get_attempts (self->priv->stats) + 1);
bearer_reset_ongoing_interface_stats (self);
/* Clear previous connection error, if any */
bearer_update_connection_error (self, NULL);
/* The connect request may imply a profile update internally, so ignore it */
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self->priv->modem)))
mm_iface_modem_3gpp_profile_manager_update_ignore_start (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self->priv->modem));
/* Connecting! */
mm_obj_dbg (self, "connecting...");
self->priv->connect_cancellable = g_cancellable_new ();
bearer_update_status (self, MM_BEARER_STATUS_CONNECTING);
MM_BASE_BEARER_GET_CLASS (self)->connect (
self,
self->priv->connect_cancellable,
(GAsyncReadyCallback)connect_ready,
task);
}
typedef struct {
MMBaseBearer *self;
GDBusMethodInvocation *invocation;
} HandleConnectContext;
static void
handle_connect_context_free (HandleConnectContext *ctx)
{
g_object_unref (ctx->invocation);
g_object_unref (ctx->self);
g_free (ctx);
}
static void
handle_connect_ready (MMBaseBearer *self,
GAsyncResult *res,
HandleConnectContext *ctx)
{
GError *error = NULL;
if (!mm_base_bearer_connect_finish (self, res, &error))
mm_dbus_method_invocation_take_error (ctx->invocation, error);
else
mm_gdbus_bearer_complete_connect (MM_GDBUS_BEARER (self), ctx->invocation);
handle_connect_context_free (ctx);
}
static void
handle_connect_auth_ready (MMAuthProvider *authp,
GAsyncResult *res,
HandleConnectContext *ctx)
{
GError *error = NULL;
if (!mm_auth_provider_authorize_finish (authp, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_connect_context_free (ctx);
return;
}
mm_obj_info (ctx->self, "processing user request to connect...");
mm_base_bearer_connect (ctx->self,
(GAsyncReadyCallback)handle_connect_ready,
ctx);
}
static gboolean
handle_connect (MMBaseBearer *self,
GDBusMethodInvocation *invocation)
{
HandleConnectContext *ctx;
ctx = g_new0 (HandleConnectContext, 1);
ctx->self = g_object_ref (self);
ctx->invocation = g_object_ref (invocation);
mm_auth_provider_authorize (self->priv->authp,
invocation,
MM_AUTHORIZATION_DEVICE_CONTROL,
self->priv->authp_cancellable,
(GAsyncReadyCallback)handle_connect_auth_ready,
ctx);
return TRUE;
}
/*****************************************************************************/
/* DISCONNECT */
gboolean
mm_base_bearer_disconnect_finish (MMBaseBearer *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
disconnect_ready (MMBaseBearer *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!MM_BASE_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
mm_obj_dbg (self, "couldn't disconnect: %s", error->message);
bearer_update_status (self, MM_BEARER_STATUS_CONNECTED);
g_task_return_error (task, error);
}
else {
mm_obj_dbg (self, "disconnected");
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
g_task_return_boolean (task, TRUE);
}
g_object_unref (task);
}
static void
status_changed_complete_disconnect (MMBaseBearer *self,
GParamSpec *pspec,
GTask *task)
{
/* We may get other states here before DISCONNECTED, like DISCONNECTING or
* even CONNECTED. */
if (self->priv->status != MM_BEARER_STATUS_DISCONNECTED)
return;
mm_obj_dbg (self, "disconnected after cancelling previous connect request");
g_signal_handler_disconnect (self,
self->priv->disconnect_signal_handler);
self->priv->disconnect_signal_handler = 0;
/* Note: interface state is updated when the DISCONNECTED state is set */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
void
mm_base_bearer_disconnect (MMBaseBearer *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (!MM_BASE_BEARER_GET_CLASS (self)->disconnect) {
g_assert (!MM_BASE_BEARER_GET_CLASS (self)->disconnect_finish);
g_task_return_new_error (
task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Bearer doesn't allow explicit disconnection requests");
g_object_unref (task);
return;
}
/* If already disconnected, done */
if (self->priv->status == MM_BEARER_STATUS_DISCONNECTED) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
/* If already disconnecting, return error, don't allow a second request. */
if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING) {
g_task_return_new_error (
task,
MM_CORE_ERROR,
MM_CORE_ERROR_IN_PROGRESS,
"Bearer already being disconnected");
g_object_unref (task);
return;
}
/* If currently connecting, try to cancel that operation, and wait to get
* disconnected. */
if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
/* Set ourselves as disconnecting */
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
/* We MUST ensure that we get to DISCONNECTED */
g_cancellable_cancel (self->priv->connect_cancellable);
/* Note that we only allow to remove disconnected bearers, so should
* be safe to assume that we'll get the signal handler called properly
*/
self->priv->disconnect_signal_handler =
g_signal_connect (self,
"notify::" MM_BASE_BEARER_STATUS,
(GCallback)status_changed_complete_disconnect,
task); /* takes ownership */
return;
}
/* Disconnecting! */
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
MM_BASE_BEARER_GET_CLASS (self)->disconnect (
self,
(GAsyncReadyCallback)disconnect_ready,
task); /* takes ownership */
}
typedef struct {
MMBaseBearer *self;
GDBusMethodInvocation *invocation;
} HandleDisconnectContext;
static void
handle_disconnect_context_free (HandleDisconnectContext *ctx)
{
g_object_unref (ctx->invocation);
g_object_unref (ctx->self);
g_free (ctx);
}
static void
handle_disconnect_ready (MMBaseBearer *self,
GAsyncResult *res,
HandleDisconnectContext *ctx)
{
GError *error = NULL;
if (!mm_base_bearer_disconnect_finish (self, res, &error))
mm_dbus_method_invocation_take_error (ctx->invocation, error);
else
mm_gdbus_bearer_complete_disconnect (MM_GDBUS_BEARER (self), ctx->invocation);
handle_disconnect_context_free (ctx);
}
static void
handle_disconnect_auth_ready (MMAuthProvider *authp,
GAsyncResult *res,
HandleDisconnectContext *ctx)
{
GError *error = NULL;
if (!mm_auth_provider_authorize_finish (authp, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_disconnect_context_free (ctx);
return;
}
mm_obj_info (ctx->self, "processing user request to disconnect...");
mm_base_bearer_disconnect (ctx->self,
(GAsyncReadyCallback)handle_disconnect_ready,
ctx);
}
static gboolean
handle_disconnect (MMBaseBearer *self,
GDBusMethodInvocation *invocation)
{
HandleDisconnectContext *ctx;
ctx = g_new0 (HandleDisconnectContext, 1);
ctx->self = g_object_ref (self);
ctx->invocation = g_object_ref (invocation);
mm_auth_provider_authorize (self->priv->authp,
invocation,
MM_AUTHORIZATION_DEVICE_CONTROL,
self->priv->authp_cancellable,
(GAsyncReadyCallback)handle_disconnect_auth_ready,
ctx);
return TRUE;
}
/*****************************************************************************/
static void
base_bearer_dbus_export (MMBaseBearer *self)
{
GError *error = NULL;
/* Handle method invocations */
g_signal_connect (self,
"handle-connect",
G_CALLBACK (handle_connect),
NULL);
g_signal_connect (self,
"handle-disconnect",
G_CALLBACK (handle_disconnect),
NULL);
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self),
self->priv->connection,
self->priv->path,
&error)) {
mm_obj_warn (self, "couldn't export to bus: %s", error->message);
g_error_free (error);
}
}
static void
base_bearer_dbus_unexport (MMBaseBearer *self)
{
const gchar *path;
path = g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self));
/* Only unexport if currently exported */
if (path) {
mm_obj_dbg (self, "removing from bus");
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self));
}
}
/*****************************************************************************/
MMBearerStatus
mm_base_bearer_get_status (MMBaseBearer *self)
{
return self->priv->status;
}
const gchar *
mm_base_bearer_get_path (MMBaseBearer *self)
{
return self->priv->path;
}
MMBearerProperties *
mm_base_bearer_peek_config (MMBaseBearer *self)
{
return self->priv->config;
}
MMBearerProperties *
mm_base_bearer_get_config (MMBaseBearer *self)
{
return (self->priv->config ?
g_object_ref (self->priv->config) :
NULL);
}
gint
mm_base_bearer_get_profile_id (MMBaseBearer *self)
{
return mm_gdbus_bearer_get_profile_id (MM_GDBUS_BEARER (self));
}
MMBearerApnType
mm_base_bearer_get_apn_type (MMBaseBearer *self)
{
/* when none explicitly requested, apn type always defaults to internet */
return (self->priv->config ?
mm_bearer_properties_get_apn_type (self->priv->config) :
MM_BEARER_APN_TYPE_DEFAULT);
}
/*****************************************************************************/
static void
disconnect_force_ready (MMBaseBearer *self,
GAsyncResult *res)
{
GError *error = NULL;
if (!MM_BASE_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
mm_obj_warn (self, "error disconnecting: %s; will assume disconnected anyway", error->message);
g_error_free (error);
}
else
mm_obj_dbg (self, "disconnected");
/* Report disconnection to the bearer object using class method
* mm_bearer_report_connection_status. This gives subclass implementations a
* chance to correctly update their own connection state, in case this base
* class ignores a failed disconnection attempt.
*/
mm_base_bearer_report_connection_status (self, MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
}
void
mm_base_bearer_disconnect_force (MMBaseBearer *self)
{
if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING ||
self->priv->status == MM_BEARER_STATUS_DISCONNECTED)
return;
if (self->priv->ignore_disconnection_reports) {
mm_obj_msg (self, "disconnection should be forced, but we can't. Request disconnection instead.");
mm_gdbus_bearer_set_disconnect_request (MM_GDBUS_BEARER(self), TRUE);
bearer_run_dispatcher_scripts (self,
MM_DISPATCHER_CONNECTION_EVENT_DISCONNECT_REQUEST);
return;
}
mm_obj_msg (self, "forcing disconnection");
/* If currently connecting, try to cancel that operation. */
if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
g_cancellable_cancel (self->priv->connect_cancellable);
return;
}
/* Disconnecting! */
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
MM_BASE_BEARER_GET_CLASS (self)->disconnect (
self,
(GAsyncReadyCallback)disconnect_force_ready,
NULL);
}
/*****************************************************************************/
static void
report_connection_status (MMBaseBearer *self,
MMBearerConnectionStatus status,
const GError *connection_error)
{
/* The only status expected at this point is DISCONNECTED or CONNECTED,
* although here we just process the DISCONNECTED one.
*/
g_assert (status == MM_BEARER_CONNECTION_STATUS_CONNECTED || status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
/* In the generic bearer implementation we just need to reset the
* interface status */
if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) {
bearer_update_connection_error (self, connection_error);
bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
}
}
/*
* This method is used exclusively in two different scenarios:
* a) to report disconnections detected by ModemManager itself (e.g. based on
* CGACT polling or CGEV URCs), applicable to bearers using both NET and
* PPP data ports.
* b) to report failed or successful connection attempts by plugins using NET
* data ports that rely on vendor-specific URCs (e.g. Icera, MBM, Option
* HSO).
*
* The method is also subclass-able because plugins may require specific
* cleanup operations to be done when a bearer is reported as disconnected.
* (e.g. the QMI or MBIM implementations require removing signal handlers).
*
* For all the scenarios involving a) the plugins are required to call the
* parent report_connection_status() implementation to report the
* DISCONNECTED state. For scenarios involving b) the parent reporting is not
* expected at all. In other words, the parent report_connection_status()
* is exclusively used in processing disconnections detected by ModemManager
* itself.
*
* If the bearer has been connected and it has required PPP method, we will
* ignore all disconnection reports because we cannot disconnect a PPP-based
* bearer before the upper layers have stopped using the TTY. In this case,
* we must wait for upper layers to detect the disconnection themselves (e.g.
* pppd should detect it) and disconnect the bearer through DBus.
*/
void
mm_base_bearer_report_connection_status_detailed (MMBaseBearer *self,
MMBearerConnectionStatus status,
const GError *connection_error)
{
/* Reporting disconnection? */
if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED || status == MM_BEARER_CONNECTION_STATUS_CONNECTION_FAILED) {
if (self->priv->ignore_disconnection_reports) {
mm_obj_dbg (self, "ignoring disconnection report");
return;
}
/* Setup a generic default error if none explicitly given when reporting
* bearer disconnections. */
if (!connection_error) {
g_autoptr(GError) default_connection_error = NULL;
default_connection_error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, self);
return MM_BASE_BEARER_GET_CLASS (self)->report_connection_status (self, status, default_connection_error);
}
}
return MM_BASE_BEARER_GET_CLASS (self)->report_connection_status (self, status, connection_error);
}
/*****************************************************************************/
#if defined WITH_SUSPEND_RESUME
typedef struct _SyncingContext SyncingContext;
static void interface_syncing_step (GTask *task);
typedef enum {
SYNCING_STEP_FIRST,
SYNCING_STEP_REFRESH_CONNECTION,
SYNCING_STEP_LAST
} SyncingStep;
struct _SyncingContext {
SyncingStep step;
MMBearerStatus status;
};
gboolean
mm_base_bearer_sync_finish (MMBaseBearer *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
reload_connection_status_ready (MMBaseBearer *self,
GAsyncResult *res,
GTask *task)
{
SyncingContext *ctx;
MMBearerConnectionStatus reloaded_status;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
/* The only update we're really interested in is the connected->disconnected
* one, because any other would be extremely strange and it's probably not
* worth trying to support those; e.g. a disconnected->connected change here
* would be impossible to be handled correctly. We'll also ignore intermediate
* states (connecting/disconnecting), as we can rely on the reports of the final
* state at some point soon.
*
* So, just handle DISCONNECTED at this point.
*/
reloaded_status = MM_BASE_BEARER_GET_CLASS (self)->reload_connection_status_finish (self, res, &error);
if (reloaded_status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
mm_obj_warn (self, "reloading connection status failed: %s", error->message);
else if ((ctx->status == MM_BEARER_STATUS_CONNECTED) &&
(reloaded_status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED)) {
mm_obj_dbg (self, "disconnection detected during status synchronization");
mm_base_bearer_report_connection_status (self, reloaded_status);
}
/* Go on to the next step */
ctx->step++;
interface_syncing_step (task);
}
static void
interface_syncing_step (GTask *task)
{
MMBaseBearer *self;
SyncingContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case SYNCING_STEP_FIRST:
ctx->step++;
/* fall through */
case SYNCING_STEP_REFRESH_CONNECTION:
/*
* AT+PPP based connections should not be synced.
* When a AT+PPP connection bearer is connected, the 'ignore_disconnection_reports' flag is set.
*/
if (!self->priv->ignore_disconnection_reports) {
if (!MM_BASE_BEARER_GET_CLASS (self)->reload_connection_status)
mm_obj_warn (self, "unable to reload connection status, method not implemented");
else {
mm_obj_dbg (self, "refreshing connection status");
MM_BASE_BEARER_GET_CLASS (self)->reload_connection_status (self,
(GAsyncReadyCallback) reload_connection_status_ready,
task);
return;
}
}
ctx->step++;
/* fall through */
case SYNCING_STEP_LAST:
/* We are done without errors! */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
break;
}
g_assert_not_reached ();
}
void
mm_base_bearer_sync (MMBaseBearer *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
SyncingContext *ctx;
GTask *task;
/* Create SyncingContext and store the original bearer status */
ctx = g_new0 (SyncingContext, 1);
ctx->step = SYNCING_STEP_FIRST;
ctx->status = self->priv->status;
/* Create sync steps task and execute it */
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)g_free);
interface_syncing_step (task);
}
#endif
/*****************************************************************************/
static gchar *
log_object_build_id (MMLogObject *_self)
{
MMBaseBearer *self;
self = MM_BASE_BEARER (_self);
return g_strdup_printf ("bearer%u", self->priv->dbus_id);
}
/*****************************************************************************/
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MMBaseBearer *self = MM_BASE_BEARER (object);
switch (prop_id) {
case PROP_PATH:
g_free (self->priv->path);
self->priv->path = g_value_dup_string (value);
/* Export when we get a DBus connection AND we have a path */
if (self->priv->path &&
self->priv->connection)
base_bearer_dbus_export (self);
break;
case PROP_CONNECTION:
g_clear_object (&self->priv->connection);
self->priv->connection = g_value_dup_object (value);
/* Export when we get a DBus connection AND we have a path */
if (!self->priv->connection)
base_bearer_dbus_unexport (self);
else if (self->priv->path)
base_bearer_dbus_export (self);
break;
case PROP_BIND_TO:
g_clear_object (&self->priv->bind_to);
self->priv->bind_to = g_value_dup_object (value);
mm_bind_to (MM_BIND (self), MM_BASE_BEARER_CONNECTION, self->priv->bind_to);
break;
case PROP_MODEM:
g_clear_object (&self->priv->modem);
self->priv->modem = g_value_dup_object (value);
if (self->priv->modem) {
if (self->priv->config) {
/* Listen to 3GPP/CDMA registration state changes. We need both
* 'config' and 'modem' set. */
set_signal_handlers (self);
}
}
break;
case PROP_STATUS:
/* We don't allow g_object_set()-ing the status property */
g_assert_not_reached ();
break;
case PROP_CONFIG: {
GVariant *dictionary;
g_clear_object (&self->priv->config);
self->priv->config = g_value_dup_object (value);
if (self->priv->modem) {
/* Listen to 3GPP/CDMA registration state changes. We need both
* 'config' and 'modem' set. */
set_signal_handlers (self);
}
/* Also expose the properties */
dictionary = mm_bearer_properties_get_dictionary (self->priv->config);
mm_gdbus_bearer_set_properties (MM_GDBUS_BEARER (self), dictionary);
if (dictionary)
g_variant_unref (dictionary);
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)
{
MMBaseBearer *self = MM_BASE_BEARER (object);
switch (prop_id) {
case PROP_PATH:
g_value_set_string (value, self->priv->path);
break;
case PROP_CONNECTION:
g_value_set_object (value, self->priv->connection);
break;
case PROP_BIND_TO:
g_value_set_object (value, self->priv->bind_to);
break;
case PROP_MODEM:
g_value_set_object (value, self->priv->modem);
break;
case PROP_STATUS:
g_value_set_enum (value, self->priv->status);
break;
case PROP_CONFIG:
g_value_set_object (value, self->priv->config);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
mm_base_bearer_init (MMBaseBearer *self)
{
static guint id = 0;
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
MM_TYPE_BASE_BEARER,
MMBaseBearerPrivate);
/* Each bearer is given a unique id to build its own DBus path */
self->priv->dbus_id = id++;
/* Setup authorization provider */
self->priv->authp = mm_auth_provider_get ();
self->priv->authp_cancellable = g_cancellable_new ();
self->priv->status = MM_BEARER_STATUS_DISCONNECTED;
self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE;
self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE;
self->priv->reload_stats_supported = FALSE;
self->priv->stats = mm_bearer_stats_new ();
/* Set defaults */
mm_gdbus_bearer_set_interface (MM_GDBUS_BEARER (self), NULL);
mm_gdbus_bearer_set_multiplexed (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_profile_id (MM_GDBUS_BEARER (self), MM_3GPP_PROFILE_ID_UNKNOWN);
mm_gdbus_bearer_set_connected (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_suspended (MM_GDBUS_BEARER (self), FALSE);
mm_gdbus_bearer_set_properties (MM_GDBUS_BEARER (self), NULL);
mm_gdbus_bearer_set_ip_timeout (MM_GDBUS_BEARER (self), BEARER_IP_TIMEOUT_DEFAULT);
mm_gdbus_bearer_set_bearer_type (MM_GDBUS_BEARER (self), MM_BEARER_TYPE_DEFAULT);
mm_gdbus_bearer_set_ip4_config (MM_GDBUS_BEARER (self),
mm_bearer_ip_config_get_dictionary (NULL));
mm_gdbus_bearer_set_ip6_config (MM_GDBUS_BEARER (self),
mm_bearer_ip_config_get_dictionary (NULL));
bearer_update_interface_stats (self);
}
static void
finalize (GObject *object)
{
MMBaseBearer *self = MM_BASE_BEARER (object);
g_free (self->priv->path);
G_OBJECT_CLASS (mm_base_bearer_parent_class)->finalize (object);
}
static void
dispose (GObject *object)
{
MMBaseBearer *self = MM_BASE_BEARER (object);
connection_monitor_stop (self);
bearer_stats_stop (self);
g_clear_object (&self->priv->stats);
if (self->priv->connection) {
base_bearer_dbus_unexport (self);
g_clear_object (&self->priv->connection);
}
reset_signal_handlers (self);
reset_deferred_unregistration (self);
g_clear_object (&self->priv->modem);
g_clear_object (&self->priv->bind_to);
g_clear_object (&self->priv->config);
g_cancellable_cancel (self->priv->authp_cancellable);
g_clear_object (&self->priv->authp_cancellable);
G_OBJECT_CLASS (mm_base_bearer_parent_class)->dispose (object);
}
static void
log_object_iface_init (MMLogObjectInterface *iface)
{
iface->build_id = log_object_build_id;
}
static void
bind_iface_init (MMBindInterface *iface)
{
}
static void
mm_base_bearer_class_init (MMBaseBearerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMBaseBearerPrivate));
/* Virtual methods */
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
object_class->dispose = dispose;
klass->report_connection_status = report_connection_status;
properties[PROP_CONNECTION] =
g_param_spec_object (MM_BASE_BEARER_CONNECTION,
"Connection",
"GDBus connection to the system bus.",
G_TYPE_DBUS_CONNECTION,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]);
properties[PROP_PATH] =
g_param_spec_string (MM_BASE_BEARER_PATH,
"Path",
"DBus path of the Bearer",
NULL,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]);
g_object_class_override_property (object_class, PROP_BIND_TO, MM_BIND_TO);
properties[PROP_MODEM] =
g_param_spec_object (MM_BASE_BEARER_MODEM,
"Modem",
"The Modem which owns this Bearer",
MM_TYPE_BASE_MODEM,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
properties[PROP_STATUS] =
g_param_spec_enum (MM_BASE_BEARER_STATUS,
"Bearer status",
"Status of the bearer",
MM_TYPE_BEARER_STATUS,
MM_BEARER_STATUS_DISCONNECTED,
G_PARAM_READABLE);
g_object_class_install_property (object_class, PROP_STATUS, properties[PROP_STATUS]);
properties[PROP_CONFIG] =
g_param_spec_object (MM_BASE_BEARER_CONFIG,
"Bearer configuration",
"List of user provided properties",
MM_TYPE_BEARER_PROPERTIES,
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_CONFIG, properties[PROP_CONFIG]);
}
/*****************************************************************************/
/* Helpers to implement connect() */
struct _MMBearerConnectResult {
volatile gint ref_count;
MMPort *data;
MMBearerIpConfig *ipv4_config;
MMBearerIpConfig *ipv6_config;
gboolean multiplexed;
gint profile_id;
guint64 uplink_speed;
guint64 downlink_speed;
};
MMBearerConnectResult *
mm_bearer_connect_result_ref (MMBearerConnectResult *result)
{
g_atomic_int_inc (&result->ref_count);
return result;
}
void
mm_bearer_connect_result_unref (MMBearerConnectResult *result)
{
if (g_atomic_int_dec_and_test (&result->ref_count)) {
if (result->ipv4_config)
g_object_unref (result->ipv4_config);
if (result->ipv6_config)
g_object_unref (result->ipv6_config);
if (result->data)
g_object_unref (result->data);
g_slice_free (MMBearerConnectResult, result);
}
}
MMPort *
mm_bearer_connect_result_peek_data (MMBearerConnectResult *result)
{
return result->data;
}
MMBearerIpConfig *
mm_bearer_connect_result_peek_ipv4_config (MMBearerConnectResult *result)
{
return result->ipv4_config;
}
MMBearerIpConfig *
mm_bearer_connect_result_peek_ipv6_config (MMBearerConnectResult *result)
{
return result->ipv6_config;
}
void
mm_bearer_connect_result_set_multiplexed (MMBearerConnectResult *result,
gboolean multiplexed)
{
result->multiplexed = multiplexed;
}
gboolean
mm_bearer_connect_result_get_multiplexed (MMBearerConnectResult *result)
{
return result->multiplexed;
}
void
mm_bearer_connect_result_set_profile_id (MMBearerConnectResult *result,
gint profile_id)
{
result->profile_id = profile_id;
}
gint
mm_bearer_connect_result_get_profile_id (MMBearerConnectResult *result)
{
return result->profile_id;
}
void
mm_bearer_connect_result_set_uplink_speed (MMBearerConnectResult *result,
guint64 speed)
{
result->uplink_speed = speed;
}
guint64
mm_bearer_connect_result_get_uplink_speed (MMBearerConnectResult *result)
{
return result->uplink_speed;
}
void
mm_bearer_connect_result_set_downlink_speed (MMBearerConnectResult *result,
guint64 speed)
{
result->downlink_speed = speed;
}
guint64
mm_bearer_connect_result_get_downlink_speed (MMBearerConnectResult *result)
{
return result->downlink_speed;
}
MMBearerConnectResult *
mm_bearer_connect_result_new (MMPort *data,
MMBearerIpConfig *ipv4_config,
MMBearerIpConfig *ipv6_config)
{
MMBearerConnectResult *result;
/* 'data' must always be given */
g_assert (MM_IS_PORT (data));
result = g_slice_new0 (MMBearerConnectResult);
result->ref_count = 1;
result->data = g_object_ref (data);
if (ipv4_config)
result->ipv4_config = g_object_ref (ipv4_config);
if (ipv6_config)
result->ipv6_config = g_object_ref (ipv6_config);
result->multiplexed = FALSE; /* default */
result->profile_id = MM_3GPP_PROFILE_ID_UNKNOWN;
return result;
}