/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* cc-wwan-data.c * * Copyright 2019 Purism SPC * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author(s): * Mohammed Sadiq * * SPDX-License-Identifier: GPL-3.0-or-later */ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "cc-wwan-data" #ifdef HAVE_CONFIG_H # include #endif #define _GNU_SOURCE #include #include #include #include "cc-wwan-data.h" /** * @short_description: Device Internet Data Object * @include: "cc-wwan-device-data.h" * * #CcWwanData represents the data object of the given * #CcWwanDevice. Please note that while #CcWWanDevice * is bound to the hardware device, #CcWwanData may also * depend on the inserted SIM (if supported). So the state * of #CcWwanData changes when SIM is changed. */ /* * Priority for connections. The larger the number, the lower the priority * https://developer.gnome.org/NetworkManager/stable/nm-settings.html: * * A lower value is better (higher priority). Zero selects a globally * configured default value. If the latter is missing or zero too, it * defaults to 50 for VPNs and 100 for other connections. * * Since WiFi and other network connections will likely get the default * setting of 100, set WWAN DNS priorities higher than the default, with * room to allow multiple modems to set priority above/below each other. */ #define CC_WWAN_DNS_PRIORITY_LOW (120) #define CC_WWAN_DNS_PRIORITY_HIGH (115) /* These are to be set as route metric */ #define CC_WWAN_ROUTE_PRIORITY_LOW (1050) #define CC_WWAN_ROUTE_PRIORITY_HIGH (1040) struct _CcWwanData { GObject parent_instance; MMObject *mm_object; MMModem *modem; MMSim *sim; gchar *sim_id; gchar *operator_code; /* MCCMNC */ GError *error; NMClient *nm_client; NMDevice *nm_device; NMAMobileProvidersDatabase *apn_db; NMAMobileProvider *apn_provider; CcWwanDataApn *default_apn; CcWwanDataApn *old_default_apn; GListStore *apn_list; NMActiveConnection *active_connection; gint priority; gboolean data_enabled; /* autoconnect enabled */ gboolean home_only; /* Data roaming */ gboolean apn_list_updated; /* APN list updated from mobile-provider-info */ }; G_DEFINE_TYPE (CcWwanData, cc_wwan_data, G_TYPE_OBJECT) /* * Default Access Point Settings Logic: * For a provided SIM, all the APNs available from NetworkManager * that matches the given SIM identifier (ICCID, available via * mm_sim_get_identifier() or similar gdbus API) is loaded for * the Device (In NetworkManager, it is saved as ‘sim-id’, if * present). At a time, only one connection will be bound to * a device. If there are more than one match, the item with * the highest ‘route-metric’ is taken. If more matches are * still available, the first item is chosen. * * Populating All available APNs: * All Possible APNs for the given sim are populated the following * way (A list of all the following avoiding duplicates) * 1. The above mentioned “Default Access Point Settings Logic” * 2. Get All saved Network Manager connections with the * provided MCCMNC of the given SIM * 3. Get All possible APNs for the MCCMNC from mobile-provider-info * * Testing if data is enabled: * Check if any of the items from step 1 have ‘autoconnect’ set * * Checking/Setting current SIM for data (in case of multiple SIM): * Since other networks (like wifi, ethernet) should have higher * priorities we use a negative number for priority. * 1. All APNs by default have priority CC_WWAN_APN_PRIORITY_LOW * 2. APN of selected SIM for active data have priority of * CC_WWAN_APN_PRIORITY_HIGH * * XXX: Since users may create custom APNs via nmtui or like tools * we may have to check if there are some inconsistencies with APNs * available in NetworkManager, and ask user if they have to reset * the APNs that have invalid settings (basically, we care only APNs * that are set to have ‘autoconnect’ enabled, and all we need is to * disable autoconnect). We won’t interfere CDMA/EVDO networks. */ struct _CcWwanDataApn { GObject parent_instance; /* Set if the APN is from the mobile-provider-info database */ NMAMobileAccessMethod *access_method; /* Set if the APN is saved in NetworkManager */ NMConnection *nm_connection; NMRemoteConnection *remote_connection; gboolean modified; }; G_DEFINE_TYPE (CcWwanDataApn, cc_wwan_data_apn, G_TYPE_OBJECT) enum { PROP_0, PROP_ERROR, PROP_ENABLED, N_PROPS }; static GParamSpec *properties[N_PROPS]; static void wwan_data_apn_reset (CcWwanDataApn *apn) { if (!apn) return; g_clear_object (&apn->nm_connection); g_clear_object (&apn->remote_connection); } static NMConnection * wwan_data_get_nm_connection (CcWwanDataApn *apn) { NMConnection *connection; NMSetting *setting; g_autofree gchar *uuid = NULL; if (apn->nm_connection) return apn->nm_connection; if (apn->remote_connection) return NM_CONNECTION (apn->remote_connection); connection = nm_simple_connection_new (); apn->nm_connection = connection; setting = nm_setting_connection_new (); uuid = nm_utils_uuid_generate (); g_object_set (setting, NM_SETTING_CONNECTION_UUID, uuid, NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME, NULL); nm_connection_add_setting (connection, setting); setting = nm_setting_serial_new (); nm_connection_add_setting (connection, setting); setting = nm_setting_ip4_config_new (); g_object_set (setting, NM_SETTING_IP_CONFIG_METHOD, "auto", NULL); nm_connection_add_setting (connection, setting); nm_connection_add_setting (connection, nm_setting_gsm_new ()); nm_connection_add_setting (connection, nm_setting_ppp_new ()); return apn->nm_connection; } static gboolean wwan_data_apn_are_same (CcWwanDataApn *apn, NMAMobileAccessMethod *access_method) { NMConnection *connection; NMSetting *setting; if (!apn->remote_connection) return FALSE; connection = NM_CONNECTION (apn->remote_connection); setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); if (g_strcmp0 (nma_mobile_access_method_get_3gpp_apn (access_method), nm_setting_gsm_get_apn (NM_SETTING_GSM (setting))) != 0) return FALSE; if (g_strcmp0 (nma_mobile_access_method_get_username (access_method), nm_setting_gsm_get_username (NM_SETTING_GSM (setting))) != 0) return FALSE; if (g_strcmp0 (nma_mobile_access_method_get_password (access_method), cc_wwan_data_apn_get_password (apn)) != 0) return FALSE; return TRUE; } static CcWwanDataApn * wwan_data_find_matching_apn (CcWwanData *self, NMAMobileAccessMethod *access_method) { CcWwanDataApn *apn; guint i, n_items; n_items = g_list_model_get_n_items (G_LIST_MODEL (self->apn_list)); for (i = 0; i < n_items; i++) { apn = g_list_model_get_item (G_LIST_MODEL (self->apn_list), i); if (apn->access_method == access_method) return apn; if (wwan_data_apn_are_same (apn, access_method)) return apn; g_object_unref (apn); } return NULL; } static gboolean wwan_data_nma_method_is_mms (NMAMobileAccessMethod *method) { const char *str; str = nma_mobile_access_method_get_3gpp_apn (method); if (str && strcasestr (str, "mms")) return TRUE; str = nma_mobile_access_method_get_name (method); if (str && strcasestr (str, "mms")) return TRUE; return FALSE; } static void wwan_data_update_apn_list_db (CcWwanData *self) { GSList *apn_methods = NULL, *l; g_autoptr(GError) error = NULL; guint i = 0; if (!self->sim || !self->operator_code || self->apn_list_updated) return; if (!self->apn_list) return; if (!self->apn_db) self->apn_db = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error); if (error) { g_warning ("%s", error->message); return; } if (!self->apn_provider) self->apn_provider = nma_mobile_providers_database_lookup_3gpp_mcc_mnc (self->apn_db, self->operator_code); if (self->apn_provider) apn_methods = nma_mobile_provider_get_methods (self->apn_provider); self->apn_list_updated = TRUE; for (l = apn_methods; l; l = l->next, i++) { g_autoptr(CcWwanDataApn) apn = NULL; /* We don’t list MMS APNs */ if (wwan_data_nma_method_is_mms (l->data)) continue; apn = wwan_data_find_matching_apn (self, l->data); /* Prepend the item in order */ if (!apn) { apn = cc_wwan_data_apn_new (); apn->access_method = l->data; g_list_store_insert (self->apn_list, i, apn); } apn->access_method = l->data; } } static void wwan_data_update_apn_list (CcWwanData *self) { const GPtrArray *nm_connections; guint i; if (self->apn_list || !self->sim || !self->nm_device || nm_device_get_state (self->nm_device) <= NM_DEVICE_STATE_UNAVAILABLE) return; if (!self->apn_list) self->apn_list = g_list_store_new (CC_TYPE_WWAN_DATA_APN); if (self->nm_device) { nm_connections = nm_device_get_available_connections (self->nm_device); for (i = 0; i < nm_connections->len; i++) { g_autoptr(CcWwanDataApn) apn = NULL; apn = cc_wwan_data_apn_new (); apn->remote_connection = g_object_ref (nm_connections->pdata[i]); g_list_store_append (self->apn_list, apn); /* Load the default APN */ if (!self->default_apn && self->sim_id) { NMSettingConnection *connection_setting; NMSettingIPConfig *ip_setting; NMSettingGsm *setting; NMConnection *connection; const gchar *sim_id; connection = NM_CONNECTION (apn->remote_connection); setting = nm_connection_get_setting_gsm (connection); connection_setting = nm_connection_get_setting_connection (connection); sim_id = nm_setting_gsm_get_sim_id (setting); if (sim_id && *sim_id && g_str_equal (sim_id, self->sim_id)) { self->default_apn = apn; self->home_only = nm_setting_gsm_get_home_only (setting); self->data_enabled = nm_setting_connection_get_autoconnect (connection_setting); /* If any of the APN has a high priority, the device have high priority */ ip_setting = nm_connection_get_setting_ip4_config (connection); if (nm_setting_ip_config_get_route_metric (ip_setting) == CC_WWAN_ROUTE_PRIORITY_HIGH) self->priority = CC_WWAN_APN_PRIORITY_HIGH; } } } } } static void wwan_device_state_changed_cb (CcWwanData *self) { g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED]); } static void wwan_device_3gpp_operator_code_changd_cb (CcWwanData *self) { MMModem3gpp *modem_3gpp; modem_3gpp = mm_object_peek_modem_3gpp (self->mm_object); if (!self->operator_code) { self->operator_code = mm_modem_3gpp_dup_operator_code (modem_3gpp); if (self->operator_code) { wwan_data_update_apn_list (self); wwan_data_update_apn_list_db (self); } } } static void cc_wwan_data_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CcWwanData *self = (CcWwanData *)object; switch (prop_id) { case PROP_ERROR: g_value_set_boolean (value, self->error != NULL); break; case PROP_ENABLED: g_value_set_boolean (value, cc_wwan_data_get_enabled (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void cc_wwan_data_dispose (GObject *object) { CcWwanData *self = (CcWwanData *)object; g_clear_pointer (&self->sim_id, g_free); g_clear_pointer (&self->operator_code, g_free); g_clear_error (&self->error); g_clear_object (&self->apn_list); g_clear_object (&self->modem); g_clear_object (&self->mm_object); g_clear_object (&self->nm_client); g_clear_object (&self->active_connection); g_clear_object (&self->apn_db); G_OBJECT_CLASS (cc_wwan_data_parent_class)->dispose (object); } static void cc_wwan_data_class_init (CcWwanDataClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = cc_wwan_data_get_property; object_class->dispose = cc_wwan_data_dispose; properties[PROP_ERROR] = g_param_spec_boolean ("error", "Error", "Set if some Error occurs", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); properties[PROP_ENABLED] = g_param_spec_boolean ("enabled", "Enabled", "Get if the data is enabled", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, properties); } static void cc_wwan_data_init (CcWwanData *self) { self->home_only = TRUE; self->priority = CC_WWAN_APN_PRIORITY_LOW; } /** * cc_wwan_data_new: * @mm_object: An #MMObject * @nm_client: An #NMClient * * Create a new device data representing the given * @mm_object. If @mm_object isn’t a 3G/CDMA/LTE * modem, %NULL will be returned * * Returns: A #CcWwanData or %NULL. */ CcWwanData * cc_wwan_data_new (MMObject *mm_object, NMClient *nm_client) { CcWwanData *self; NMDevice *nm_device = NULL; g_autoptr(MMModem) modem = NULL; NMDeviceModemCapabilities capabilities = 0; g_return_val_if_fail (MM_IS_OBJECT (mm_object), NULL); g_return_val_if_fail (NM_CLIENT (nm_client), NULL); modem = mm_object_get_modem (mm_object); if (modem) nm_device = nm_client_get_device_by_iface (nm_client, mm_modem_get_primary_port (modem)); if (NM_IS_DEVICE_MODEM (nm_device)) capabilities = nm_device_modem_get_current_capabilities (NM_DEVICE_MODEM (nm_device)); if (!(capabilities & (NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS | NM_DEVICE_MODEM_CAPABILITY_LTE))) return NULL; self = g_object_new (CC_TYPE_WWAN_DATA, NULL); self->nm_client = g_object_ref (nm_client); self->mm_object = g_object_ref (mm_object); self->modem = g_steal_pointer (&modem); self->sim = mm_modem_get_sim_sync (self->modem, NULL, NULL); self->sim_id = mm_sim_dup_identifier (self->sim); self->operator_code = mm_sim_dup_operator_identifier (self->sim); self->nm_device = g_object_ref (nm_device); self->active_connection = nm_device_get_active_connection (nm_device); if (!self->operator_code) { MMModem3gpp *modem_3gpp; modem_3gpp = mm_object_peek_modem_3gpp (mm_object); if (modem_3gpp) { g_signal_connect_object (modem_3gpp, "notify::operator-code", G_CALLBACK (wwan_device_3gpp_operator_code_changd_cb), self, G_CONNECT_SWAPPED); wwan_device_3gpp_operator_code_changd_cb (self); } } if (self->active_connection) g_object_ref (self->active_connection); g_signal_connect_object (self->nm_device, "notify::state", G_CALLBACK (wwan_device_state_changed_cb), self, G_CONNECT_SWAPPED); wwan_data_update_apn_list (self); wwan_data_update_apn_list_db (self); return self; } GError * cc_wwan_data_get_error (CcWwanData *self) { g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); return self->error; } const gchar * cc_wwan_data_get_simple_html_error (CcWwanData *self) { g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); if (!self->error) return NULL; if (g_error_matches (self->error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return _("Operation Cancelled"); if (g_error_matches (self->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) return _("Error: Access denied changing settings"); if (self->error->domain == MM_MOBILE_EQUIPMENT_ERROR) return _("Error: Mobile Equipment Error"); return NULL; } GListModel * cc_wwan_data_get_apn_list (CcWwanData *self) { g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); if (!self->apn_list) wwan_data_update_apn_list (self); return G_LIST_MODEL (self->apn_list); } static gboolean wwan_data_apn_is_new (CcWwanDataApn *apn) { return apn->remote_connection == NULL; } static void wwan_data_update_apn (CcWwanData *self, CcWwanDataApn *apn, NMConnection *connection) { NMSetting *setting; const gchar *name, *username, *password, *apn_name; gint dns_priority, route_metric; setting = NM_SETTING (nm_connection_get_setting_connection (connection)); g_object_set (setting, NM_SETTING_CONNECTION_AUTOCONNECT, self->data_enabled, NULL); setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); g_object_set (setting, NM_SETTING_GSM_HOME_ONLY, self->home_only, NULL); setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection)); if (self->priority == CC_WWAN_APN_PRIORITY_HIGH && self->default_apn == apn) { dns_priority = CC_WWAN_DNS_PRIORITY_HIGH; route_metric = CC_WWAN_ROUTE_PRIORITY_HIGH; } else { dns_priority = CC_WWAN_DNS_PRIORITY_LOW; route_metric = CC_WWAN_ROUTE_PRIORITY_LOW; } g_object_set (setting, NM_SETTING_IP_CONFIG_DNS_PRIORITY, dns_priority, NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)route_metric, NULL); if (apn->access_method && !apn->remote_connection) { name = nma_mobile_access_method_get_name (apn->access_method); username = nma_mobile_access_method_get_username (apn->access_method); password = nma_mobile_access_method_get_password (apn->access_method); apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method); } else { return; } setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); g_object_set (setting, NM_SETTING_GSM_USERNAME, username, NM_SETTING_GSM_PASSWORD, password, NM_SETTING_GSM_APN, apn_name, NULL); setting = NM_SETTING (nm_connection_get_setting_connection (connection)); g_object_set (setting, NM_SETTING_CONNECTION_ID, name, NULL); } static gint wwan_data_get_apn_index (CcWwanData *self, CcWwanDataApn *apn) { GListModel *model; guint i, n_items; model = G_LIST_MODEL (self->apn_list); n_items = g_list_model_get_n_items (model); for (i = 0; i < n_items; i++) { g_autoptr(CcWwanDataApn) cached_apn = NULL; cached_apn = g_list_model_get_item (model, i); if (apn == cached_apn) return i; } return -1; } static void cc_wwan_data_connection_updated_cb (GObject *object, GAsyncResult *result, gpointer user_data) { CcWwanData *self; CcWwanDataApn *apn; g_autoptr(GTask) task = user_data; g_autoptr(GError) error = NULL; self = g_task_get_source_object (G_TASK (task)); apn = g_task_get_task_data (G_TASK (task)); nm_remote_connection_commit_changes_finish (apn->remote_connection, result, &error); if (!error) { guint apn_index; apn_index = wwan_data_get_apn_index (self, apn); if (apn_index >= 0) g_list_model_items_changed (G_LIST_MODEL (self->apn_list), apn_index, 1, 1); else g_warning ("APN ‘%s’ not in APN list", cc_wwan_data_apn_get_name (apn)); apn->modified = FALSE; g_task_return_boolean (task, TRUE); } else { g_task_return_error (task, g_steal_pointer (&error)); } } static void cc_wwan_data_new_connection_added_cb (GObject *object, GAsyncResult *result, gpointer user_data) { CcWwanData *self; CcWwanDataApn *apn; g_autoptr(GTask) task = user_data; g_autoptr(GError) error = NULL; self = g_task_get_source_object (G_TASK (task)); apn = g_task_get_task_data (G_TASK (task)); apn->remote_connection = nm_client_add_connection_finish (self->nm_client, result, &error); if (!error) { apn->modified = FALSE; /* If APN has access method, it’s already on the list */ if (!apn->access_method) { g_list_store_append (self->apn_list, apn); g_object_unref (apn); } g_task_return_pointer (task, apn, NULL); } else { g_task_return_error (task, g_steal_pointer (&error)); } } void cc_wwan_data_save_apn (CcWwanData *self, CcWwanDataApn *apn, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { NMConnection *connection = NULL; g_autoptr(GTask) task = NULL; g_return_if_fail (CC_IS_WWAN_DATA (self)); g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, apn, NULL); connection = wwan_data_get_nm_connection (apn); /* If the item has a remote connection, it should already be saved. * We should save it again only if it got modified */ if (apn->remote_connection && !apn->modified) { g_task_return_pointer (task, apn, NULL); return; } wwan_data_update_apn (self, apn, connection); if (wwan_data_apn_is_new (apn)) { nm_client_add_connection_async (self->nm_client, apn->nm_connection, TRUE, cancellable, cc_wwan_data_new_connection_added_cb, g_steal_pointer (&task)); } else { nm_remote_connection_commit_changes_async (apn->remote_connection, TRUE, cancellable, cc_wwan_data_connection_updated_cb, g_steal_pointer (&task)); } } CcWwanDataApn * cc_wwan_data_save_apn_finish (CcWwanData *self, GAsyncResult *result, GError **error) { g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); g_return_val_if_fail (G_IS_TASK (result), NULL); return g_task_propagate_pointer (G_TASK (result), error); } static void cc_wwan_data_activated_cb (GObject *object, GAsyncResult *result, gpointer user_data) { CcWwanData *self; NMActiveConnection *connection; g_autoptr(GTask) task = user_data; g_autoptr(GError) error = NULL; self = g_task_get_source_object (G_TASK (task)); connection = nm_client_activate_connection_finish (self->nm_client, result, &error); if (connection) { g_set_object (&self->active_connection, connection); g_task_return_boolean (task, TRUE); } else { g_task_return_error (task, g_steal_pointer (&error)); } if (error) g_warning ("Error: %s", error->message); } static void cc_wwan_data_disconnect_cb (GObject *object, GAsyncResult *result, gpointer user_data) { CcWwanData *self; g_autoptr(GTask) task = user_data; g_autoptr(GError) error = NULL; self = g_task_get_source_object (G_TASK (task)); if (nm_device_disconnect_finish (self->nm_device, result, &error)) { g_clear_object (&self->active_connection); g_task_return_boolean (task, TRUE); } else { g_task_return_error (task, g_steal_pointer (&error)); } if (error) g_warning ("Error: %s", error->message); } static void cc_wwan_data_settings_saved_cb (GObject *object, GAsyncResult *result, gpointer user_data) { CcWwanData *self; GCancellable *cancellable; g_autoptr(GTask) task = user_data; g_autoptr(GError) error = NULL; self = g_task_get_source_object (G_TASK (task)); cancellable = g_task_get_cancellable (G_TASK (task)); if (!cc_wwan_data_save_apn_finish (self, result, &error)) { g_task_return_error (task, g_steal_pointer (&error)); return; } self->default_apn->modified = FALSE; if (self->data_enabled) { nm_client_activate_connection_async (self->nm_client, NM_CONNECTION (self->default_apn->remote_connection), self->nm_device, NULL, cancellable, cc_wwan_data_activated_cb, g_steal_pointer (&task)); } else { nm_device_disconnect_async (self->nm_device, cancellable, cc_wwan_data_disconnect_cb, g_steal_pointer (&task)); } } /** * cc_wwan_data_save_settings: * @cancellable: (nullable): a #GCancellable or %NULL * @callback: a #GAsyncReadyCallback, or %NULL * @user_data: closure data for @callback * * Save default settings to disk and apply changes. * If the default APN has data enabled, the data is * activated after the settings are saved. * * It’s a programmer error to call this function without * a default APN set. * Finish with cc_wwan_data_save_settings_finish(). */ void cc_wwan_data_save_settings (CcWwanData *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { NMConnection *connection; NMSetting *setting; g_autoptr(GTask) task = NULL; g_return_if_fail (CC_IS_WWAN_DATA (self)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (self->default_apn != NULL); task = g_task_new (self, cancellable, callback, user_data); /* Reset old settings to default value */ if (self->old_default_apn && self->old_default_apn->remote_connection) { connection = NM_CONNECTION (self->old_default_apn->remote_connection); setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); g_object_set (G_OBJECT (setting), NM_SETTING_GSM_HOME_ONLY, TRUE, NM_SETTING_GSM_SIM_ID, NULL, NULL); setting = NM_SETTING (nm_connection_get_setting_ip4_config (connection)); g_object_set (setting, NM_SETTING_IP_CONFIG_DNS_PRIORITY, CC_WWAN_DNS_PRIORITY_LOW, NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64)CC_WWAN_ROUTE_PRIORITY_LOW, NULL); setting = NM_SETTING (nm_connection_get_setting_connection (connection)); g_object_set (G_OBJECT (setting), NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL); nm_remote_connection_commit_changes (NM_REMOTE_CONNECTION (connection), TRUE, cancellable, NULL); self->old_default_apn->modified = FALSE; self->old_default_apn = NULL; } self->default_apn->modified = TRUE; connection = wwan_data_get_nm_connection (self->default_apn); setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); g_object_set (G_OBJECT (setting), NM_SETTING_GSM_HOME_ONLY, self->home_only, NM_SETTING_GSM_SIM_ID, self->sim_id, NULL); cc_wwan_data_save_apn (self, self->default_apn, cancellable, cc_wwan_data_settings_saved_cb, g_steal_pointer (&task)); } gboolean cc_wwan_data_save_settings_finish (CcWwanData *self, GAsyncResult *result, GError **error) { g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); g_return_val_if_fail (G_IS_TASK (result), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } gboolean cc_wwan_data_delete_apn (CcWwanData *self, CcWwanDataApn *apn, GCancellable *cancellable, GError **error) { NMRemoteConnection *connection = NULL; gboolean ret = FALSE; gint apn_index; g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE); g_return_val_if_fail (error != NULL, FALSE); apn_index = wwan_data_get_apn_index (self, apn); if (apn_index == -1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "APN not found for the connection"); return FALSE; } connection = g_steal_pointer (&apn->remote_connection); wwan_data_apn_reset (apn); if (connection) ret = nm_remote_connection_delete (connection, cancellable, error); if (!ret) { apn->remote_connection = connection; *error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "Deleting APN from NetworkManager failed"); return ret; } g_object_unref (connection); /* We remove the item only if it's not in the mobile provider database */ if (!apn->access_method) { if (self->default_apn == apn) self->default_apn = NULL; g_list_store_remove (self->apn_list, apn_index); return TRUE; } *error = g_error_new (G_IO_ERROR, G_IO_ERROR_READ_ONLY, "Deleting APN from NetworkManager failed"); return FALSE; } CcWwanDataApn * cc_wwan_data_get_default_apn (CcWwanData *self) { g_return_val_if_fail (CC_IS_WWAN_DATA (self), NULL); return self->default_apn; } gboolean cc_wwan_data_set_default_apn (CcWwanData *self, CcWwanDataApn *apn) { NMConnection *connection; NMSetting *setting; g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), FALSE); if (self->default_apn == apn) return FALSE; /* * APNs are bound to the SIM, not the modem device. * This will let the APN work if the same SIM inserted * in a different device, and not enable data if a * different SIM is inserted to the modem. */ apn->modified = TRUE; self->old_default_apn = self->default_apn; self->default_apn = apn; connection = wwan_data_get_nm_connection (apn); setting = NM_SETTING (nm_connection_get_setting_gsm (connection)); if (self->sim_id) g_object_set (G_OBJECT (setting), NM_SETTING_GSM_SIM_ID, self->sim_id, NULL); return TRUE; } gboolean cc_wwan_data_get_enabled (CcWwanData *self) { NMSettingConnection *setting; NMConnection *connection; NMDeviceState state; g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); state = nm_device_get_state (self->nm_device); if (state == NM_DEVICE_STATE_DISCONNECTED || state == NM_DEVICE_STATE_DEACTIVATING) if (nm_device_get_state_reason (self->nm_device) == NM_DEVICE_STATE_REASON_USER_REQUESTED) return FALSE; if (nm_device_get_active_connection (self->nm_device) != NULL) return TRUE; if (!self->default_apn || !self->default_apn->remote_connection) return FALSE; connection = NM_CONNECTION (self->default_apn->remote_connection); setting = nm_connection_get_setting_connection (connection); return nm_setting_connection_get_autoconnect (setting); } /** * cc_wwan_data_set_enabled: * @self: A #CcWwanData * @enable_data: whether to enable data * * Enable data for the device. The settings is * saved to disk only after a default APN is set. * * If the data is enabled, the device will automatically * turn data on everytime the same SIM is available. * The data set is bound to the SIM, not the modem device. * * Use @cc_wwan_data_save_apn() with the default APN * to save the changes and really enable/disable data. */ void cc_wwan_data_set_enabled (CcWwanData *self, gboolean enable_data) { g_return_if_fail (CC_IS_WWAN_DATA (self)); self->data_enabled = !!enable_data; if (self->default_apn) self->default_apn->modified = TRUE; } gboolean cc_wwan_data_get_roaming_enabled (CcWwanData *self) { g_return_val_if_fail (CC_IS_WWAN_DATA (self), FALSE); if (!self->default_apn) return FALSE; return !self->home_only; } /** * cc_wwan_data_apn_set_roaming_enabled: * @self: A #CcWwanData * @enable_roaming: whether to enable roaming or not * * Enable roaming for the device. The settings is * saved to disk only after a default APN is set. * * Use @cc_wwan_data_save_apn() with the default APN * to save the changes and really enable/disable data. */ void cc_wwan_data_set_roaming_enabled (CcWwanData *self, gboolean enable_roaming) { g_return_if_fail (CC_IS_WWAN_DATA (self)); self->home_only = !enable_roaming; if (self->default_apn) self->default_apn->modified = TRUE; } static void cc_wwan_data_apn_finalize (GObject *object) { CcWwanDataApn *apn = CC_WWAN_DATA_APN (object); wwan_data_apn_reset (apn); g_clear_pointer (&apn->access_method, nma_mobile_access_method_unref); G_OBJECT_CLASS (cc_wwan_data_parent_class)->finalize (object); } static void cc_wwan_data_apn_class_init (CcWwanDataApnClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cc_wwan_data_apn_finalize; } static void cc_wwan_data_apn_init (CcWwanDataApn *apn) { } CcWwanDataApn * cc_wwan_data_apn_new (void) { return g_object_new (CC_TYPE_WWAN_DATA_APN, NULL); } /** * cc_wwan_data_apn_get_name: * @apn: A #CcWwanDataApn * * Get the Name of @apn * * Returns: (transfer none): The Name of @apn */ const gchar * cc_wwan_data_apn_get_name (CcWwanDataApn *apn) { g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); if (apn->remote_connection) return nm_connection_get_id (NM_CONNECTION (apn->remote_connection)); if (apn->access_method) return nma_mobile_access_method_get_name (apn->access_method); return ""; } /** * cc_wwan_data_apn_set_name: * @apn: A #CcWwanDataApn * @name: The name to be given for APN, should not * be empty * * Set the name of @apn to be @name. * * @apn is only modified, use @cc_wwan_data_save_apn() * to save the changes. */ void cc_wwan_data_apn_set_name (CcWwanDataApn *apn, const gchar *name) { NMConnection *connection; NMSettingConnection *setting; g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); g_return_if_fail (name != NULL); g_return_if_fail (*name != '\0'); if (g_str_equal (cc_wwan_data_apn_get_name (apn), name)) return; apn->modified = TRUE; connection = wwan_data_get_nm_connection (apn); setting = nm_connection_get_setting_connection (connection); g_object_set (G_OBJECT (setting), NM_SETTING_CONNECTION_ID, name, NULL); } /** * cc_wwan_data_apn_get_apn: * @apn: A #CcWwanDataApn * * Get the APN of @apn * * Returns: (transfer none): The APN of @apn */ const gchar * cc_wwan_data_apn_get_apn (CcWwanDataApn *apn) { const gchar *apn_name = ""; g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); if (apn->remote_connection) { NMSettingGsm *setting; setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection)); apn_name = nm_setting_gsm_get_apn (setting); } else if (apn->access_method) { apn_name = nma_mobile_access_method_get_3gpp_apn (apn->access_method); } return apn_name ? apn_name : ""; } /** * cc_wwan_data_apn_set_apn: * @apn: A #CcWwanDataApn * @apn_name: The apn to be used, should not be * empty * * Set the APN of @apn to @apn_name. @apn_name is * usually a URL like “example.com” or a simple string * like “internet” * * @apn is only modified, use @cc_wwan_data_save_apn() * to save the changes. */ void cc_wwan_data_apn_set_apn (CcWwanDataApn *apn, const gchar *apn_name) { NMConnection *connection; NMSettingGsm *setting; g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); g_return_if_fail (apn_name != NULL); g_return_if_fail (*apn_name != '\0'); if (g_str_equal (cc_wwan_data_apn_get_apn (apn), apn_name)) return; apn->modified = TRUE; connection = wwan_data_get_nm_connection (apn); setting = nm_connection_get_setting_gsm (connection); g_object_set (G_OBJECT (setting), NM_SETTING_GSM_APN, apn_name, NULL); } /** * cc_wwan_data_apn_get_username: * @apn: A #CcWwanDataApn * * Get the Username of @apn * * Returns: (transfer none): The Username of @apn */ const gchar * cc_wwan_data_apn_get_username (CcWwanDataApn *apn) { const gchar *username = ""; g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); if (apn->remote_connection) { NMSettingGsm *setting; setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection)); username = nm_setting_gsm_get_username (setting); } else if (apn->access_method) { username = nma_mobile_access_method_get_username (apn->access_method); } return username ? username : ""; } /** * cc_wwan_data_apn_set_username: * @apn: A #CcWwanDataAPN * @username: The username to be used * * Set the Username of @apn to @username. * * @apn is only modified, use @cc_wwan_data_save_apn() * to save the changes. */ void cc_wwan_data_apn_set_username (CcWwanDataApn *apn, const gchar *username) { NMConnection *connection; NMSettingGsm *setting; g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); if (username && !*username) username = NULL; if (g_strcmp0 (cc_wwan_data_apn_get_username (apn), username) == 0) return; apn->modified = TRUE; connection = wwan_data_get_nm_connection (apn); setting = nm_connection_get_setting_gsm (connection); g_object_set (G_OBJECT (setting), NM_SETTING_GSM_USERNAME, username, NULL); } /** * cc_wwan_data_apn_get_password: * @apn: A #CcWwanDataApn * * Get the Password of @apn * * Returns: (transfer none): The Password of @apn */ const gchar * cc_wwan_data_apn_get_password (CcWwanDataApn *apn) { const gchar *password = ""; g_return_val_if_fail (CC_IS_WWAN_DATA_APN (apn), ""); if (NM_IS_REMOTE_CONNECTION (apn->remote_connection)) { g_autoptr(GVariant) secrets = NULL; g_autoptr(GError) error = NULL; secrets = nm_remote_connection_get_secrets (NM_REMOTE_CONNECTION (apn->remote_connection), "gsm", NULL, &error); if (!error) nm_connection_update_secrets (NM_CONNECTION (apn->remote_connection), "gsm", secrets, &error); if (error) { g_warning ("Error: %s", error->message); return ""; } } if (apn->remote_connection) { NMSettingGsm *setting; setting = nm_connection_get_setting_gsm (NM_CONNECTION (apn->remote_connection)); password = nm_setting_gsm_get_password (setting); } else if (apn->access_method) { password = nma_mobile_access_method_get_password (apn->access_method); } return password ? password : ""; } /** * cc_wwan_data_apn_set_password: * @apn: A #CcWwanDataApn * @password: The password to be used * * Set the Password of @apn to @password. * * @apn is only modified, use @cc_wwan_data_save_apn() * to save the changes. */ void cc_wwan_data_apn_set_password (CcWwanDataApn *apn, const gchar *password) { NMConnection *connection; NMSettingGsm *setting; g_return_if_fail (CC_IS_WWAN_DATA_APN (apn)); if (password && !*password) password = NULL; if (g_strcmp0 (cc_wwan_data_apn_get_password (apn), password) == 0) return; apn->modified = TRUE; connection = wwan_data_get_nm_connection (apn); setting = nm_connection_get_setting_gsm (connection); g_object_set (G_OBJECT (setting), NM_SETTING_GSM_PASSWORD, password, NULL); } gint cc_wwan_data_get_priority (CcWwanData *self) { CcWwanDataApn *apn; NMSettingIPConfig *setting; g_return_val_if_fail (CC_IS_WWAN_DATA (self), CC_WWAN_APN_PRIORITY_LOW); apn = self->default_apn; if (!apn || !apn->remote_connection) return CC_WWAN_APN_PRIORITY_LOW; setting = nm_connection_get_setting_ip4_config (NM_CONNECTION (apn->remote_connection)); /* Lower the number, higher the priority */ if (nm_setting_ip_config_get_route_metric (setting) <= CC_WWAN_ROUTE_PRIORITY_HIGH) return CC_WWAN_APN_PRIORITY_HIGH; else return CC_WWAN_APN_PRIORITY_LOW; } void cc_wwan_data_set_priority (CcWwanData *self, int priority) { g_return_if_fail (CC_IS_WWAN_DATA (self)); g_return_if_fail (priority == CC_WWAN_APN_PRIORITY_LOW || priority == CC_WWAN_APN_PRIORITY_HIGH); if (self->priority == priority) return; self->priority = priority; if (self->default_apn) self->default_apn->modified = TRUE; }