/* -*- 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 */ #include #include #include #include #include #include #include #define _LIBMM_INSIDE_MM #include #include "mm-daemon-enums-types.h" #include "mm-iface-modem.h" #include "mm-iface-modem-3gpp.h" #include "mm-iface-modem-cdma.h" #include "mm-base-bearer.h" #include "mm-base-modem-at.h" #include "mm-base-modem.h" #include "mm-log.h" #include "mm-modem-helpers.h" #include "mm-bearer-stats.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 G_DEFINE_TYPE (MMBaseBearer, mm_base_bearer, MM_GDBUS_TYPE_BEARER_SKELETON) 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_MODEM, PROP_STATUS, PROP_CONFIG, PROP_DEFAULT_IP_FAMILY, PROP_LAST }; static GParamSpec *properties[PROP_LAST]; struct _MMBaseBearerPrivate { /* The connection to the system bus */ GDBusConnection *connection; /* The modem which owns this BEARER */ MMBaseModem *modem; /* The path where the BEARER object is exported */ gchar *path; /* Status of this bearer */ MMBearerStatus status; /* Configuration of the bearer */ MMBearerProperties *config; /* Default IP family of this bearer */ MMBearerIpFamily default_ip_family; /* 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_unsupported; }; /*****************************************************************************/ 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) { static guint id = 0; gchar *path; path = g_strdup_printf (MM_DBUS_BEARER_PREFIX "/%d", 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_warn ("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_dbg ("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_dbg ("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 */ 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) { 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_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_interface_stats (MMBaseBearer *self) { g_clear_object (&self->priv->stats); mm_gdbus_bearer_set_stats (MM_GDBUS_BEARER (self), NULL); } static void bearer_stats_stop (MMBaseBearer *self) { if (self->priv->duration_timer) { if (self->priv->stats) mm_bearer_stats_set_duration (self->priv->stats, (guint64) g_timer_elapsed (self->priv->duration_timer, NULL)); 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)) { /* If reloading stats fails, warn about it and don't update anything */ if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) { mm_warn ("Reloading stats failed: %s", error->message); g_error_free (error); return; } /* If we're being told that reloading stats is unsupported, just ignore * the error and update oly the duration timer. */ mm_dbg ("Reloading stats is unsupported by the device"); self->priv->reload_stats_unsupported = TRUE; rx_bytes = 0; tx_bytes = 0; g_error_free (error); } /* We only update stats if they were retrieved properly */ mm_bearer_stats_set_duration (self->priv->stats, (guint32) g_timer_elapsed (self->priv->duration_timer, NULL)); mm_bearer_stats_set_tx_bytes (self->priv->stats, tx_bytes); mm_bearer_stats_set_rx_bytes (self->priv->stats, rx_bytes); bearer_update_interface_stats (self); } static gboolean stats_update_cb (MMBaseBearer *self) { /* If the implementation knows how to update stat values, run it */ if (!self->priv->reload_stats_unsupported && 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_ready, NULL); return G_SOURCE_CONTINUE; } /* Otherwise, just update duration and we're done */ mm_bearer_stats_set_duration (self->priv->stats, (guint32) g_timer_elapsed (self->priv->duration_timer, NULL)); mm_bearer_stats_set_tx_bytes (self->priv->stats, 0); mm_bearer_stats_set_rx_bytes (self->priv->stats, 0); bearer_update_interface_stats (self); return G_SOURCE_CONTINUE; } static void bearer_stats_start (MMBaseBearer *self) { /* Allocate new stats object. If there was one already created from a * previous run, deallocate it */ g_assert (!self->priv->stats); self->priv->stats = mm_bearer_stats_new (); /* 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); /* Load initial values */ stats_update_cb (self); } /*****************************************************************************/ static void bearer_reset_interface_status (MMBaseBearer *self) { mm_gdbus_bearer_set_connected (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 */ /* 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) { bearer_reset_interface_status (self); /* Stop statistics */ bearer_stats_stop (self); /* Stop connection monitoring */ connection_monitor_stop (self); } } static void bearer_update_status_connected (MMBaseBearer *self, const gchar *interface, MMBearerIpConfig *ipv4_config, MMBearerIpConfig *ipv6_config) { 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)); /* Start statistics */ bearer_stats_start (self); /* Start connection monitor, if supported */ connection_monitor_start (self); /* Update the property value */ self->priv->status = MM_BEARER_STATUS_CONNECTED; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]); } /*****************************************************************************/ 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_dbg ("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: 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; } /* 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_dbg ("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_dbg ("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_dbg ("Bearer not allowed to connect, not registered in 3GPP network"); mm_base_bearer_disconnect_force (self); return; } /* Otherwise, setup the new timeout */ mm_dbg ("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_dbg ("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_dbg ("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_dbg ("Bearer not allowed to connect, not registered in CDMA network"); mm_base_bearer_disconnect_force (self); return; } /* Otherwise, setup the new timeout */ mm_dbg ("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 disconnect_after_cancel_ready (MMBaseBearer *self, GAsyncResult *res) { GError *error = NULL; if (!MM_BASE_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) { mm_warn ("Error disconnecting bearer '%s': '%s'. " "Will assume disconnected anyway.", self->priv->path, error->message); g_error_free (error); } else mm_dbg ("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_ready (MMBaseBearer *self, GAsyncResult *res, GTask *task) { GError *error = NULL; gboolean launch_disconnect = FALSE; MMBearerConnectResult *result; /* NOTE: connect() implementations *MUST* handle cancellations themselves */ result = MM_BASE_BEARER_GET_CLASS (self)->connect_finish (self, res, &error); if (!result) { mm_dbg ("Couldn't connect bearer '%s': '%s'", self->priv->path, error->message); if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED)) { /* Will launch disconnection */ launch_disconnect = TRUE; } else bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED); } /* Handle cancellations detected after successful connection */ else if (g_cancellable_is_cancelled (self->priv->connect_cancellable)) { mm_dbg ("Connected bearer '%s', but need to disconnect", self->priv->path); mm_bearer_connect_result_unref (result); error = g_error_new ( MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Bearer got connected, but had to disconnect after cancellation request"); launch_disconnect = TRUE; } else { mm_dbg ("Connected bearer '%s'", self->priv->path); /* 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_peek_ipv4_config (result), mm_bearer_connect_result_peek_ipv6_config (result)); mm_bearer_connect_result_unref (result); } if (launch_disconnect) { bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING); MM_BASE_BEARER_GET_CLASS (self)->disconnect ( self, (GAsyncReadyCallback)disconnect_after_cancel_ready, NULL); } g_clear_object (&self->priv->connect_cancellable); if (error) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (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; } /* Connecting! */ mm_dbg ("Connecting bearer '%s'", self->priv->path); self->priv->connect_cancellable = g_cancellable_new (); bearer_update_status (self, MM_BEARER_STATUS_CONNECTING); bearer_reset_interface_stats (self); MM_BASE_BEARER_GET_CLASS (self)->connect ( self, self->priv->connect_cancellable, (GAsyncReadyCallback)connect_ready, task); } typedef struct { MMBaseBearer *self; MMBaseModem *modem; GDBusMethodInvocation *invocation; } HandleConnectContext; static void handle_connect_context_free (HandleConnectContext *ctx) { g_object_unref (ctx->invocation); g_object_unref (ctx->modem); 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)) g_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 (MMBaseModem *modem, GAsyncResult *res, HandleConnectContext *ctx) { GError *error = NULL; if (!mm_base_modem_authorize_finish (modem, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_connect_context_free (ctx); return; } 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); g_object_get (self, MM_BASE_BEARER_MODEM, &ctx->modem, NULL); mm_base_modem_authorize (ctx->modem, invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (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_dbg ("Couldn't disconnect bearer '%s'", self->priv->path); bearer_update_status (self, MM_BEARER_STATUS_CONNECTED); g_task_return_error (task, error); } else { mm_dbg ("Disconnected bearer '%s'", self->priv->path); 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_dbg ("Disconnected bearer '%s' after cancelling previous connect request", self->priv->path); 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; } mm_dbg ("Disconnecting bearer '%s'", self->priv->path); /* 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; MMBaseModem *modem; GDBusMethodInvocation *invocation; } HandleDisconnectContext; static void handle_disconnect_context_free (HandleDisconnectContext *ctx) { g_object_unref (ctx->invocation); g_object_unref (ctx->modem); 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)) g_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 (MMBaseModem *modem, GAsyncResult *res, HandleDisconnectContext *ctx) { GError *error = NULL; if (!mm_base_modem_authorize_finish (modem, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); handle_disconnect_context_free (ctx); return; } 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); g_object_get (self, MM_BASE_BEARER_MODEM, &ctx->modem, NULL); mm_base_modem_authorize (ctx->modem, invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (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_warn ("couldn't export BEARER at '%s': '%s'", self->priv->path, 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_dbg ("Removing from DBus bearer at '%s'", path); 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); } MMBearerIpFamily mm_base_bearer_get_default_ip_family (MMBaseBearer *self) { return self->priv->default_ip_family; } /*****************************************************************************/ 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_warn ("Error disconnecting bearer '%s': '%s'. " "Will assume disconnected anyway.", self->priv->path, error->message); g_error_free (error); } else mm_dbg ("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); } void mm_base_bearer_disconnect_force (MMBaseBearer *self) { if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING || self->priv->status == MM_BEARER_STATUS_DISCONNECTED) return; mm_dbg ("Forcing disconnection of bearer '%s'", self->priv->path); /* 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) { /* 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_status (self, MM_BEARER_STATUS_DISCONNECTED); } void mm_base_bearer_report_connection_status (MMBaseBearer *self, MMBearerConnectionStatus status) { return MM_BASE_BEARER_GET_CLASS (self)->report_connection_status (self, status); } 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_MODEM: g_clear_object (&self->priv->modem); self->priv->modem = g_value_dup_object (value); if (self->priv->modem) { /* Bind the modem's connection (which is set when it is exported, * and unset when unexported) to the BEARER's connection */ g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION, self, MM_BASE_BEARER_CONNECTION, G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); 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; } case PROP_DEFAULT_IP_FAMILY: self->priv->default_ip_family = g_value_get_flags (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { 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_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; case PROP_DEFAULT_IP_FAMILY: g_value_set_flags (value, self->priv->default_ip_family); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void mm_base_bearer_init (MMBaseBearer *self) { /* Initialize private data */ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BASE_BEARER, MMBaseBearerPrivate); 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->default_ip_family = MM_BEARER_IP_FAMILY_IPV4; /* Set defaults */ mm_gdbus_bearer_set_interface (MM_GDBUS_BEARER (self), NULL); 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)); } 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->config); G_OBJECT_CLASS (mm_base_bearer_parent_class)->dispose (object); } 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]); 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]); properties[PROP_DEFAULT_IP_FAMILY] = g_param_spec_flags (MM_BASE_BEARER_DEFAULT_IP_FAMILY, "Bearer default IP family", "IP family to use for this bearer when no IP family is specified", MM_TYPE_BEARER_IP_FAMILY, MM_BEARER_IP_FAMILY_IPV4, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_DEFAULT_IP_FAMILY, properties[PROP_DEFAULT_IP_FAMILY]); } /*****************************************************************************/ /* Helpers to implement connect() */ struct _MMBearerConnectResult { volatile gint ref_count; MMPort *data; MMBearerIpConfig *ipv4_config; MMBearerIpConfig *ipv6_config; }; 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; } 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); return result; }