When we get a disconnect event that we wanted to ignore, set the
DisconnectRequest property instead so clients on dbus can act on it.
Also upgrade the logs from dbg to msg (info) as there would be otherwise
no message indicating the disconnect request if it is not acted on.
This was checked with dbus-monitor:
```
$ dbus-monitor --system "type='signal',sender='org.freedesktop.ModemManager1'"
signal time=1747362238.227804 sender=:1.73798 -> destination=(null destination) serial=741 path=/org/freedesktop/ModemManager1/Bearer/0; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
string "org.freedesktop.ModemManager1.Bearer"
array [
dict entry(
string "DisconnectRequest"
variant boolean true
)
]
array [
]
```
Signed-off-by: Dominique Martinet <dominique.martinet@atmark-techno.com>
2104 lines
74 KiB
C
2104 lines
74 KiB
C
/* -*- 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;
|
|
}
|