diff options
Diffstat (limited to 'src/core/devices/wwan')
| -rw-r--r-- | src/core/devices/wwan/libnm-wwan.ver | 41 | ||||
| -rw-r--r-- | src/core/devices/wwan/meson.build | 88 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-device-modem.c | 957 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-device-modem.h | 36 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem-broadband.c | 1625 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem-broadband.h | 29 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem-manager.c | 876 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem-manager.h | 45 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem-ofono.c | 1239 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem-ofono.h | 35 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem.c | 2037 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-modem.h | 269 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-service-providers.c | 455 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-service-providers.h | 24 | ||||
| -rw-r--r-- | src/core/devices/wwan/nm-wwan-factory.c | 146 | ||||
| -rw-r--r-- | src/core/devices/wwan/tests/test-service-providers.c | 130 | ||||
| -rw-r--r-- | src/core/devices/wwan/tests/test-service-providers.xml | 73 |
17 files changed, 8105 insertions, 0 deletions
diff --git a/src/core/devices/wwan/libnm-wwan.ver b/src/core/devices/wwan/libnm-wwan.ver new file mode 100644 index 00000000..c368a590 --- /dev/null +++ b/src/core/devices/wwan/libnm-wwan.ver @@ -0,0 +1,41 @@ +{ +global: + nm_modem_act_stage1_prepare; + nm_modem_act_stage2_config; + nm_modem_check_connection_compatible; + nm_modem_claim; + nm_modem_complete_connection; + nm_modem_deactivate; + nm_modem_deactivate_async; + nm_modem_device_state_changed; + nm_modem_get_apn; + nm_modem_get_capabilities; + nm_modem_get_configured_mtu; + nm_modem_get_control_port; + nm_modem_get_device_id; + nm_modem_get_driver; + nm_modem_get_iid; + nm_modem_get_ip_ifindex; + nm_modem_get_operator_code; + nm_modem_get_path; + nm_modem_get_secrets; + nm_modem_get_state; + nm_modem_get_type; + nm_modem_get_uid; + nm_modem_ip4_pre_commit; + nm_modem_is_claimed; + nm_modem_manager_get; + nm_modem_manager_get_modems; + nm_modem_manager_get_type; + nm_modem_manager_name_owner_get; + nm_modem_manager_name_owner_ref; + nm_modem_manager_name_owner_unref; + nm_modem_owns_port; + nm_modem_set_mm_enabled; + nm_modem_stage3_ip4_config_start; + nm_modem_stage3_ip6_config_start; + nm_modem_state_to_string; + nm_modem_unclaim; +local: + *; +}; diff --git a/src/core/devices/wwan/meson.build b/src/core/devices/wwan/meson.build new file mode 100644 index 00000000..87af0429 --- /dev/null +++ b/src/core/devices/wwan/meson.build @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +wwan_inc = include_directories('.') + +linker_script = join_paths(meson.current_source_dir(), 'libnm-wwan.ver') + +libnm_wwan = shared_module( + 'nm-wwan', + sources: files( + 'nm-service-providers.c', + 'nm-modem-broadband.c', + 'nm-modem.c', + 'nm-modem-manager.c', + ) + (enable_ofono ? files('nm-modem-ofono.c') : files()), + dependencies: [ + core_plugin_dep, + libsystemd_dep, + mm_glib_dep, + ], + c_args: daemon_c_flags, + link_args: '-Wl,--version-script,@0@'.format(linker_script), + link_depends: linker_script, + install: true, + install_dir: nm_plugindir, +) + +libnm_wwan_dep = declare_dependency( + include_directories: wwan_inc, + link_with: libnm_wwan, +) + +core_plugins += libnm_wwan + +test( + 'check-wwan', + check_exports, + args: [ + libnm_wwan.full_path(), + linker_script, + ], +) + +libnm_device_plugin_wwan = shared_module( + 'nm-device-plugin-wwan', + sources: files( + 'nm-device-modem.c', + 'nm-wwan-factory.c', + ), + dependencies: [ + core_plugin_dep, + libsystemd_dep, + mm_glib_dep, + ], + c_args: daemon_c_flags, + link_with: libnm_wwan, + link_args: ldflags_linker_script_devices, + link_depends: linker_script_devices, + install: true, + install_dir: nm_plugindir, + install_rpath: nm_plugindir, +) + +core_plugins += libnm_device_plugin_wwan + +run_target( + 'check-local-devices-wwan', + command: [check_exports, libnm_device_plugin_wwan.full_path(), linker_script_devices], + depends: libnm_device_plugin_wwan, +) + +if enable_tests + exe = executable( + 'test-service-providers', + files( + 'tests/test-service-providers.c', + 'nm-service-providers.c', + ), + include_directories: wwan_inc, + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, + ) + test( + 'wwan/test-service-providers', + test_script, + timeout: default_test_timeout, + args: test_args + [exe.full_path()], + ) +endif diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c new file mode 100644 index 00000000..3ea89d2c --- /dev/null +++ b/src/core/devices/wwan/nm-device-modem.c @@ -0,0 +1,957 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2019 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-device-modem.h" + +#include "nm-modem.h" +#include "nm-ip4-config.h" +#include "devices/nm-device-private.h" +#include "nm-rfkill-manager.h" +#include "settings/nm-settings-connection.h" +#include "nm-modem-broadband.h" +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceModem +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceModem, + PROP_MODEM, + PROP_CAPABILITIES, + PROP_CURRENT_CAPABILITIES, + PROP_DEVICE_ID, + PROP_OPERATOR_CODE, + PROP_APN, ); + +typedef struct { + NMModem * modem; + NMDeviceModemCapabilities caps; + NMDeviceModemCapabilities current_caps; + char * device_id; + char * operator_code; + char * apn; + bool rf_enabled : 1; + NMDeviceStageState stage1_state : 3; +} NMDeviceModemPrivate; + +struct _NMDeviceModem { + NMDevice parent; + NMDeviceModemPrivate _priv; +}; + +struct _NMDeviceModemClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceModem, nm_device_modem, NM_TYPE_DEVICE) + +#define NM_DEVICE_MODEM_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceModem, NM_IS_DEVICE_MODEM, NMDevice) + +/*****************************************************************************/ + +static void +ppp_failed(NMModem *modem, guint i_reason, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceStateReason reason = i_reason; + + switch (nm_device_get_state(device)) { + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason); + break; + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + case NM_DEVICE_STATE_ACTIVATED: + if (nm_device_activate_ip4_state_in_conf(device)) + nm_device_activate_schedule_ip_config_timeout(device, AF_INET); + else if (nm_device_activate_ip6_state_in_conf(device)) + nm_device_activate_schedule_ip_config_timeout(device, AF_INET6); + else if (nm_device_activate_ip4_state_done(device)) { + nm_device_ip_method_failed(device, + AF_INET, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else if (nm_device_activate_ip6_state_done(device)) { + nm_device_ip_method_failed(device, + AF_INET6, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else { + _LOGW(LOGD_MB, + "PPP failure in unexpected state %u", + (guint) nm_device_get_state(device)); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + break; + default: + break; + } +} + +static void +modem_prepare_result(NMModem *modem, gboolean success, guint i_reason, gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceStateReason reason = i_reason; + + if (nm_device_get_state(device) != NM_DEVICE_STATE_PREPARE + || priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING) { + nm_assert_not_reached(); + success = FALSE; + } + + if (!success) { + /* There are several reasons to block autoconnection at device level: + * + * - Wrong SIM-PIN: The device won't autoconnect because it doesn't make sense + * to retry the connection with the same PIN. This error also makes autoconnection + * blocked at settings level, so not even a modem unplug and replug will allow + * autoconnection again. It is somewhat redundant to block autoconnection at + * both device and setting level really. + * + * - SIM wrong or not inserted: If the modem is reporting a SIM not inserted error, + * we can block autoconnection at device level, so that if the same device is + * unplugged and replugged with a SIM (or if a SIM hotplug event happens in MM, + * recreating the device completely), we can try the autoconnection again. + * + * - Modem initialization failed: For some reason unknown to NM, the modem wasn't + * initialized correctly, which leads to an unusable device. A device unplug and + * replug may solve the issue, so make it a device-level autoconnection blocking + * reason. + */ + switch (nm_device_state_reason_check(reason)) { + case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: + case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: + case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: + nm_device_autoconnect_blocked_set(device, NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN); + break; + case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED: + case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG: + nm_device_autoconnect_blocked_set(device, NM_DEVICE_AUTOCONNECT_BLOCKED_SIM_MISSING); + break; + case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + nm_device_autoconnect_blocked_set(device, NM_DEVICE_AUTOCONNECT_BLOCKED_INIT_FAILED); + break; + default: + break; + } + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason); + return; + } + + priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED; + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +modem_auth_requested(NMModem *modem, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + /* Auth requests (PIN, PAP/CHAP passwords, etc) only get handled + * during activation. + */ + if (!nm_device_is_activating(device)) + return; + + nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); +} + +static void +modem_auth_result(NMModem *modem, GError *error, gpointer user_data) +{ + NMDevice * device = NM_DEVICE(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH); + + if (error) { + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + return; + } + + priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT; + nm_device_activate_schedule_stage1_device_prepare(device, FALSE); +} + +static void +modem_ip4_config_result(NMModem *modem, NMIP4Config *config, GError *error, gpointer user_data) +{ + NMDeviceModem *self = NM_DEVICE_MODEM(user_data); + NMDevice * device = NM_DEVICE(self); + + g_return_if_fail(nm_device_activate_ip4_state_in_conf(device) == TRUE); + + if (error) { + _LOGW(LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: %s", error->message); + nm_device_ip_method_failed(device, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else { + nm_device_set_dev2_ip_config(device, AF_INET, NM_IP_CONFIG_CAST(config)); + nm_device_activate_schedule_ip_config_result(device, AF_INET, NULL); + } +} + +static void +modem_ip6_config_result(NMModem * modem, + NMIP6Config *config, + gboolean do_slaac, + GError * error, + gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDevice * device = NM_DEVICE(self); + NMActStageReturn ret; + NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE; + gs_unref_object NMIP6Config *ignored = NULL; + gboolean got_config = !!config; + + g_return_if_fail(nm_device_activate_ip6_state_in_conf(device) == TRUE); + + if (error) { + _LOGW(LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s", error->message); + nm_device_ip_method_failed(device, AF_INET6, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } + + /* Re-enable IPv6 on the interface */ + nm_device_sysctl_ip_conf_set(device, AF_INET6, "disable_ipv6", "0"); + + if (config) + nm_device_set_dev2_ip_config(device, AF_INET6, NM_IP_CONFIG_CAST(config)); + + if (do_slaac == FALSE) { + if (got_config) + nm_device_activate_schedule_ip_config_result(device, AF_INET6, NULL); + else { + _LOGW(LOGD_MB | LOGD_IP6, + "retrieving IPv6 configuration failed: SLAAC not requested and no addresses"); + nm_device_ip_method_failed(device, + AF_INET6, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } + return; + } + + /* Start SLAAC now that we have a link-local address from the modem */ + ret = + NM_DEVICE_CLASS(nm_device_modem_parent_class) + ->act_stage3_ip_config_start(device, AF_INET6, (gpointer *) &ignored, &failure_reason); + + nm_assert(ignored == NULL); + + switch (ret) { + case NM_ACT_STAGE_RETURN_FAILURE: + nm_device_ip_method_failed(device, AF_INET6, failure_reason); + break; + case NM_ACT_STAGE_RETURN_IP_FAIL: + /* all done */ + nm_device_activate_schedule_ip_config_result(device, AF_INET6, NULL); + break; + case NM_ACT_STAGE_RETURN_POSTPONE: + /* let SLAAC run */ + break; + default: + /* Should never get here since we've assured that the IPv6 method + * will either be "auto" or "ignored" when starting IPv6 configuration. + */ + nm_assert_not_reached(); + } +} + +static void +ip_ifindex_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDevice *device = NM_DEVICE(user_data); + + if (!nm_device_is_activating(device)) + return; + + if (!nm_device_set_ip_ifindex(device, nm_modem_get_ip_ifindex(modem))) { + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } + + /* Disable IPv6 immediately on the interface since NM handles IPv6 + * internally, and leaving it enabled could allow the kernel's IPv6 + * RA handling code to run before NM is ready. + */ + nm_device_sysctl_ip_conf_set(device, AF_INET6, "disable_ipv6", "1"); +} + +static void +operator_code_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + const char * operator_code = nm_modem_get_operator_code(modem); + + if (g_strcmp0(priv->operator_code, operator_code) != 0) { + g_free(priv->operator_code); + priv->operator_code = g_strdup(operator_code); + _notify(self, PROP_OPERATOR_CODE); + } +} + +static void +apn_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + const char * apn = nm_modem_get_apn(modem); + + if (g_strcmp0(priv->apn, apn) != 0) { + g_free(priv->apn); + priv->apn = g_strdup(apn); + _notify(self, PROP_APN); + } +} + +static void +ids_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + nm_device_recheck_available_connections(NM_DEVICE(user_data)); +} + +static void +modem_state_cb(NMModem *modem, int new_state_i, int old_state_i, gpointer user_data) +{ + NMModemState new_state = new_state_i; + NMModemState old_state = old_state_i; + NMDevice * device = NM_DEVICE(user_data); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + NMDeviceState dev_state = nm_device_get_state(device); + + if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING + && priv->rf_enabled) { + /* Called when the ModemManager modem enabled state is changed externally + * to NetworkManager (eg something using MM's D-Bus API directly). + */ + if (nm_device_is_activating(device) || dev_state == NM_DEVICE_STATE_ACTIVATED) { + /* user-initiated action, hence DISCONNECTED not FAILED */ + nm_device_state_changed(device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + return; + } + } + + if (new_state < NM_MODEM_STATE_CONNECTING && old_state >= NM_MODEM_STATE_CONNECTING + && dev_state >= NM_DEVICE_STATE_NEED_AUTH && dev_state <= NM_DEVICE_STATE_ACTIVATED) { + /* Fail the device if the modem disconnects unexpectedly while the + * device is activating/activated. */ + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return; + } + + if (new_state > NM_MODEM_STATE_LOCKED && old_state == NM_MODEM_STATE_LOCKED) { + /* If the modem is now unlocked, enable/disable it according to the + * device's enabled/disabled state. + */ + nm_modem_set_mm_enabled(priv->modem, priv->rf_enabled); + + if (dev_state == NM_DEVICE_STATE_NEED_AUTH) { + /* The modem was unlocked externally to NetworkManager, + * deactivate so the default connection can be + * automatically activated again */ + nm_device_state_changed(device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_MODEM_AVAILABLE); + } + + /* Now allow connections without a PIN to be available */ + nm_device_recheck_available_connections(device); + } + + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, + NM_DEVICE_STATE_REASON_MODEM_FAILED); +} + +static void +modem_removed_cb(NMModem *modem, gpointer user_data) +{ + g_signal_emit_by_name(NM_DEVICE(user_data), NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +static gboolean +owns_iface(NMDevice *device, const char *iface) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + g_return_val_if_fail(priv->modem, FALSE); + + return nm_modem_owns_port(priv->modem, iface); +} + +/*****************************************************************************/ + +static void +device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + + g_return_if_fail(priv->modem); + + if (new_state == NM_DEVICE_STATE_UNAVAILABLE && old_state < NM_DEVICE_STATE_UNAVAILABLE) { + /* Log initial modem state */ + _LOGI(LOGD_MB, + "modem state '%s'", + nm_modem_state_to_string(nm_modem_get_state(priv->modem))); + } + nm_modem_device_state_changed(priv->modem, new_state, old_state); +} + +static NMDeviceCapabilities +get_generic_capabilities(NMDevice *device) +{ + return NM_DEVICE_CAP_IS_NON_KERNEL; +} + +static const char * +get_type_description(NMDevice *device) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + if (NM_FLAGS_HAS(priv->current_caps, NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS)) + return "gsm"; + if (NM_FLAGS_HAS(priv->current_caps, NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO)) + return "cdma"; + return NM_DEVICE_CLASS(nm_device_modem_parent_class)->get_type_description(device); +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + GError *local = NULL; + + if (!NM_DEVICE_CLASS(nm_device_modem_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + if (!nm_modem_check_connection_compatible(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, + connection, + error ? &local : NULL)) { + if (error) { + g_set_error(error, + NM_UTILS_ERROR, + g_error_matches(local, + NM_UTILS_ERROR, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE) + ? NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE + : NM_UTILS_ERROR_UNKNOWN, + "modem is incompatible with connection: %s", + local->message); + g_error_free(local); + } + return FALSE; + } + return TRUE; +} + +static gboolean +check_connection_available(NMDevice * device, + NMConnection * connection, + NMDeviceCheckConAvailableFlags flags, + const char * specific_object, + GError ** error) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + NMModemState state; + + if (!priv->rf_enabled) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "RFKILL for modem enabled"); + return FALSE; + } + + if (!priv->modem) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem not available"); + return FALSE; + } + + state = nm_modem_get_state(priv->modem); + if (state <= NM_MODEM_STATE_INITIALIZING) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem not initialized"); + return FALSE; + } + + if (state == NM_MODEM_STATE_LOCKED) { + if (!nm_connection_get_setting_gsm(connection)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem is locked without pin available"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice * device, + NMConnection * connection, + const char * specific_object, + NMConnection *const *existing_connections, + GError ** error) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + return nm_modem_complete_connection(priv->modem, + nm_device_get_iface(device), + connection, + existing_connections, + error); +} + +static void +deactivate(NMDevice *device) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + nm_modem_deactivate(priv->modem, device); + priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT; +} + +/*****************************************************************************/ + +static void +modem_deactivate_async_cb(NMModem *modem, GError *error, gpointer user_data) +{ + gs_unref_object NMDevice * self = NULL; + NMDeviceDeactivateCallback callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + callback(self, error, callback_user_data); +} + +static void +deactivate_async(NMDevice * self, + GCancellable * cancellable, + NMDeviceDeactivateCallback callback, + gpointer user_data) +{ + nm_assert(G_IS_CANCELLABLE(cancellable)); + nm_assert(callback); + + nm_modem_deactivate_async(NM_DEVICE_MODEM_GET_PRIVATE(self)->modem, + self, + cancellable, + modem_deactivate_async_cb, + nm_utils_user_data_pack(g_object_ref(self), callback, user_data)); +} + +/*****************************************************************************/ + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + NMActRequest * req; + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + if (priv->stage1_state == NM_DEVICE_STAGE_STATE_INIT) { + priv->stage1_state = NM_DEVICE_STAGE_STATE_PENDING; + return nm_modem_act_stage1_prepare(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, + req, + out_failure_reason); + } + + if (priv->stage1_state == NM_DEVICE_STAGE_STATE_PENDING) + return NM_ACT_STAGE_RETURN_POSTPONE; + + nm_assert(priv->stage1_state == NM_DEVICE_STAGE_STATE_COMPLETED); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + nm_modem_act_stage2_config(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static NMActStageReturn +act_stage3_ip_config_start(NMDevice * device, + int addr_family, + gpointer * out_config, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + + nm_assert_addr_family(addr_family); + + if (addr_family == AF_INET) { + return nm_modem_stage3_ip4_config_start(priv->modem, + device, + NM_DEVICE_CLASS(nm_device_modem_parent_class), + out_failure_reason); + } else { + return nm_modem_stage3_ip6_config_start(priv->modem, device, out_failure_reason); + } +} + +static void +ip4_config_pre_commit(NMDevice *device, NMIP4Config *config) +{ + nm_modem_ip4_pre_commit(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, device, config); +} + +static gboolean +get_ip_iface_identifier(NMDevice *device, NMUtilsIPv6IfaceId *out_iid) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + gboolean success; + + g_return_val_if_fail(priv->modem, FALSE); + success = nm_modem_get_iid(priv->modem, out_iid); + if (!success) + success = + NM_DEVICE_CLASS(nm_device_modem_parent_class)->get_ip_iface_identifier(device, out_iid); + return success; +} + +/*****************************************************************************/ + +static gboolean +get_enabled(NMDevice *device) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device); + NMModemState modem_state = nm_modem_get_state(priv->modem); + + return priv->rf_enabled && (modem_state >= NM_MODEM_STATE_LOCKED); +} + +static void +set_enabled(NMDevice *device, gboolean enabled) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + + /* Called only by the Manager in response to rfkill switch changes or + * global user WWAN enable/disable preference changes. + */ + priv->rf_enabled = enabled; + + if (priv->modem) { + /* Sync the ModemManager modem enabled/disabled with rfkill/user preference */ + nm_modem_set_mm_enabled(priv->modem, enabled); + } + + if (enabled == FALSE) { + nm_device_state_changed(device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_NONE); + } +} + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceModem * self = NM_DEVICE_MODEM(device); + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + NMModemState modem_state; + + if (!priv->rf_enabled) + return FALSE; + + g_assert(priv->modem); + modem_state = nm_modem_get_state(priv->modem); + if (modem_state <= NM_MODEM_STATE_INITIALIZING) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +static void +set_modem(NMDeviceModem *self, NMModem *modem) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + + g_return_if_fail(modem != NULL); + + priv->modem = nm_modem_claim(modem); + + g_signal_connect(modem, NM_MODEM_PPP_FAILED, G_CALLBACK(ppp_failed), self); + g_signal_connect(modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK(modem_prepare_result), self); + g_signal_connect(modem, NM_MODEM_IP4_CONFIG_RESULT, G_CALLBACK(modem_ip4_config_result), self); + g_signal_connect(modem, NM_MODEM_IP6_CONFIG_RESULT, G_CALLBACK(modem_ip6_config_result), self); + g_signal_connect(modem, NM_MODEM_AUTH_REQUESTED, G_CALLBACK(modem_auth_requested), self); + g_signal_connect(modem, NM_MODEM_AUTH_RESULT, G_CALLBACK(modem_auth_result), self); + g_signal_connect(modem, NM_MODEM_STATE_CHANGED, G_CALLBACK(modem_state_cb), self); + g_signal_connect(modem, NM_MODEM_REMOVED, G_CALLBACK(modem_removed_cb), self); + + g_signal_connect(modem, + "notify::" NM_MODEM_IP_IFINDEX, + G_CALLBACK(ip_ifindex_changed_cb), + self); + g_signal_connect(modem, "notify::" NM_MODEM_DEVICE_ID, G_CALLBACK(ids_changed_cb), self); + g_signal_connect(modem, "notify::" NM_MODEM_SIM_ID, G_CALLBACK(ids_changed_cb), self); + g_signal_connect(modem, "notify::" NM_MODEM_SIM_OPERATOR_ID, G_CALLBACK(ids_changed_cb), self); + g_signal_connect(modem, + "notify::" NM_MODEM_OPERATOR_CODE, + G_CALLBACK(operator_code_changed_cb), + self); + g_signal_connect(modem, "notify::" NM_MODEM_APN, G_CALLBACK(apn_changed_cb), self); +} + +static guint32 +get_dhcp_timeout_for_device(NMDevice *device, int addr_family) +{ + /* DHCP is always done by the modem firmware, not by the network, and + * by the time we get around to DHCP the firmware should already know + * the IP addressing details. So the DHCP timeout can be much shorter. + */ + return 15; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object(value, priv->modem); + break; + case PROP_CAPABILITIES: + g_value_set_uint(value, priv->caps); + break; + case PROP_CURRENT_CAPABILITIES: + g_value_set_uint(value, priv->current_caps); + break; + case PROP_DEVICE_ID: + g_value_set_string(value, priv->device_id); + break; + case PROP_OPERATOR_CODE: + g_value_set_string(value, priv->operator_code); + break; + case PROP_APN: + g_value_set_string(value, priv->apn); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MODEM: + /* construct-only */ + set_modem(NM_DEVICE_MODEM(object), g_value_get_object(value)); + break; + case PROP_CAPABILITIES: + priv->caps = g_value_get_uint(value); + break; + case PROP_CURRENT_CAPABILITIES: + priv->current_caps = g_value_get_uint(value); + break; + case PROP_DEVICE_ID: + /* construct-only */ + priv->device_id = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_modem_init(NMDeviceModem *self) +{} + +NMDevice * +nm_device_modem_new(NMModem *modem) +{ + NMDeviceModemCapabilities caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + NMDeviceModemCapabilities current_caps = NM_DEVICE_MODEM_CAPABILITY_NONE; + + g_return_val_if_fail(NM_IS_MODEM(modem), NULL); + + /* Load capabilities */ + nm_modem_get_capabilities(modem, &caps, ¤t_caps); + + return g_object_new(NM_TYPE_DEVICE_MODEM, + NM_DEVICE_UDI, + nm_modem_get_path(modem), + NM_DEVICE_IFACE, + nm_modem_get_uid(modem), + NM_DEVICE_DRIVER, + nm_modem_get_driver(modem), + NM_DEVICE_TYPE_DESC, + "Broadband", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_MODEM, + NM_DEVICE_RFKILL_TYPE, + RFKILL_TYPE_WWAN, + NM_DEVICE_MODEM_MODEM, + modem, + NM_DEVICE_MODEM_CAPABILITIES, + caps, + NM_DEVICE_MODEM_CURRENT_CAPABILITIES, + current_caps, + NM_DEVICE_MODEM_DEVICE_ID, + nm_modem_get_device_id(modem), + NULL); +} + +static void +dispose(GObject *object) +{ + NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(object); + + if (priv->modem) { + g_signal_handlers_disconnect_by_data(priv->modem, NM_DEVICE_MODEM(object)); + nm_clear_pointer(&priv->modem, nm_modem_unclaim); + } + + nm_clear_g_free(&priv->device_id); + nm_clear_g_free(&priv->operator_code); + nm_clear_g_free(&priv->apn); + + G_OBJECT_CLASS(nm_device_modem_parent_class)->dispose(object); +} + +static const NMDBusInterfaceInfoExtended interface_info_device_modem = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_MODEM, + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ModemCapabilities", + "u", + NM_DEVICE_MODEM_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("CurrentCapabilities", + "u", + NM_DEVICE_MODEM_CURRENT_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DeviceId", + "s", + NM_DEVICE_MODEM_DEVICE_ID), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("OperatorCode", + "s", + NM_DEVICE_MODEM_OPERATOR_CODE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Apn", "s", NM_DEVICE_MODEM_APN), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_device_modem_class_init(NMDeviceModemClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass * device_class = NM_DEVICE_CLASS(klass); + + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_modem); + + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->get_type_description = get_type_description; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + device_class->complete_connection = complete_connection; + device_class->deactivate_async = deactivate_async; + device_class->deactivate = deactivate; + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->act_stage3_ip_config_start = act_stage3_ip_config_start; + device_class->ip4_config_pre_commit = ip4_config_pre_commit; + device_class->get_enabled = get_enabled; + device_class->set_enabled = set_enabled; + device_class->owns_iface = owns_iface; + device_class->is_available = is_available; + device_class->get_ip_iface_identifier = get_ip_iface_identifier; + device_class->get_configured_mtu = nm_modem_get_configured_mtu; + device_class->get_dhcp_timeout_for_device = get_dhcp_timeout_for_device; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_MODEM] = + g_param_spec_object(NM_DEVICE_MODEM_MODEM, + "", + "", + NM_TYPE_MODEM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_MODEM_CAPABILITIES, + "", + "", + 0, + G_MAXUINT32, + NM_DEVICE_MODEM_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CURRENT_CAPABILITIES] = + g_param_spec_uint(NM_DEVICE_MODEM_CURRENT_CAPABILITIES, + "", + "", + 0, + G_MAXUINT32, + NM_DEVICE_MODEM_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEVICE_ID] = + g_param_spec_string(NM_DEVICE_MODEM_DEVICE_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_OPERATOR_CODE] = + g_param_spec_string(NM_DEVICE_MODEM_OPERATOR_CODE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_APN] = g_param_spec_string(NM_DEVICE_MODEM_APN, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wwan/nm-device-modem.h b/src/core/devices/wwan/nm-device-modem.h new file mode 100644 index 00000000..f171d76f --- /dev/null +++ b/src/core/devices/wwan/nm-device-modem.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_MODEM_H__ +#define __NETWORKMANAGER_DEVICE_MODEM_H__ + +#include "devices/nm-device.h" +#include "nm-modem.h" + +#define NM_TYPE_DEVICE_MODEM (nm_device_modem_get_type()) +#define NM_DEVICE_MODEM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModem)) +#define NM_DEVICE_MODEM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_MODEM, NMDeviceModemClass)) +#define NM_IS_DEVICE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_MODEM)) +#define NM_IS_DEVICE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_MODEM)) +#define NM_DEVICE_MODEM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModemClass)) + +#define NM_DEVICE_MODEM_MODEM "modem" +#define NM_DEVICE_MODEM_CAPABILITIES "modem-capabilities" +#define NM_DEVICE_MODEM_CURRENT_CAPABILITIES "current-capabilities" +#define NM_DEVICE_MODEM_DEVICE_ID "device-id" +#define NM_DEVICE_MODEM_OPERATOR_CODE "operator-code" +#define NM_DEVICE_MODEM_APN "apn" + +typedef struct _NMDeviceModem NMDeviceModem; +typedef struct _NMDeviceModemClass NMDeviceModemClass; + +GType nm_device_modem_get_type(void); + +NMDevice *nm_device_modem_new(NMModem *modem); + +#endif /* __NETWORKMANAGER_DEVICE_MODEM_H__ */ diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c new file mode 100644 index 00000000..ca028804 --- /dev/null +++ b/src/core/devices/wwan/nm-modem-broadband.c @@ -0,0 +1,1625 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-modem-broadband.h" +#include "nm-service-providers.h" + +#include <arpa/inet.h> +#include <libmm-glib.h> + +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" +#include "devices/nm-device-private.h" +#include "platform/nm-platform.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" + +#define NM_MODEM_BROADBAND_MODEM "modem" + +static gboolean +MODEM_CAPS_3GPP(MMModemCapability caps) +{ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + /* MM_MODEM_CAPABILITY_LTE_ADVANCED is marked as deprecated since ModemManager 1.14.0. + * + * The flag probably was never used, it certainly isn't used since 1.14.0. + * + * Still, just to be sure, there is no harm in checking it here. Suppress the + * warning, it should have no bad effect. + */ + return NM_FLAGS_ANY(caps, + (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE + | MM_MODEM_CAPABILITY_LTE_ADVANCED)); + G_GNUC_END_IGNORE_DEPRECATIONS +} + +#define MODEM_CAPS_3GPP2(caps) (caps & (MM_MODEM_CAPABILITY_CDMA_EVDO)) + +/* Maximum time to keep the DBus call waiting for a connection result. + * This value is greater than the default timeout in ModemManager (180s since + * 1.16), so that whenever possible the timeout happens first there instead of + * in NetworkManager. */ +#define MODEM_CONNECT_TIMEOUT_SECS 200 + +/*****************************************************************************/ + +typedef enum { + CONNECT_STEP_FIRST, + CONNECT_STEP_WAIT_FOR_SIM, + CONNECT_STEP_UNLOCK, + CONNECT_STEP_WAIT_FOR_READY, + CONNECT_STEP_CONNECT, + CONNECT_STEP_LAST, +} ConnectStep; + +typedef struct { + NMModemBroadband *self; + ConnectStep step; + + MMModemCapability caps; + NMConnection * connection; + GCancellable * cancellable; + MMSimpleConnectProperties *connect_properties; + GArray * ip_types; + guint ip_types_i; + guint ip_type_tries; + GError * first_error; +} ConnectContext; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_MODEM, ); + +typedef struct { + /* The modem object from dbus */ + MMObject *modem_object; + /* Per-interface objects */ + MMModem * modem_iface; + MMModem3gpp * modem_3gpp_iface; + MMModemSimple *simple_iface; + MMSim * sim_iface; + + /* Connection setup */ + ConnectContext *ctx; + + MMBearer * bearer; + MMBearerIpConfig *ipv4_config; + MMBearerIpConfig *ipv6_config; + + guint idle_id_ip4; + guint idle_id_ip6; + + guint32 pin_tries; +} NMModemBroadbandPrivate; + +struct _NMModemBroadband { + NMModem parent; + NMModemBroadbandPrivate _priv; +}; + +struct _NMModemBroadbandClass { + NMModemClass parent; +}; + +G_DEFINE_TYPE(NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM) + +#define NM_MODEM_BROADBAND_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMModemBroadband, NM_IS_MODEM_BROADBAND) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_MB +#define _NMLOG_PREFIX_NAME "modem-broadband" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) { \ + NMModemBroadband *const __self = (self); \ + char __prefix_name[128]; \ + const char * __uid; \ + \ + _nm_log(_level, \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + ((__self && __self->_priv.ctx) \ + ? nm_connection_get_uuid(__self->_priv.ctx->connection) \ + : NULL), \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (__self ? ({ \ + ((__uid = nm_modem_get_uid((NMModem *) __self)) \ + ? nm_sprintf_buf(__prefix_name, "[%s]", __uid) \ + : "(null)"); \ + }) \ + : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static NMDeviceStateReason +translate_mm_error(NMModemBroadband *self, GError *error) +{ + NMDeviceStateReason reason; + + g_return_val_if_fail(error != NULL, NM_DEVICE_STATE_REASON_UNKNOWN); + + if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_CARRIER)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER; + else if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_DIALTONE)) + reason = NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE; + else if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_BUSY)) + reason = NM_DEVICE_STATE_REASON_MODEM_BUSY; + else if (g_error_matches(error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_ANSWER)) + reason = NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_NOT_ALLOWED)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK)) + reason = NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED; + else if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED; + else if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED; + else if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG)) + reason = NM_DEVICE_STATE_REASON_GSM_SIM_WRONG; + else if (g_error_matches(error, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD)) + reason = NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT; + else { + /* unable to map the ModemManager error to a NM_DEVICE_STATE_REASON */ + _LOGD("unmapped error detected: '%s'", error->message); + reason = NM_DEVICE_STATE_REASON_UNKNOWN; + } + + return reason; +} + +/*****************************************************************************/ + +static void +get_capabilities(NMModem * _self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(_self); + MMModemCapability all_supported = MM_MODEM_CAPABILITY_NONE; + MMModemCapability *supported; + guint n_supported; + + /* For now, we don't care about the capability combinations, just merge all + * combinations in a single mask */ + if (mm_modem_get_supported_capabilities(self->_priv.modem_iface, &supported, &n_supported)) { + guint i; + + for (i = 0; i < n_supported; i++) + all_supported |= supported[i]; + + g_free(supported); + } + + *modem_caps = (NMDeviceModemCapabilities) all_supported; + *current_caps = + (NMDeviceModemCapabilities) mm_modem_get_current_capabilities(self->_priv.modem_iface); +} + +static gboolean +owns_port(NMModem *_self, const char *iface) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(_self); + const MMModemPortInfo *ports = NULL; + guint n_ports = 0, i; + + mm_modem_peek_ports(self->_priv.modem_iface, &ports, &n_ports); + for (i = 0; i < n_ports; i++) { + if (nm_streq0(iface, ports[i].name)) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +static void +ask_for_pin(NMModemBroadband *self) +{ + guint32 tries; + + tries = self->_priv.pin_tries++; + nm_modem_get_secrets(NM_MODEM(self), + NM_SETTING_GSM_SETTING_NAME, + tries ? TRUE : FALSE, + NM_SETTING_GSM_PIN); +} + +static NMModemIPMethod +get_bearer_ip_method(MMBearerIpConfig *config) +{ + MMBearerIpMethod mm_method; + + mm_method = mm_bearer_ip_config_get_method(config); + if (mm_method == MM_BEARER_IP_METHOD_PPP) + return NM_MODEM_IP_METHOD_PPP; + else if (mm_method == MM_BEARER_IP_METHOD_STATIC) + return NM_MODEM_IP_METHOD_STATIC; + else if (mm_method == MM_BEARER_IP_METHOD_DHCP) + return NM_MODEM_IP_METHOD_AUTO; + return NM_MODEM_IP_METHOD_UNKNOWN; +} + +static MMSimpleConnectProperties * +create_cdma_connect_properties(NMConnection *connection) +{ + MMSimpleConnectProperties *properties; + + properties = mm_simple_connect_properties_new(); + +#if !MM_CHECK_VERSION(1, 9, 1) + { + NMSettingCdma *setting; + const char * str; + + setting = nm_connection_get_setting_cdma(connection); + str = nm_setting_cdma_get_number(setting); + if (str) + mm_simple_connect_properties_set_number(properties, str); + } +#endif + + return properties; +} + +static MMSimpleConnectProperties * +create_gsm_connect_properties(NMConnection *connection, + const char * apn, + const char * username, + const char * password) +{ + NMSettingGsm * setting; + NMSettingPpp * s_ppp; + MMSimpleConnectProperties *properties; + const char * str; + + setting = nm_connection_get_setting_gsm(connection); + + properties = mm_simple_connect_properties_new(); + + mm_simple_connect_properties_set_apn(properties, apn ?: ""); + if (username) + mm_simple_connect_properties_set_user(properties, username); + if (password) + mm_simple_connect_properties_set_password(properties, password); + + str = nm_setting_gsm_get_network_id(setting); + if (str) + mm_simple_connect_properties_set_operator_id(properties, str); + + str = nm_setting_gsm_get_pin(setting); + if (str) + mm_simple_connect_properties_set_pin(properties, str); + + /* Roaming */ + if (nm_setting_gsm_get_home_only(setting)) + mm_simple_connect_properties_set_allow_roaming(properties, FALSE); + + /* For IpMethod == STATIC or DHCP */ + s_ppp = nm_connection_get_setting_ppp(connection); + if (s_ppp) { + MMBearerAllowedAuth allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + + if (nm_setting_ppp_get_noauth(s_ppp)) + allowed_auth = MM_BEARER_ALLOWED_AUTH_NONE; + if (!nm_setting_ppp_get_refuse_pap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_PAP; + if (!nm_setting_ppp_get_refuse_chap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_CHAP; + if (!nm_setting_ppp_get_refuse_mschap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAP; + if (!nm_setting_ppp_get_refuse_mschapv2(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAPV2; + if (!nm_setting_ppp_get_refuse_eap(s_ppp)) + allowed_auth |= MM_BEARER_ALLOWED_AUTH_EAP; + + mm_simple_connect_properties_set_allowed_auth(properties, allowed_auth); + } + + return properties; +} + +static void +connect_context_clear(NMModemBroadband *self) +{ + if (self->_priv.ctx) { + ConnectContext *ctx = self->_priv.ctx; + + g_clear_error(&ctx->first_error); + nm_clear_pointer(&ctx->ip_types, g_array_unref); + nm_clear_g_cancellable(&ctx->cancellable); + g_clear_object(&ctx->connection); + g_clear_object(&ctx->connect_properties); + g_clear_object(&ctx->self); + g_slice_free(ConnectContext, ctx); + self->_priv.ctx = NULL; + } +} + +static void connect_context_step(NMModemBroadband *self); + +static void +connect_ready(MMModemSimple *simple_iface, GAsyncResult *res, NMModemBroadband *self) +{ + ConnectContext *ctx; + GError * error = NULL; + NMModemIPMethod ip4_method = NM_MODEM_IP_METHOD_UNKNOWN; + NMModemIPMethod ip6_method = NM_MODEM_IP_METHOD_UNKNOWN; + MMBearer * bearer; + + bearer = mm_modem_simple_connect_finish(simple_iface, res, &error); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + return; + } + + ctx = self->_priv.ctx; + + if (!ctx) + return; + + self->_priv.bearer = bearer; + + if (!self->_priv.bearer) { + if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) + || (g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED) + && mm_modem_get_unlock_required(self->_priv.modem_iface) + == MM_MODEM_LOCK_SIM_PIN)) { + g_error_free(error); + + /* Request PIN */ + ask_for_pin(self); + connect_context_clear(self); + return; + } + + /* Save the error, if it's the first one */ + if (!ctx->first_error) { + /* Strip remote error info before saving it */ + if (g_dbus_error_is_remote_error(error)) + g_dbus_error_strip_remote_error(error); + ctx->first_error = error; + } else + g_clear_error(&error); + + if (ctx->ip_type_tries == 0 && g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY)) { + /* Try one more time */ + ctx->ip_type_tries++; + } else { + /* If the modem/provider lies and the IP type we tried isn't supported, + * retry with the next one, if any. + */ + ctx->ip_types_i++; + ctx->ip_type_tries = 0; + } + connect_context_step(self); + return; + } + + /* Grab IP configurations */ + self->_priv.ipv4_config = mm_bearer_get_ipv4_config(self->_priv.bearer); + if (self->_priv.ipv4_config) + ip4_method = get_bearer_ip_method(self->_priv.ipv4_config); + + self->_priv.ipv6_config = mm_bearer_get_ipv6_config(self->_priv.bearer); + if (self->_priv.ipv6_config) + ip6_method = get_bearer_ip_method(self->_priv.ipv6_config); + + if (!nm_modem_set_data_port(NM_MODEM(self), + NM_PLATFORM_GET, + mm_bearer_get_interface(self->_priv.bearer), + ip4_method, + ip6_method, + mm_bearer_get_ip_timeout(self->_priv.bearer), + &error)) { + _LOGW("failed to connect modem: %s", error->message); + g_error_free(error); + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + connect_context_clear(self); + return; + } + + ctx->step++; + connect_context_step(self); +} + +static void +send_pin_ready(MMSim *sim, GAsyncResult *result, NMModemBroadband *self) +{ + gs_free_error GError *error = NULL; + + mm_sim_send_pin_finish(sim, result, &error); + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (!self->_priv.ctx || self->_priv.ctx->step != CONNECT_STEP_UNLOCK) + g_return_if_reached(); + + if (error) { + if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) + || (g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED) + && mm_modem_get_unlock_required(self->_priv.modem_iface) == MM_MODEM_LOCK_SIM_PIN)) + ask_for_pin(self); + else + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, translate_mm_error(self, error)); + return; + } + + self->_priv.ctx->step++; + connect_context_step(self); +} + +static void +find_gsm_apn_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + NMModemBroadband * self = user_data; + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + ConnectContext * ctx = priv->ctx; + + if (error) { + _LOGW("failed to connect '%s': APN not found: %s", + nm_connection_get_id(ctx->connection), + error->message); + + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_GSM_APN_FAILED); + connect_context_clear(self); + return; + } + + /* Blank APN ("") means the default subscription APN */ + ctx->connect_properties = + create_gsm_connect_properties(ctx->connection, apn, username, password); + g_return_if_fail(ctx->connect_properties); + connect_context_step(self); +} + +static gboolean +try_create_connect_properties(NMModemBroadband *self) +{ + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + ConnectContext * ctx = priv->ctx; + + if (MODEM_CAPS_3GPP(ctx->caps)) { + NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(ctx->connection); + + if (!s_gsm || nm_setting_gsm_get_auto_config(s_gsm)) { + gs_unref_object MMModem3gpp *modem_3gpp = NULL; + const char * network_id = NULL; + + s_gsm = nm_connection_get_setting_gsm(ctx->connection); + if (s_gsm) + network_id = nm_setting_gsm_get_network_id(s_gsm); + if (!network_id) { + if (mm_modem_get_state(self->_priv.modem_iface) < MM_MODEM_STATE_REGISTERED) + return FALSE; + modem_3gpp = mm_object_get_modem_3gpp(priv->modem_object); + network_id = mm_modem_3gpp_get_operator_code(modem_3gpp); + } + if (!network_id) { + _LOGW("failed to connect '%s': unable to determine the network id", + nm_connection_get_id(ctx->connection)); + goto out; + } + + nm_service_providers_find_gsm_apn(MOBILE_BROADBAND_PROVIDER_INFO_DATABASE, + network_id, + ctx->cancellable, + find_gsm_apn_cb, + self); + } else { + ctx->connect_properties = + create_gsm_connect_properties(ctx->connection, + nm_setting_gsm_get_apn(s_gsm), + nm_setting_gsm_get_username(s_gsm), + nm_setting_gsm_get_password(s_gsm)); + g_return_val_if_fail(ctx->connect_properties, TRUE); + } + + return TRUE; + } else if (MODEM_CAPS_3GPP2(ctx->caps)) { + ctx->connect_properties = create_cdma_connect_properties(ctx->connection); + g_return_val_if_fail(ctx->connect_properties, FALSE); + return TRUE; + } else { + _LOGW("failed to connect '%s': not a mobile broadband modem", + nm_connection_get_id(ctx->connection)); + } + +out: + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED); + connect_context_clear(self); + return TRUE; +} + +static void +connect_context_step(NMModemBroadband *self) +{ + ConnectContext *ctx = self->_priv.ctx; + + switch (ctx->step) { + case CONNECT_STEP_FIRST: + ctx->step++; + /* fall-through */ + + case CONNECT_STEP_WAIT_FOR_SIM: + if (MODEM_CAPS_3GPP(ctx->caps) && !self->_priv.sim_iface) { + /* Have to wait for the SIM to show up */ + break; + } + ctx->step++; + /* fall-through */ + + case CONNECT_STEP_UNLOCK: + if (MODEM_CAPS_3GPP(ctx->caps) + && mm_modem_get_unlock_required(self->_priv.modem_iface) == MM_MODEM_LOCK_SIM_PIN) { + NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(ctx->connection); + const char * pin = nm_setting_gsm_get_pin(s_gsm); + + /* If we have a PIN already, send it. If we don't, get it. */ + if (pin) { + mm_sim_send_pin(self->_priv.sim_iface, + pin, + ctx->cancellable, + (GAsyncReadyCallback) send_pin_ready, + self); + } else { + ask_for_pin(self); + } + break; + } + ctx->step++; + /* fall-through */ + case CONNECT_STEP_WAIT_FOR_READY: + { + GError *error = NULL; + + if (mm_modem_get_state(self->_priv.modem_iface) <= MM_MODEM_STATE_LOCKED) + break; + + if (!try_create_connect_properties(self)) + break; + + if (!self->_priv.ctx) + break; + + /* Build up list of IP types that we need to use in the retries */ + ctx->ip_types = nm_modem_get_connection_ip_type(NM_MODEM(self), ctx->connection, &error); + if (!ctx->ip_types) { + _LOGW("failed to connect '%s': %s", + nm_connection_get_id(ctx->connection), + error->message); + g_clear_error(&error); + + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED); + connect_context_clear(self); + break; + } + + ctx->step++; + } + /* fall-through */ + case CONNECT_STEP_CONNECT: + if (!ctx->connect_properties) + break; + + if (ctx->ip_types_i < ctx->ip_types->len) { + NMModemIPType current; + + current = g_array_index(ctx->ip_types, NMModemIPType, ctx->ip_types_i); + + if (current == NM_MODEM_IP_TYPE_IPV4) + mm_simple_connect_properties_set_ip_type(ctx->connect_properties, + MM_BEARER_IP_FAMILY_IPV4); + else if (current == NM_MODEM_IP_TYPE_IPV6) + mm_simple_connect_properties_set_ip_type(ctx->connect_properties, + MM_BEARER_IP_FAMILY_IPV6); + else if (current == NM_MODEM_IP_TYPE_IPV4V6) + mm_simple_connect_properties_set_ip_type(ctx->connect_properties, + MM_BEARER_IP_FAMILY_IPV4V6); + else + g_return_if_reached(); + + _nm_modem_set_apn(NM_MODEM(self), + mm_simple_connect_properties_get_apn(ctx->connect_properties)); + + _LOGD("launching connection with ip type '%s' (try %d)", + nm_modem_ip_type_to_string(current), + ctx->ip_type_tries + 1); + + mm_modem_simple_connect(self->_priv.simple_iface, + ctx->connect_properties, + ctx->cancellable, + (GAsyncReadyCallback) connect_ready, + self); + break; + } + + ctx->step++; + /* fall-through */ + + case CONNECT_STEP_LAST: + if (self->_priv.ipv4_config || self->_priv.ipv6_config) + nm_modem_emit_prepare_result(NM_MODEM(self), TRUE, NM_DEVICE_STATE_REASON_NONE); + else { + /* If we have a saved error from a previous attempt, use it */ + if (!ctx->first_error) + ctx->first_error = g_error_new_literal(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "invalid bearer IP configuration"); + + _LOGW("failed to connect modem: %s", ctx->first_error->message); + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + translate_mm_error(self, ctx->first_error)); + } + + connect_context_clear(self); + break; + } +} + +static NMActStageReturn +modem_act_stage1_prepare(NMModem * _self, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(_self); + + /* Make sure we can get the Simple interface from the modem */ + if (!self->_priv.simple_iface) { + self->_priv.simple_iface = mm_object_get_modem_simple(self->_priv.modem_object); + if (!self->_priv.simple_iface) { + _LOGW("cannot access the Simple mobile broadband modem interface"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + } + + connect_context_clear(self); + + /* Allocate new context for this connect stage attempt */ + self->_priv.ctx = g_slice_new0(ConnectContext); + self->_priv.ctx->caps = mm_modem_get_current_capabilities(self->_priv.modem_iface); + self->_priv.ctx->cancellable = g_cancellable_new(); + self->_priv.ctx->connection = g_object_ref(connection); + + g_dbus_proxy_set_default_timeout(G_DBUS_PROXY(self->_priv.simple_iface), + MODEM_CONNECT_TIMEOUT_SECS * 1000); + connect_context_step(self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static gboolean +check_connection_compatible_with_modem(NMModem *_self, NMConnection *connection, GError **error) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(_self); + MMModemCapability modem_caps; + + modem_caps = mm_modem_get_current_capabilities(self->_priv.modem_iface); + + if (MODEM_CAPS_3GPP(modem_caps)) { + if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, error)) + return FALSE; + + return TRUE; + } + + if (MODEM_CAPS_3GPP2(modem_caps)) { + if (!_nm_connection_check_main_setting(connection, NM_SETTING_CDMA_SETTING_NAME, error)) + return FALSE; + + return TRUE; + } + + if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, NULL) + && !_nm_connection_check_main_setting(connection, NM_SETTING_CDMA_SETTING_NAME, NULL)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "connection type %s is not supported by modem", + nm_connection_get_connection_type(connection)); + return FALSE; + } + + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem lacks capabilities for %s profile", + nm_connection_get_connection_type(connection)); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +complete_connection(NMModem * modem, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(modem); + MMModemCapability modem_caps; + NMSettingPpp * s_ppp; + + modem_caps = mm_modem_get_current_capabilities(self->_priv.modem_iface); + + /* PPP settings common to 3GPP and 3GPP2 */ + s_ppp = nm_connection_get_setting_ppp(connection); + if (!s_ppp) { + s_ppp = (NMSettingPpp *) nm_setting_ppp_new(); + g_object_set(G_OBJECT(s_ppp), + NM_SETTING_PPP_LCP_ECHO_FAILURE, + 5, + NM_SETTING_PPP_LCP_ECHO_INTERVAL, + 30, + NULL); + nm_connection_add_setting(connection, NM_SETTING(s_ppp)); + } + + if (MODEM_CAPS_3GPP(modem_caps)) { + NMSettingGsm *s_gsm; + + s_gsm = nm_connection_get_setting_gsm(connection); + if (!s_gsm) { + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + nm_connection_add_setting(connection, NM_SETTING(s_gsm)); + g_object_set(G_OBJECT(s_gsm), NM_SETTING_GSM_AUTO_CONFIG, TRUE, NULL); + } + + if (!nm_setting_gsm_get_device_id(s_gsm)) { + g_object_set(G_OBJECT(s_gsm), + NM_SETTING_GSM_DEVICE_ID, + nm_modem_get_device_id(modem), + NULL); + } + + nm_utils_complete_generic(NM_PLATFORM_GET, + connection, + NM_SETTING_GSM_SETTING_NAME, + existing_connections, + NULL, + _("GSM connection"), + NULL, + NULL, + FALSE); /* No IPv6 yet by default */ + + return TRUE; + } + + if (MODEM_CAPS_3GPP2(modem_caps)) { + NMSettingCdma *s_cdma; + + s_cdma = nm_connection_get_setting_cdma(connection); + if (!s_cdma) { + s_cdma = (NMSettingCdma *) nm_setting_cdma_new(); + nm_connection_add_setting(connection, NM_SETTING(s_cdma)); + } + + if (!nm_setting_cdma_get_number(s_cdma)) + g_object_set(G_OBJECT(s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL); + + nm_utils_complete_generic(NM_PLATFORM_GET, + connection, + NM_SETTING_CDMA_SETTING_NAME, + existing_connections, + NULL, + _("CDMA connection"), + NULL, + iface, + FALSE); /* No IPv6 yet by default */ + + return TRUE; + } + + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Device is not a mobile broadband modem"); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +get_user_pass(NMModem *modem, NMConnection *connection, const char **user, const char **pass) +{ + NMSettingGsm * s_gsm; + NMSettingCdma *s_cdma; + + s_gsm = nm_connection_get_setting_gsm(connection); + s_cdma = nm_connection_get_setting_cdma(connection); + if (!s_gsm && !s_cdma) + return FALSE; + + if (user) { + if (s_gsm) + *user = nm_setting_gsm_get_username(s_gsm); + else if (s_cdma) + *user = nm_setting_cdma_get_username(s_cdma); + } + if (pass) { + if (s_gsm) + *pass = nm_setting_gsm_get_password(s_gsm); + else if (s_cdma) + *pass = nm_setting_cdma_get_password(s_cdma); + } + + return TRUE; +} + +/*****************************************************************************/ +/* Query/Update enabled state */ + +static void +set_power_state_low_ready(MMModem *modem, GAsyncResult *result, NMModemBroadband *self) +{ + GError *error = NULL; + + if (!mm_modem_set_power_state_finish(modem, result, &error)) { + /* Log but ignore errors; not all modems support low power state */ + _LOGD("failed to set modem low power state: %s", NM_G_ERROR_MSG(error)); + g_clear_error(&error); + } + + /* Balance refcount */ + g_object_unref(self); +} + +static void +modem_disable_ready(MMModem *modem_iface, GAsyncResult *res, NMModemBroadband *self) +{ + GError *error = NULL; + + if (mm_modem_disable_finish(modem_iface, res, &error)) { + /* Once disabled, move to low-power mode */ + mm_modem_set_power_state(modem_iface, + MM_MODEM_POWER_STATE_LOW, + NULL, + (GAsyncReadyCallback) set_power_state_low_ready, + g_object_ref(self)); + } else { + _LOGW("failed to disable modem: %s", NM_G_ERROR_MSG(error)); + nm_modem_set_prev_state(NM_MODEM(self), "disable failed"); + g_clear_error(&error); + } + + /* Balance refcount */ + g_object_unref(self); +} + +static void +modem_enable_ready(MMModem *modem_iface, GAsyncResult *res, NMModemBroadband *self) +{ + GError *error = NULL; + + if (!mm_modem_enable_finish(modem_iface, res, &error)) { + _LOGW("failed to enable modem: %s", NM_G_ERROR_MSG(error)); + nm_modem_set_prev_state(NM_MODEM(self), "enable failed"); + g_clear_error(&error); + } + + /* Balance refcount */ + g_object_unref(self); +} + +static void +set_mm_enabled(NMModem *_self, gboolean enabled) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(_self); + + if (enabled) { + mm_modem_enable(self->_priv.modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback) modem_enable_ready, + g_object_ref(self)); + } else { + mm_modem_disable(self->_priv.modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback) modem_disable_ready, + g_object_ref(self)); + } +} + +/*****************************************************************************/ +/* IPv4 method static */ + +static gboolean +static_stage3_ip4_done(NMModemBroadband *self) +{ + GError * error = NULL; + gs_unref_object NMIP4Config *config = NULL; + const char * data_port; + const char * address_string; + const char * gw_string; + guint32 address_network; + guint32 gw = 0; + NMPlatformIP4Address address; + const char ** dns; + guint i; + guint32 ip4_route_table, ip4_route_metric; + NMPlatformIP4Route * r; + guint32 mtu_n; + + g_return_val_if_fail(self->_priv.ipv4_config, FALSE); + g_return_val_if_fail(self->_priv.bearer, FALSE); + + self->_priv.idle_id_ip4 = 0; + + _LOGI("IPv4 static configuration:"); + + /* Fully fail if invalid IP address retrieved */ + address_string = mm_bearer_ip_config_get_address(self->_priv.ipv4_config); + if (!address_string + || !nm_utils_parse_inaddr_bin(AF_INET, address_string, NULL, &address_network)) { + error = + g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IP4 configuration failed: invalid address given %s%s%s", + nm_modem_get_uid(NM_MODEM(self)), + NM_PRINT_FMT_QUOTE_STRING(address_string)); + goto out; + } + + /* Missing gateway not a hard failure */ + gw_string = mm_bearer_ip_config_get_gateway(self->_priv.ipv4_config); + if (gw_string && !nm_utils_parse_inaddr_bin(AF_INET, gw_string, NULL, &gw)) { + error = + g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IP4 configuration failed: invalid gateway address \"%s\"", + nm_modem_get_uid(NM_MODEM(self)), + gw_string); + goto out; + } + + data_port = mm_bearer_get_interface(self->_priv.bearer); + g_return_val_if_fail(data_port, FALSE); + config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), + nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port)); + + memset(&address, 0, sizeof(address)); + address.address = address_network; + address.peer_address = address_network; + address.plen = mm_bearer_ip_config_get_prefix(self->_priv.ipv4_config); + address.addr_source = NM_IP_CONFIG_SOURCE_WWAN; + if (address.plen <= 32) + nm_ip4_config_add_address(config, &address); + + _LOGI(" address %s/%d", address_string, address.plen); + + nm_modem_get_route_parameters(NM_MODEM(self), &ip4_route_table, &ip4_route_metric, NULL, NULL); + r = &(NMPlatformIP4Route){ + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = gw, + .table_coerced = nm_platform_route_table_coerce(ip4_route_table), + .metric = ip4_route_metric, + }; + nm_ip4_config_add_route(config, r, NULL); + _LOGI(" gateway %s", gw_string); + + /* DNS servers */ + dns = mm_bearer_ip_config_get_dns(self->_priv.ipv4_config); + for (i = 0; dns && dns[i]; i++) { + if (nm_utils_parse_inaddr_bin(AF_INET, dns[i], NULL, &address_network) + && address_network > 0) { + nm_ip4_config_add_nameserver(config, address_network); + _LOGI(" DNS %s", dns[i]); + } + } + +#if MM_CHECK_VERSION(1, 4, 0) + mtu_n = mm_bearer_ip_config_get_mtu(self->_priv.ipv4_config); + if (mtu_n) { + nm_ip4_config_set_mtu(config, mtu_n, NM_IP_CONFIG_SOURCE_WWAN); + _LOGI(" MTU %u", mtu_n); + } +#endif + +out: + g_signal_emit_by_name(self, NM_MODEM_IP4_CONFIG_RESULT, config, error); + g_clear_error(&error); + return FALSE; +} + +static NMActStageReturn +static_stage3_ip4_config_start(NMModem * modem, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(modem); + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + + /* We schedule it in an idle just to follow the same logic as in the + * generic modem implementation. */ + nm_clear_g_source(&priv->idle_id_ip4); + priv->idle_id_ip4 = g_idle_add((GSourceFunc) static_stage3_ip4_done, self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ +/* IPv6 method static */ + +static gboolean +stage3_ip6_done(NMModemBroadband *self) +{ + GError * error = NULL; + NMIP6Config * config = NULL; + const char * data_port; + const char * address_string; + NMPlatformIP6Address address; + NMModemIPMethod ip_method; + const char ** dns; + guint i; + + g_return_val_if_fail(self->_priv.ipv6_config, FALSE); + + self->_priv.idle_id_ip6 = 0; + memset(&address, 0, sizeof(address)); + + ip_method = get_bearer_ip_method(self->_priv.ipv6_config); + + address_string = mm_bearer_ip_config_get_address(self->_priv.ipv6_config); + if (!address_string) { + /* DHCP/SLAAC is allowed to skip addresses; other methods require it */ + if (ip_method != NM_MODEM_IP_METHOD_AUTO) { + error = g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: no address given", + nm_modem_get_uid(NM_MODEM(self))); + } + goto out; + } + + /* Fail if invalid IP address retrieved */ + if (!inet_pton(AF_INET6, address_string, (void *) &(address.address))) { + error = g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: invalid address given '%s'", + nm_modem_get_uid(NM_MODEM(self)), + address_string); + goto out; + } + + _LOGI("IPv6 base configuration:"); + + data_port = mm_bearer_get_interface(self->_priv.bearer); + g_return_val_if_fail(data_port, FALSE); + + config = nm_ip6_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), + nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port)); + + address.plen = mm_bearer_ip_config_get_prefix(self->_priv.ipv6_config); + if (address.plen <= 128) + nm_ip6_config_add_address(config, &address); + + _LOGI(" address %s/%d", address_string, address.plen); + + address_string = mm_bearer_ip_config_get_gateway(self->_priv.ipv6_config); + if (address_string) { + guint32 ip6_route_table, ip6_route_metric; + + if (inet_pton(AF_INET6, address_string, &address.address) != 1) { + error = + g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: invalid gateway given '%s'", + nm_modem_get_uid(NM_MODEM(self)), + address_string); + goto out; + } + + nm_modem_get_route_parameters(NM_MODEM(self), + NULL, + NULL, + &ip6_route_table, + &ip6_route_metric); + { + const NMPlatformIP6Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = address.address, + .table_coerced = nm_platform_route_table_coerce(ip6_route_table), + .metric = ip6_route_metric, + }; + + _LOGI(" gateway %s", address_string); + nm_ip6_config_add_route(config, &r, NULL); + } + } else if (ip_method == NM_MODEM_IP_METHOD_STATIC) { + /* Gateway required for the 'static' method */ + error = g_error_new(NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "(%s) retrieving IPv6 configuration failed: missing gateway", + nm_modem_get_uid(NM_MODEM(self))); + goto out; + } + + /* DNS servers */ + dns = mm_bearer_ip_config_get_dns(self->_priv.ipv6_config); + for (i = 0; dns && dns[i]; i++) { + struct in6_addr addr; + + if (inet_pton(AF_INET6, dns[i], &addr)) { + nm_ip6_config_add_nameserver(config, &addr); + _LOGI(" DNS %s", dns[i]); + } + } + +out: + nm_modem_emit_ip6_config_result(NM_MODEM(self), config, error); + g_clear_object(&config); + g_clear_error(&error); + return FALSE; +} + +static NMActStageReturn +stage3_ip6_config_request(NMModem *modem, NMDeviceStateReason *out_failure_reason) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(modem); + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + + /* We schedule it in an idle just to follow the same logic as in the + * generic modem implementation. */ + nm_clear_g_source(&priv->idle_id_ip6); + priv->idle_id_ip6 = g_idle_add((GSourceFunc) stage3_ip6_done, self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ +/* Disconnect */ + +typedef struct { + NMModemBroadband * self; + _NMModemDisconnectCallback callback; + gpointer callback_user_data; + GCancellable * cancellable; + gboolean warn; +} DisconnectContext; + +static void +disconnect_context_complete(DisconnectContext *ctx, GError *error) +{ + if (ctx->callback) + ctx->callback(NM_MODEM(ctx->self), error, ctx->callback_user_data); + nm_g_object_unref(ctx->cancellable); + g_object_unref(ctx->self); + g_slice_free(DisconnectContext, ctx); +} + +static void +disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellable) +{ + DisconnectContext *ctx = user_data; + gs_free_error GError *cancelled_error = NULL; + + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + disconnect_context_complete(ctx, cancelled_error); +} + +static void +simple_disconnect_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + MMModemSimple * modem_iface = MM_MODEM_SIMPLE(source_object); + DisconnectContext *ctx = user_data; + gs_free_error GError *error = NULL; + + if (!mm_modem_simple_disconnect_finish(modem_iface, res, &error)) { + if (ctx->warn && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) { + NMModemBroadband *self = ctx->self; + + _LOGW("failed to disconnect modem: %s", error->message); + } + } + + disconnect_context_complete(ctx, error); +} + +static void +disconnect(NMModem * modem, + gboolean warn, + GCancellable * cancellable, + _NMModemDisconnectCallback callback, + gpointer user_data) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(modem); + DisconnectContext *ctx; + + connect_context_clear(self); + _nm_modem_set_apn(NM_MODEM(self), NULL); + + ctx = g_slice_new0(DisconnectContext); + ctx->self = g_object_ref(self); + ctx->cancellable = nm_g_object_ref(cancellable); + ctx->callback = callback; + ctx->callback_user_data = user_data; + + /* Don't bother warning on FAILED since the modem is already gone */ + ctx->warn = warn; + + /* Already cancelled or no simple-iface? We are done. */ + if (!ctx->self->_priv.simple_iface || g_cancellable_is_cancelled(cancellable)) { + nm_utils_invoke_on_idle(cancellable, disconnect_context_complete_on_idle, ctx); + return; + } + + _LOGD("notifying ModemManager about the modem disconnection"); + mm_modem_simple_disconnect(self->_priv.simple_iface, + NULL, /* bearer path; if NULL given ALL get disconnected */ + cancellable, + simple_disconnect_ready, + ctx); +} + +/*****************************************************************************/ + +static void +deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(modem); + + /* TODO: cancel SimpleConnect() if any */ + + /* Cleanup IPv4 addresses and routes */ + g_clear_object(&self->_priv.ipv4_config); + g_clear_object(&self->_priv.ipv6_config); + g_clear_object(&self->_priv.bearer); + + self->_priv.pin_tries = 0; + + NM_MODEM_CLASS(nm_modem_broadband_parent_class) + ->deactivate_cleanup(modem, device, stop_ppp_manager); +} + +/*****************************************************************************/ + +#define MAP_STATE(name) \ + case MM_MODEM_STATE_##name: \ + return NM_MODEM_STATE_##name; + +static NMModemState +mm_state_to_nm(MMModemState mm_state) +{ + switch (mm_state) { + MAP_STATE(UNKNOWN) + MAP_STATE(FAILED) + MAP_STATE(INITIALIZING) + MAP_STATE(LOCKED) + MAP_STATE(DISABLED) + MAP_STATE(DISABLING) + MAP_STATE(ENABLING) + MAP_STATE(ENABLED) + MAP_STATE(SEARCHING) + MAP_STATE(REGISTERED) + MAP_STATE(DISCONNECTING) + MAP_STATE(CONNECTING) + MAP_STATE(CONNECTED) + } + return NM_MODEM_STATE_UNKNOWN; +} + +static void +modem_state_changed(MMModem * modem, + MMModemState old_state, + MMModemState new_state, + MMModemStateChangeReason reason, + NMModemBroadband * self) +{ + /* After the SIM is unlocked MM1 will move the device to INITIALIZING which + * is an unavailable state. That makes state handling confusing here, so + * suppress this state change and let the modem move from LOCKED to DISABLED. + */ + if (new_state == MM_MODEM_STATE_INITIALIZING && old_state == MM_MODEM_STATE_LOCKED) + return; + + nm_modem_set_state(NM_MODEM(self), + mm_state_to_nm(new_state), + mm_modem_state_change_reason_get_string(reason)); + + if (self->_priv.ctx && self->_priv.ctx->step == CONNECT_STEP_WAIT_FOR_READY) + connect_context_step(self); +} + +/*****************************************************************************/ + +static NMModemIPType +mm_ip_family_to_nm(MMBearerIpFamily family) +{ + NMModemIPType nm_type = NM_MODEM_IP_TYPE_UNKNOWN; + + if (family & MM_BEARER_IP_FAMILY_IPV4) + nm_type |= NM_MODEM_IP_TYPE_IPV4; + if (family & MM_BEARER_IP_FAMILY_IPV6) + nm_type |= NM_MODEM_IP_TYPE_IPV6; + if (family & MM_BEARER_IP_FAMILY_IPV4V6) + nm_type |= MM_BEARER_IP_FAMILY_IPV4V6; + + return nm_type; +} + +static void +get_sim_ready(MMModem *modem, GAsyncResult *res, NMModemBroadband *self) +{ + GError *error = NULL; + MMSim * new_sim; + + new_sim = mm_modem_get_sim_finish(modem, res, &error); + if (new_sim != self->_priv.sim_iface) { + g_clear_object(&self->_priv.sim_iface); + self->_priv.sim_iface = new_sim; + } else + g_clear_object(&new_sim); + + if (self->_priv.sim_iface) { + g_object_set(G_OBJECT(self), + NM_MODEM_SIM_ID, + mm_sim_get_identifier(self->_priv.sim_iface), + NM_MODEM_SIM_OPERATOR_ID, + mm_sim_get_operator_identifier(self->_priv.sim_iface), + NULL); + + /* If we're waiting for the SIM during a connect, proceed with the connect */ + if (self->_priv.ctx && self->_priv.ctx->step == CONNECT_STEP_WAIT_FOR_SIM) + connect_context_step(self); + } else { + _NMLOG(g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND) ? LOGL_INFO + : LOGL_WARN, + "failed to retrieve SIM object: %s", + NM_G_ERROR_MSG(error)); + } + g_clear_error(&error); + g_object_unref(self); +} + +static void +sim_changed(MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(user_data); + + g_return_if_fail(modem == self->_priv.modem_iface); + + if (mm_modem_get_sim_path(self->_priv.modem_iface)) { + mm_modem_get_sim(self->_priv.modem_iface, + NULL, /* cancellable */ + (GAsyncReadyCallback) get_sim_ready, + g_object_ref(self)); + } else + g_object_set(G_OBJECT(self), NM_MODEM_SIM_ID, NULL, NM_MODEM_SIM_OPERATOR_ID, NULL, NULL); +} + +static void +supported_ip_families_changed(MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(user_data); + + g_return_if_fail(modem == self->_priv.modem_iface); + + g_object_set(G_OBJECT(self), + NM_MODEM_IP_TYPES, + mm_ip_family_to_nm(mm_modem_get_supported_ip_families(modem)), + NULL); +} + +static void +operator_code_changed(MMModem3gpp *modem_3gpp, GParamSpec *pspec, gpointer user_data) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(user_data); + + g_return_if_fail(modem_3gpp == self->_priv.modem_3gpp_iface); + _nm_modem_set_operator_code(NM_MODEM(self), mm_modem_3gpp_get_operator_code(modem_3gpp)); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object(value, self->_priv.modem_object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMModemBroadband *self = NM_MODEM_BROADBAND(object); + + switch (prop_id) { + case PROP_MODEM: + /* construct-only */ + self->_priv.modem_object = g_value_dup_object(value); + self->_priv.modem_iface = mm_object_get_modem(self->_priv.modem_object); + g_return_if_fail(self->_priv.modem_iface); + self->_priv.modem_3gpp_iface = mm_object_get_modem_3gpp(self->_priv.modem_object); + + g_signal_connect(self->_priv.modem_iface, + "state-changed", + G_CALLBACK(modem_state_changed), + self); + g_signal_connect(self->_priv.modem_iface, "notify::sim", G_CALLBACK(sim_changed), self); + sim_changed(self->_priv.modem_iface, NULL, self); + g_signal_connect(self->_priv.modem_iface, + "notify::supported-ip-families", + G_CALLBACK(supported_ip_families_changed), + self); + + if (self->_priv.modem_3gpp_iface) { + g_signal_connect(self->_priv.modem_3gpp_iface, + "notify::operator-code", + G_CALLBACK(operator_code_changed), + self); + } + + /* Note: don't grab the Simple iface here; the Modem interface is the + * only one assumed to be always valid and available */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_modem_broadband_init(NMModemBroadband *self) +{} + +NMModem * +nm_modem_broadband_new(GObject *object, GError **error) +{ + MMObject * modem_object; + MMModem * modem_iface; + MMModem3gpp * modem_3gpp_iface; + const char *const *drivers; + const char * operator_code = NULL; + gs_free char * driver = NULL; + + g_return_val_if_fail(MM_IS_OBJECT(object), NULL); + modem_object = MM_OBJECT(object); + + /* Ensure we have the 'Modem' interface and the primary port at least */ + modem_iface = mm_object_peek_modem(modem_object); + g_return_val_if_fail(modem_iface, NULL); + g_return_val_if_fail(mm_modem_get_primary_port(modem_iface), NULL); + + /* Build a single string with all drivers listed */ + drivers = mm_modem_get_drivers(modem_iface); + if (drivers) + driver = g_strjoinv(", ", (char **) drivers); + + modem_3gpp_iface = mm_object_peek_modem_3gpp(modem_object); + if (modem_3gpp_iface) + operator_code = mm_modem_3gpp_get_operator_code(modem_3gpp_iface); + + return g_object_new(NM_TYPE_MODEM_BROADBAND, + NM_MODEM_PATH, + mm_object_get_path(modem_object), + NM_MODEM_UID, + mm_modem_get_primary_port(modem_iface), + NM_MODEM_CONTROL_PORT, + mm_modem_get_primary_port(modem_iface), + NM_MODEM_IP_TYPES, + mm_ip_family_to_nm(mm_modem_get_supported_ip_families(modem_iface)), + NM_MODEM_STATE, + (int) mm_state_to_nm(mm_modem_get_state(modem_iface)), + NM_MODEM_DEVICE_ID, + mm_modem_get_device_identifier(modem_iface), + NM_MODEM_BROADBAND_MODEM, + modem_object, + NM_MODEM_DRIVER, + driver, + NM_MODEM_OPERATOR_CODE, + operator_code, + NULL); +} + +static void +dispose(GObject *object) +{ + NMModemBroadband * self = NM_MODEM_BROADBAND(object); + NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self); + + nm_clear_g_source(&priv->idle_id_ip4); + nm_clear_g_source(&priv->idle_id_ip6); + + connect_context_clear(self); + g_clear_object(&self->_priv.ipv4_config); + g_clear_object(&self->_priv.ipv6_config); + g_clear_object(&self->_priv.bearer); + + if (self->_priv.modem_iface) { + g_signal_handlers_disconnect_by_data(self->_priv.modem_iface, self); + g_clear_object(&self->_priv.modem_iface); + } + + if (self->_priv.modem_3gpp_iface) { + g_signal_handlers_disconnect_by_data(self->_priv.modem_3gpp_iface, self); + g_clear_object(&self->_priv.modem_3gpp_iface); + } + + g_clear_object(&self->_priv.simple_iface); + g_clear_object(&self->_priv.sim_iface); + g_clear_object(&self->_priv.modem_object); + + G_OBJECT_CLASS(nm_modem_broadband_parent_class)->dispose(object); +} + +static void +nm_modem_broadband_class_init(NMModemBroadbandClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMModemClass *modem_class = NM_MODEM_CLASS(klass); + + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + modem_class->get_capabilities = get_capabilities; + modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start; + modem_class->stage3_ip6_config_request = stage3_ip6_config_request; + modem_class->disconnect = disconnect; + modem_class->deactivate_cleanup = deactivate_cleanup; + modem_class->set_mm_enabled = set_mm_enabled; + modem_class->get_user_pass = get_user_pass; + modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem; + modem_class->complete_connection = complete_connection; + modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare; + modem_class->owns_port = owns_port; + + obj_properties[PROP_MODEM] = + g_param_spec_object(NM_MODEM_BROADBAND_MODEM, + "", + "", + MM_GDBUS_TYPE_OBJECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wwan/nm-modem-broadband.h b/src/core/devices/wwan/nm-modem-broadband.h new file mode 100644 index 00000000..627fe25a --- /dev/null +++ b/src/core/devices/wwan/nm-modem-broadband.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef __NETWORKMANAGER_MODEM_BROADBAND_H__ +#define __NETWORKMANAGER_MODEM_BROADBAND_H__ + +#include "nm-modem.h" + +#define NM_TYPE_MODEM_BROADBAND (nm_modem_broadband_get_type()) +#define NM_MODEM_BROADBAND(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadband)) +#define NM_MODEM_BROADBAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass)) +#define NM_IS_MODEM_BROADBAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_BROADBAND)) +#define NM_IS_MODEM_BROADBAND_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM_BROADBAND)) +#define NM_MODEM_BROADBAND_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass)) + +typedef struct _NMModemBroadband NMModemBroadband; +typedef struct _NMModemBroadbandClass NMModemBroadbandClass; + +GType nm_modem_broadband_get_type(void); + +NMModem *nm_modem_broadband_new(GObject *object, GError **error); + +#endif /* __NETWORKMANAGER_MODEM_BROADBAND_H__ */ diff --git a/src/core/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c new file mode 100644 index 00000000..598c6898 --- /dev/null +++ b/src/core/devices/wwan/nm-modem-manager.c @@ -0,0 +1,876 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + * Copyright (C) 2009 - 2013 Canonical Ltd. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-modem-manager.h" + +#include <libmm-glib.h> + +#if HAVE_LIBSYSTEMD + #include <systemd/sd-daemon.h> +#else + #define sd_booted() FALSE +#endif + +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-modem.h" +#include "nm-modem-broadband.h" + +#if WITH_OFONO + #include "nm-modem-ofono.h" +#endif + +#define MODEM_POKE_INTERVAL 120 + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMModemManager, PROP_NAME_OWNER, ); + +enum { + MODEM_ADDED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + GDBusConnection *dbus_connection; + + /* used during g_bus_get() and later during mm_manager_new(). */ + GCancellable *main_cancellable; + + struct { + MMManager * manager; + GCancellable *poke_cancellable; + gulong handle_name_owner_changed_id; + gulong handle_object_added_id; + gulong handle_object_removed_id; + guint relaunch_id; + + /* this only has one use: that the <info> logging line about + * ModemManager available distinguishes between first-time + * and later name-owner-changed. */ + enum { + LOG_AVAILABLE_NOT_INITIALIZED = 0, + LOG_AVAILABLE_YES, + LOG_AVAILABLE_NO, + } log_available : 3; + + GDBusProxy * proxy; + GCancellable *proxy_cancellable; + guint proxy_ref_count; + char * proxy_name_owner; + } modm; + +#if WITH_OFONO + struct { + GDBusProxy * proxy; + GCancellable *cancellable; + } ofono; +#endif + + GHashTable *modems; +} NMModemManagerPrivate; + +struct _NMModemManager { + GObject parent; + NMModemManagerPrivate _priv; +}; + +struct _NMModemManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMModemManager, nm_modem_manager, G_TYPE_OBJECT) + +#define NM_MODEM_MANAGER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMModemManager, NM_IS_MODEM_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_MB +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "modem-manager", __VA_ARGS__) + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER(NMModemManager, nm_modem_manager_get, NM_TYPE_MODEM_MANAGER); + +/*****************************************************************************/ + +static void modm_schedule_manager_relaunch(NMModemManager *self, guint n_seconds); +static void modm_ensure_manager(NMModemManager *self); + +/*****************************************************************************/ + +static void +handle_new_modem(NMModemManager *self, NMModem *modem) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + const char * path; + + path = nm_modem_get_path(modem); + if (g_hash_table_lookup(priv->modems, path)) { + g_warn_if_reached(); + return; + } + + /* Track the new modem */ + g_hash_table_insert(priv->modems, g_strdup(path), modem); + g_signal_emit(self, signals[MODEM_ADDED], 0, modem); +} + +static gboolean +remove_one_modem(gpointer key, gpointer value, gpointer user_data) +{ + nm_modem_emit_removed(NM_MODEM(value)); + return TRUE; +} + +/*****************************************************************************/ + +NMModem ** +nm_modem_manager_get_modems(NMModemManager *self, guint *out_len) +{ + g_return_val_if_fail(NM_IS_MODEM_MANAGER(self), NULL); + + return (NMModem **) nm_utils_hash_values_to_array(NM_MODEM_MANAGER_GET_PRIVATE(self)->modems, + NULL, + NULL, + out_len); +} + +/*****************************************************************************/ + +static void +modm_clear_manager(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + if (!priv->modm.manager) + return; + nm_clear_g_signal_handler(priv->modm.manager, &priv->modm.handle_name_owner_changed_id); + nm_clear_g_signal_handler(priv->modm.manager, &priv->modm.handle_object_added_id); + nm_clear_g_signal_handler(priv->modm.manager, &priv->modm.handle_object_removed_id); + g_clear_object(&priv->modm.manager); +} + +static void +modm_handle_object_added(MMManager *modem_manager, MMObject *modem_object, NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + const char * path; + MMModem * modem_iface; + NMModem * modem; + GError * error = NULL; + + /* Ensure we don't have the same modem already */ + path = mm_object_get_path(modem_object); + if (g_hash_table_lookup(priv->modems, path)) { + _LOGW("modem with path %s already exists, ignoring", path); + return; + } + + /* Ensure we have the 'Modem' interface at least */ + modem_iface = mm_object_peek_modem(modem_object); + if (!modem_iface) { + _LOGW("modem with path %s doesn't have the Modem interface, ignoring", path); + return; + } + + /* Ensure we have a primary port reported */ + if (!mm_modem_get_primary_port(modem_iface)) { + _LOGW("modem with path %s has unknown primary port, ignoring", path); + return; + } + + /* Create a new modem object */ + modem = nm_modem_broadband_new(G_OBJECT(modem_object), &error); + if (modem) + handle_new_modem(self, modem); + else + _LOGW("failed to create modem: %s", error->message); + g_clear_error(&error); +} + +static void +modm_handle_object_removed(MMManager *manager, MMObject *modem_object, NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + NMModem * modem; + const char * path; + + path = mm_object_get_path(modem_object); + modem = (NMModem *) g_hash_table_lookup(priv->modems, path); + if (!modem) + return; + + nm_modem_emit_removed(modem); + g_hash_table_remove(priv->modems, path); +} + +static void +modm_manager_available(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + GList * modems, *l; + + if (priv->modm.log_available != LOG_AVAILABLE_YES) { + _LOGI("ModemManager %savailable", priv->modm.log_available ? "now " : ""); + priv->modm.log_available = LOG_AVAILABLE_YES; + } + + /* Update initial modems list */ + modems = g_dbus_object_manager_get_objects(G_DBUS_OBJECT_MANAGER(priv->modm.manager)); + for (l = modems; l; l = g_list_next(l)) + modm_handle_object_added(priv->modm.manager, MM_OBJECT(l->data), self); + g_list_free_full(modems, (GDestroyNotify) g_object_unref); +} + +static void +modm_handle_name_owner_changed(MMManager *modem_manager, GParamSpec *pspec, NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + char * name_owner; + + /* Quit poking, if any */ + nm_clear_g_source(&priv->modm.relaunch_id); + + name_owner = + g_dbus_object_manager_client_get_name_owner(G_DBUS_OBJECT_MANAGER_CLIENT(modem_manager)); + if (!name_owner) { + if (priv->modm.log_available != LOG_AVAILABLE_NO) { + _LOGI("ModemManager %savailable", priv->modm.log_available ? "no longer " : "not "); + priv->modm.log_available = LOG_AVAILABLE_NO; + } + + /* If not managed by systemd, schedule relaunch */ + if (!sd_booted()) + modm_schedule_manager_relaunch(self, 0); + + return; + } + + /* Available! */ + g_free(name_owner); + + /* Hack alert: GDBusObjectManagerClient won't signal neither 'object-added' + * nor 'object-removed' if it was created while there was no ModemManager in + * the bus. This hack avoids this issue until we get a GIO with the fix + * included... */ + modm_clear_manager(self); + modm_ensure_manager(self); + + /* Whenever GDBusObjectManagerClient is fixed, we can just do the following: + * modm_manager_available (self); + */ +} + +static void +modm_manager_poke_cb(GObject *connection, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *result = NULL; + + result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection), res, &error); + + if (!result && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->modm.poke_cancellable); + + if (error) { + _LOGW("error poking ModemManager: %s", error->message); + + /* Don't reschedule poke is MM service doesn't exist. */ + if (!g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) + && !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) { + /* Setup timeout to relaunch */ + modm_schedule_manager_relaunch(self, MODEM_POKE_INTERVAL); + } + } +} + +static void +modm_manager_poke(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->modm.poke_cancellable); + priv->modm.poke_cancellable = g_cancellable_new(); + + /* If there is no current owner right away, ensure we poke to get one */ + g_dbus_connection_call(priv->dbus_connection, + NM_MODEM_MANAGER_MM_DBUS_SERVICE, + NM_MODEM_MANAGER_MM_DBUS_PATH, + DBUS_INTERFACE_PEER, + "Ping", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->modm.poke_cancellable, + modm_manager_poke_cb, + self); +} + +static void +modm_manager_check_name_owner(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + gs_free char * name_owner = NULL; + + name_owner = g_dbus_object_manager_client_get_name_owner( + G_DBUS_OBJECT_MANAGER_CLIENT(priv->modm.manager)); + if (name_owner) { + modm_manager_available(self); + return; + } + + /* If the lifecycle is not managed by systemd, poke */ + if (!sd_booted()) + modm_manager_poke(self); +} + +static void +modm_manager_new_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + MMManager * modem_manager; + + modem_manager = mm_manager_new_finish(res, &error); + if (!modem_manager && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_assert(!priv->modm.manager); + + g_clear_object(&priv->main_cancellable); + + if (!modem_manager) { + /* We're not really supposed to get any error here. If we do get one, + * though, just re-schedule the MMManager creation after some time. + * During this period, name-owner changes won't be followed. */ + _LOGW("error creating ModemManager client: %s", error->message); + /* Setup timeout to relaunch */ + modm_schedule_manager_relaunch(self, MODEM_POKE_INTERVAL); + return; + } + + priv->modm.manager = modem_manager; + + /* Setup signals in the GDBusObjectManagerClient */ + priv->modm.handle_name_owner_changed_id = + g_signal_connect(priv->modm.manager, + "notify::name-owner", + G_CALLBACK(modm_handle_name_owner_changed), + self); + priv->modm.handle_object_added_id = g_signal_connect(priv->modm.manager, + "object-added", + G_CALLBACK(modm_handle_object_added), + self); + priv->modm.handle_object_removed_id = g_signal_connect(priv->modm.manager, + "object-removed", + G_CALLBACK(modm_handle_object_removed), + self); + + modm_manager_check_name_owner(self); +} + +static void +modm_ensure_manager(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_assert(priv->dbus_connection); + + /* Create the GDBusObjectManagerClient. We do not request to autostart, as + * we don't really want the MMManager creation to fail. We can always poke + * later on if we want to request the autostart */ + if (!priv->modm.manager) { + if (!priv->main_cancellable) + priv->main_cancellable = g_cancellable_new(); + mm_manager_new(priv->dbus_connection, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, + priv->main_cancellable, + modm_manager_new_cb, + self); + return; + } + + /* If already available, recheck name owner! */ + modm_manager_check_name_owner(self); +} + +static gboolean +modm_schedule_manager_relaunch_cb(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + priv->modm.relaunch_id = 0; + modm_ensure_manager(self); + return G_SOURCE_REMOVE; +} + +static void +modm_schedule_manager_relaunch(NMModemManager *self, guint n_seconds) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + /* No need to pass an extra reference to self; timeout/idle will be + * cancelled if the object gets disposed. */ + if (n_seconds) + priv->modm.relaunch_id = + g_timeout_add_seconds(n_seconds, (GSourceFunc) modm_schedule_manager_relaunch_cb, self); + else + priv->modm.relaunch_id = g_idle_add((GSourceFunc) modm_schedule_manager_relaunch_cb, self); +} + +/*****************************************************************************/ + +static void +modm_proxy_name_owner_reset(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + char * name = NULL; + + if (priv->modm.proxy) + name = g_dbus_proxy_get_name_owner(priv->modm.proxy); + + if (nm_streq0(priv->modm.proxy_name_owner, name)) { + g_free(name); + return; + } + g_free(priv->modm.proxy_name_owner); + priv->modm.proxy_name_owner = name; + + _notify(self, PROP_NAME_OWNER); +} + +static void +modm_proxy_name_owner_changed_cb(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + modm_proxy_name_owner_reset(user_data); +} + +static void +modm_proxy_new_cb(GObject *source_object, GAsyncResult *result, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + GDBusProxy * proxy; + gs_free_error GError *error = NULL; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->modm.proxy_cancellable); + + if (!proxy) { + _LOGW("could not obtain D-Bus proxy for ModemManager: %s", error->message); + return; + } + + priv->modm.proxy = proxy; + g_signal_connect(priv->modm.proxy, + "notify::g-name-owner", + G_CALLBACK(modm_proxy_name_owner_changed_cb), + self); + + modm_proxy_name_owner_reset(self); +} + +void +nm_modem_manager_name_owner_ref(NMModemManager *self) +{ + NMModemManagerPrivate *priv; + + g_return_if_fail(NM_IS_MODEM_MANAGER(self)); + + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + if (priv->modm.proxy_ref_count++ > 0) { + /* only try once to create the proxy. If proxy creation + * for the first "ref" failed, it's unclear what to do. + * The proxy is hosed. */ + return; + } + + nm_assert(!priv->modm.proxy && !priv->modm.proxy_cancellable); + + priv->modm.proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS + | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + NM_MODEM_MANAGER_MM_DBUS_SERVICE, + NM_MODEM_MANAGER_MM_DBUS_PATH, + NM_MODEM_MANAGER_MM_DBUS_INTERFACE, + priv->modm.proxy_cancellable, + modm_proxy_new_cb, + self); +} + +void +nm_modem_manager_name_owner_unref(NMModemManager *self) +{ + NMModemManagerPrivate *priv; + + g_return_if_fail(NM_IS_MODEM_MANAGER(self)); + + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_return_if_fail(priv->modm.proxy_ref_count > 0); + + if (--priv->modm.proxy_ref_count > 0) + return; + + nm_clear_g_cancellable(&priv->modm.proxy_cancellable); + g_clear_object(&priv->modm.proxy); + + modm_proxy_name_owner_reset(self); +} + +const char * +nm_modem_manager_name_owner_get(NMModemManager *self) +{ + g_return_val_if_fail(NM_IS_MODEM_MANAGER(self), NULL); + nm_assert(NM_MODEM_MANAGER_GET_PRIVATE(self)->modm.proxy_ref_count > 0); + + return NM_MODEM_MANAGER_GET_PRIVATE(self)->modm.proxy_name_owner; +} + +/*****************************************************************************/ + +#if WITH_OFONO + +static void +ofono_create_modem(NMModemManager *self, const char *path) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + NMModem * modem = NULL; + + /* Ensure duplicate modems aren't created. Because we're not using the + * ObjectManager interface there's a race during oFono startup where we + * receive ModemAdded signals before GetModems() returns, so some of the + * modems returned from GetModems() may already have been created. + */ + if (!g_hash_table_lookup(priv->modems, path)) { + modem = nm_modem_ofono_new(path); + if (modem) + handle_new_modem(self, modem); + else + _LOGW("Failed to create oFono modem for %s", path); + } +} + +static void +ofono_signal_cb(GDBusProxy *proxy, + char * sender_name, + char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMModemManager * self = NM_MODEM_MANAGER(user_data); + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + char * object_path; + NMModem * modem; + + if (g_strcmp0(signal_name, "ModemAdded") == 0) { + g_variant_get(parameters, "(oa{sv})", &object_path, NULL); + _LOGI("oFono modem appeared: %s", object_path); + + ofono_create_modem(NM_MODEM_MANAGER(user_data), object_path); + g_free(object_path); + } else if (g_strcmp0(signal_name, "ModemRemoved") == 0) { + g_variant_get(parameters, "(o)", &object_path); + _LOGI("oFono modem removed: %s", object_path); + + modem = (NMModem *) g_hash_table_lookup(priv->modems, object_path); + if (modem) { + nm_modem_emit_removed(modem); + g_hash_table_remove(priv->modems, object_path); + } else { + _LOGW("could not remove modem %s, not found in table", object_path); + } + g_free(object_path); + } +} + +static void +ofono_enumerate_devices_done(GObject *proxy, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + GVariant * results; + GVariantIter * iter; + const char * path; + + results = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error); + if (!results && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_MANAGER(user_data); + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->ofono.cancellable); + + if (!results) { + _LOGW("failed to enumerate oFono devices: %s", error->message); + return; + } + + g_variant_get(results, "(a(oa{sv}))", &iter); + while (g_variant_iter_loop(iter, "(&oa{sv})", &path, NULL)) + ofono_create_modem(self, path); + g_variant_iter_free(iter); + g_variant_unref(results); +} + +static void +ofono_check_name_owner(NMModemManager *self, gboolean first_invocation) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + gs_free char * name_owner = NULL; + + name_owner = g_dbus_proxy_get_name_owner(G_DBUS_PROXY(priv->ofono.proxy)); + if (name_owner) { + _LOGI("oFono is %savailable", first_invocation ? "" : "now "); + + nm_clear_g_cancellable(&priv->ofono.cancellable); + priv->ofono.cancellable = g_cancellable_new(); + + g_dbus_proxy_call(priv->ofono.proxy, + "GetModems", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->ofono.cancellable, + ofono_enumerate_devices_done, + self); + } else { + GHashTableIter iter; + NMModem * modem; + + _LOGI("oFono is %savailable", first_invocation ? "not " : "no longer "); + + /* Remove any oFono modems that might be left around */ + g_hash_table_iter_init(&iter, priv->modems); + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &modem)) { + if (NM_IS_MODEM_OFONO(modem)) { + nm_modem_emit_removed(modem); + g_hash_table_iter_remove(&iter); + } + } + } +} + +static void +ofono_name_owner_changed(GDBusProxy *ofono_proxy, GParamSpec *pspec, NMModemManager *self) +{ + ofono_check_name_owner(self, FALSE); +} + +static void +ofono_proxy_new_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_finish(res, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_MANAGER(user_data); + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + g_clear_object(&priv->ofono.cancellable); + + if (!proxy) { + _LOGW("error getting oFono bus proxy: %s", error->message); + return; + } + + priv->ofono.proxy = proxy; + + g_signal_connect(priv->ofono.proxy, + "notify::g-name-owner", + G_CALLBACK(ofono_name_owner_changed), + self); + + g_signal_connect(priv->ofono.proxy, "g-signal", G_CALLBACK(ofono_signal_cb), self); + + ofono_check_name_owner(self, TRUE); +} + +static void +ofono_init_proxy(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_assert(priv->dbus_connection); + nm_assert(!priv->ofono.cancellable); + + priv->ofono.cancellable = g_cancellable_new(); + + g_dbus_proxy_new(priv->dbus_connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + OFONO_DBUS_SERVICE, + OFONO_DBUS_PATH, + OFONO_DBUS_INTERFACE, + priv->ofono.cancellable, + ofono_proxy_new_cb, + self); +} +#endif + +/*****************************************************************************/ + +static void +bus_get_ready(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMModemManager * self; + NMModemManagerPrivate *priv; + gs_free_error GError *error = NULL; + GDBusConnection * connection; + + connection = g_bus_get_finish(res, &error); + if (!connection && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_MANAGER(user_data); + priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + if (!connection) { + _LOGW("error getting bus connection: %s", error->message); + return; + } + + priv->dbus_connection = connection; + + modm_ensure_manager(self); +#if WITH_OFONO + ofono_init_proxy(self); +#endif +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMModemManager * self = NM_MODEM_MANAGER(object); + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_NAME_OWNER: + g_value_set_string(value, priv->modm.proxy_name_owner); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_modem_manager_init(NMModemManager *self) +{ + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + priv->modems = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref); + + priv->main_cancellable = g_cancellable_new(); + + g_bus_get(G_BUS_TYPE_SYSTEM, priv->main_cancellable, bus_get_ready, self); +} + +static void +dispose(GObject *object) +{ + NMModemManager * self = NM_MODEM_MANAGER(object); + NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->main_cancellable); + nm_clear_g_cancellable(&priv->modm.poke_cancellable); + + nm_clear_g_source(&priv->modm.relaunch_id); + + nm_clear_g_cancellable(&priv->modm.proxy_cancellable); + g_clear_object(&priv->modm.proxy); + nm_clear_g_free(&priv->modm.proxy_name_owner); + + modm_clear_manager(self); + +#if WITH_OFONO + if (priv->ofono.proxy) { + g_signal_handlers_disconnect_by_func(priv->ofono.proxy, ofono_name_owner_changed, self); + g_signal_handlers_disconnect_by_func(priv->ofono.proxy, ofono_signal_cb, self); + g_clear_object(&priv->ofono.proxy); + } + nm_clear_g_cancellable(&priv->ofono.cancellable); +#endif + + g_clear_object(&priv->dbus_connection); + + if (priv->modems) { + g_hash_table_foreach_remove(priv->modems, remove_one_modem, object); + g_hash_table_destroy(priv->modems); + priv->modems = NULL; + } + + G_OBJECT_CLASS(nm_modem_manager_parent_class)->dispose(object); +} + +static void +nm_modem_manager_class_init(NMModemManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = dispose; + object_class->get_property = get_property; + + obj_properties[PROP_NAME_OWNER] = + g_param_spec_string(NM_MODEM_MANAGER_NAME_OWNER, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[MODEM_ADDED] = g_signal_new(NM_MODEM_MANAGER_MODEM_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_MODEM); +} diff --git a/src/core/devices/wwan/nm-modem-manager.h b/src/core/devices/wwan/nm-modem-manager.h new file mode 100644 index 00000000..e89a032b --- /dev/null +++ b/src/core/devices/wwan/nm-modem-manager.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + * Copyright (C) 2009 Canonical Ltd. + */ + +#ifndef __NETWORKMANAGER_MODEM_MANAGER_H__ +#define __NETWORKMANAGER_MODEM_MANAGER_H__ + +#include "nm-modem.h" + +#define NM_TYPE_MODEM_MANAGER (nm_modem_manager_get_type()) +#define NM_MODEM_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_MANAGER, NMModemManager)) +#define NM_MODEM_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM_MANAGER, NMModemManagerClass)) +#define NM_IS_MODEM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_MANAGER)) +#define NM_IS_MODEM_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM_MANAGER)) +#define NM_MODEM_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM_MANAGER, NMModemManagerClass)) + +#define NM_MODEM_MANAGER_MODEM_ADDED "modem-added" + +#define NM_MODEM_MANAGER_NAME_OWNER "name-owner" + +#define NM_MODEM_MANAGER_MM_DBUS_SERVICE "org.freedesktop.ModemManager1" +#define NM_MODEM_MANAGER_MM_DBUS_PATH "/org/freedesktop/ModemManager1" +#define NM_MODEM_MANAGER_MM_DBUS_INTERFACE "org.freedesktop.ModemManager1" + +typedef struct _NMModemManager NMModemManager; +typedef struct _NMModemManagerClass NMModemManagerClass; + +GType nm_modem_manager_get_type(void); + +NMModemManager *nm_modem_manager_get(void); + +void nm_modem_manager_name_owner_ref(NMModemManager *self); +void nm_modem_manager_name_owner_unref(NMModemManager *self); + +const char *nm_modem_manager_name_owner_get(NMModemManager *self); + +NMModem **nm_modem_manager_get_modems(NMModemManager *self, guint *out_len); + +#endif /* __NETWORKMANAGER_MODEM_MANAGER_H__ */ diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c new file mode 100644 index 00000000..21734cee --- /dev/null +++ b/src/core/devices/wwan/nm-modem-ofono.c @@ -0,0 +1,1239 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - 2016 Canonical Ltd. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-modem-ofono.h" + +#include "nm-core-internal.h" +#include "devices/nm-device-private.h" +#include "nm-modem.h" +#include "platform/nm-platform.h" +#include "nm-ip4-config.h" + +#define VARIANT_IS_OF_TYPE_BOOLEAN(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_BOOLEAN))) +#define VARIANT_IS_OF_TYPE_STRING(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_STRING))) +#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_OBJECT_PATH))) +#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_STRING_ARRAY))) +#define VARIANT_IS_OF_TYPE_DICTIONARY(v) \ + ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_DICTIONARY))) + +/*****************************************************************************/ + +typedef struct { + GHashTable *connect_properties; + + GDBusProxy *modem_proxy; + GDBusProxy *connman_proxy; + GDBusProxy *context_proxy; + GDBusProxy *sim_proxy; + + GCancellable *modem_proxy_cancellable; + GCancellable *connman_proxy_cancellable; + GCancellable *context_proxy_cancellable; + GCancellable *sim_proxy_cancellable; + + GError *property_error; + + char *context_path; + char *imsi; + + gboolean modem_online; + gboolean gprs_attached; + + NMIP4Config *ip4_config; +} NMModemOfonoPrivate; + +struct _NMModemOfono { + NMModem parent; + NMModemOfonoPrivate _priv; +}; + +struct _NMModemOfonoClass { + NMModemClass parent; +}; + +G_DEFINE_TYPE(NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM) + +#define NM_MODEM_OFONO_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMModemOfono, NM_IS_MODEM_OFONO) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_MB +#define _NMLOG_PREFIX_NAME "modem-ofono" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) { \ + NMModemOfono *const __self = (self); \ + char __prefix_name[128]; \ + const char * __uid; \ + \ + _nm_log(_level, \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (__self ? ({ \ + ((__uid = nm_modem_get_uid((NMModem *) __self)) \ + ? nm_sprintf_buf(__prefix_name, "[%s]", __uid) \ + : "(null)"); \ + }) \ + : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void +get_capabilities(NMModem * _self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + /* FIXME: auto-detect capabilities to allow LTE */ + *modem_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; + *current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS; +} + +static void +update_modem_state(NMModemOfono *self) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + NMModemState state = nm_modem_get_state(NM_MODEM(self)); + NMModemState new_state = NM_MODEM_STATE_DISABLED; + const char * reason = NULL; + + _LOGI("'Attached': %s 'Online': %s 'IMSI': %s", + priv->gprs_attached ? "true" : "false", + priv->modem_online ? "true" : "false", + priv->imsi); + + if (priv->modem_online == FALSE) { + reason = "modem 'Online=false'"; + } else if (priv->imsi == NULL && state != NM_MODEM_STATE_ENABLING) { + reason = "modem not ready"; + } else if (priv->gprs_attached == FALSE) { + new_state = NM_MODEM_STATE_SEARCHING; + reason = "modem searching"; + } else { + new_state = NM_MODEM_STATE_REGISTERED; + reason = "modem ready"; + } + + if (state != new_state) + nm_modem_set_state(NM_MODEM(self), new_state, reason); +} + +/* Disconnect */ +typedef struct { + NMModemOfono * self; + _NMModemDisconnectCallback callback; + gpointer callback_user_data; + GCancellable * cancellable; + gboolean warn; +} DisconnectContext; + +static void +disconnect_context_complete(DisconnectContext *ctx, GError *error) +{ + if (ctx->callback) + ctx->callback(NM_MODEM(ctx->self), error, ctx->callback_user_data); + nm_g_object_unref(ctx->cancellable); + g_object_unref(ctx->self); + g_slice_free(DisconnectContext, ctx); +} + +static void +disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellable) +{ + DisconnectContext *ctx = user_data; + gs_free_error GError *error = NULL; + + if (!g_cancellable_set_error_if_cancelled(cancellable, &error)) { + g_set_error_literal(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + ("modem is currently not connected")); + } + disconnect_context_complete(ctx, error); +} + +static void +disconnect_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + DisconnectContext *ctx = user_data; + NMModemOfono * self = ctx->self; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v = NULL; + + v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + disconnect_context_complete(ctx, error); + return; + } + + if (error && ctx->warn) + _LOGW("failed to disconnect modem: %s", error->message); + + _LOGD("modem disconnected"); + + update_modem_state(self); + disconnect_context_complete(ctx, error); +} + +static void +disconnect(NMModem * modem, + gboolean warn, + GCancellable * cancellable, + _NMModemDisconnectCallback callback, + gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + DisconnectContext * ctx; + NMModemState state = nm_modem_get_state(NM_MODEM(self)); + + _LOGD("warn: %s modem_state: %s", warn ? "TRUE" : "FALSE", nm_modem_state_to_string(state)); + + ctx = g_slice_new0(DisconnectContext); + ctx->self = g_object_ref(self); + ctx->cancellable = nm_g_object_ref(cancellable); + ctx->warn = warn; + ctx->callback = callback; + ctx->callback_user_data = user_data; + + if (state != NM_MODEM_STATE_CONNECTED || g_cancellable_is_cancelled(cancellable)) { + nm_utils_invoke_on_idle(cancellable, disconnect_context_complete_on_idle, ctx); + return; + } + + nm_modem_set_state(NM_MODEM(self), + NM_MODEM_STATE_DISCONNECTING, + nm_modem_state_to_string(NM_MODEM_STATE_DISCONNECTING)); + + g_dbus_proxy_call(priv->context_proxy, + "SetProperty", + g_variant_new("(sv)", "Active", g_variant_new("b", warn)), + G_DBUS_CALL_FLAGS_NONE, + 20000, + ctx->cancellable, + disconnect_done, + ctx); +} + +static void +deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + /* TODO: cancel SimpleConnect() if any */ + + g_clear_object(&priv->ip4_config); + + NM_MODEM_CLASS(nm_modem_ofono_parent_class) + ->deactivate_cleanup(modem, device, stop_ppp_manager); +} + +static gboolean +check_connection_compatible_with_modem(NMModem *modem, NMConnection *connection, GError **error) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + const char * id; + + if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, NULL)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "connection type %s is not supported by ofono modem", + nm_connection_get_connection_type(connection)); + return FALSE; + } + + if (!priv->imsi) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "modem has no IMSI"); + return FALSE; + } + + id = nm_connection_get_id(connection); + + if (!strstr(id, "/context")) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "the connection ID has no context"); + return FALSE; + } + + if (!strstr(id, priv->imsi)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "the connection ID does not contain the IMSI"); + return FALSE; + } + + return TRUE; +} + +static void +handle_sim_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (g_strcmp0(property, "SubscriberIdentity") == 0 && VARIANT_IS_OF_TYPE_STRING(v)) { + gsize length; + const char *value_str = g_variant_get_string(v, &length); + + _LOGD("SubscriberIdentify found"); + + /* Check for empty DBus string value */ + if (length && g_strcmp0(value_str, "(null)") != 0 + && g_strcmp0(value_str, priv->imsi) != 0) { + if (priv->imsi != NULL) { + _LOGW("SimManager:'SubscriberIdentity' changed: %s", priv->imsi); + g_free(priv->imsi); + } + + priv->imsi = g_strdup(value_str); + update_modem_state(self); + } + } +} + +static void +sim_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + GVariant *v_child = g_variant_get_child_value(v, 0); + + handle_sim_property(proxy, property, v_child, user_data); + g_variant_unref(v_child); +} + +static void +sim_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v_properties = NULL; + gs_unref_variant GVariant *v_dict = NULL; + GVariant * v; + GVariantIter i; + const char * property; + + v_properties = + _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error); + if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->sim_proxy_cancellable); + + if (!v_properties) { + g_dbus_error_strip_remote_error(error); + _LOGW("error getting sim properties: %s", error->message); + return; + } + + _LOGD("sim v_properties is type: %s", g_variant_get_type_string(v_properties)); + + v_dict = g_variant_get_child_value(v_properties, 0); + if (!v_dict) { + _LOGW("error getting sim properties: no v_dict"); + return; + } + + _LOGD("sim v_dict is type: %s", g_variant_get_type_string(v_dict)); + + /* + * TODO: + * 1) optimize by looking up properties ( Online, Interfaces ), instead + * of iterating + * + * 2) reduce code duplication between all of the get_properties_done + * functions in this class. + */ + + g_variant_iter_init(&i, v_dict); + while (g_variant_iter_next(&i, "{&sv}", &property, &v)) { + handle_sim_property(NULL, property, v, self); + g_variant_unref(v); + } +} + +static void +_sim_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGW("failed to create SimManager proxy: %s", error->message); + g_clear_object(&priv->sim_proxy_cancellable); + return; + } + + priv->sim_proxy = proxy; + + /* Watch for custom ofono PropertyChanged signals */ + _nm_dbus_signal_connect(priv->sim_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(sim_property_changed), + self); + + g_dbus_proxy_call(priv->sim_proxy, + "GetProperties", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->sim_proxy_cancellable, + sim_get_properties_done, + self); +} + +static void +handle_sim_iface(NMModemOfono *self, gboolean found) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + _LOGD("SimManager interface %sfound", found ? "" : "not "); + + if (!found && (priv->sim_proxy || priv->sim_proxy_cancellable)) { + _LOGI("SimManager interface disappeared"); + nm_clear_g_cancellable(&priv->sim_proxy_cancellable); + if (priv->sim_proxy) { + g_signal_handlers_disconnect_by_data(priv->sim_proxy, self); + g_clear_object(&priv->sim_proxy); + } + nm_clear_g_free(&priv->imsi); + update_modem_state(self); + } else if (found && (!priv->sim_proxy && !priv->sim_proxy_cancellable)) { + _LOGI("found new SimManager interface"); + + priv->sim_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, /* GDBusInterfaceInfo */ + OFONO_DBUS_SERVICE, + nm_modem_get_path(NM_MODEM(self)), + OFONO_DBUS_INTERFACE_SIM_MANAGER, + priv->sim_proxy_cancellable, /* GCancellable */ + _sim_proxy_new_cb, + self); + } +} + +static void +handle_connman_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (g_strcmp0(property, "Attached") == 0 && VARIANT_IS_OF_TYPE_BOOLEAN(v)) { + gboolean attached = g_variant_get_boolean(v); + gboolean old_attached = priv->gprs_attached; + + _LOGD("Attached: %s", attached ? "True" : "False"); + + if (priv->gprs_attached != attached) { + priv->gprs_attached = attached; + + _LOGI("Attached %s -> %s", + old_attached ? "true" : "false", + attached ? "true" : "false"); + + update_modem_state(self); + } + } +} + +static void +connman_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + GVariant *v_child = g_variant_get_child_value(v, 0); + + handle_connman_property(proxy, property, v_child, user_data); + g_variant_unref(v_child); +} + +static void +connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v_properties = NULL; + gs_unref_variant GVariant *v_dict = NULL; + GVariant * v; + GVariantIter i; + const char * property; + + v_properties = + _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error); + if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->connman_proxy_cancellable); + + if (!v_properties) { + g_dbus_error_strip_remote_error(error); + _LOGW("error getting connman properties: %s", error->message); + return; + } + + v_dict = g_variant_get_child_value(v_properties, 0); + + /* + * TODO: + * 1) optimize by looking up properties ( Online, Interfaces ), instead + * of iterating + * + * 2) reduce code duplication between all of the get_properties_done + * functions in this class. + */ + + g_variant_iter_init(&i, v_dict); + while (g_variant_iter_next(&i, "{&sv}", &property, &v)) { + handle_connman_property(NULL, property, v, self); + g_variant_unref(v); + } +} + +static void +_connman_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = user_data; + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGW("failed to create ConnectionManager proxy: %s", error->message); + g_clear_object(&priv->connman_proxy_cancellable); + return; + } + + priv->connman_proxy = proxy; + + _nm_dbus_signal_connect(priv->connman_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(connman_property_changed), + self); + + g_dbus_proxy_call(priv->connman_proxy, + "GetProperties", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->connman_proxy_cancellable, + connman_get_properties_done, + self); +} + +static void +handle_connman_iface(NMModemOfono *self, gboolean found) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + _LOGD("ConnectionManager interface %sfound", found ? "" : "not "); + + if (!found && (priv->connman_proxy || priv->connman_proxy_cancellable)) { + _LOGI("ConnectionManager interface disappeared"); + nm_clear_g_cancellable(&priv->connman_proxy_cancellable); + if (priv->connman_proxy) { + g_signal_handlers_disconnect_by_data(priv->connman_proxy, self); + g_clear_object(&priv->connman_proxy); + } + + /* The connection manager proxy disappeared, we should + * consider the modem disabled. + */ + priv->gprs_attached = FALSE; + + update_modem_state(self); + } else if (found && (!priv->connman_proxy && !priv->connman_proxy_cancellable)) { + _LOGI("found new ConnectionManager interface"); + + priv->connman_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES + | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, /* GDBusInterfaceInfo */ + OFONO_DBUS_SERVICE, + nm_modem_get_path(NM_MODEM(self)), + OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, + priv->connman_proxy_cancellable, + _connman_proxy_new_cb, + self); + } +} + +static void +handle_modem_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if ((g_strcmp0(property, "Online") == 0) && VARIANT_IS_OF_TYPE_BOOLEAN(v)) { + gboolean online = g_variant_get_boolean(v); + + _LOGD("Online: %s", online ? "True" : "False"); + + if (online != priv->modem_online) { + priv->modem_online = online; + _LOGI("modem is now %s", online ? "Online" : "Offline"); + update_modem_state(self); + } + + } else if ((g_strcmp0(property, "Interfaces") == 0) && VARIANT_IS_OF_TYPE_STRING_ARRAY(v)) { + const char **array, **iter; + gboolean found_connman = FALSE; + gboolean found_sim = FALSE; + + _LOGD("Interfaces found"); + + array = g_variant_get_strv(v, NULL); + if (array) { + for (iter = array; *iter; iter++) { + if (g_strcmp0(OFONO_DBUS_INTERFACE_SIM_MANAGER, *iter) == 0) + found_sim = TRUE; + else if (g_strcmp0(OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, *iter) == 0) + found_connman = TRUE; + } + g_free(array); + } + + handle_sim_iface(self, found_sim); + handle_connman_iface(self, found_connman); + } +} + +static void +modem_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + GVariant *v_child = g_variant_get_child_value(v, 0); + + handle_modem_property(proxy, property, v_child, user_data); + g_variant_unref(v_child); +} + +static void +modem_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v_properties = NULL; + gs_unref_variant GVariant *v_dict = NULL; + GVariant * v; + GVariantIter i; + const char * property; + + v_properties = + _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error); + if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->modem_proxy_cancellable); + + if (!v_properties) { + g_dbus_error_strip_remote_error(error); + _LOGW("error getting modem properties: %s", error->message); + return; + } + + v_dict = g_variant_get_child_value(v_properties, 0); + if (!v_dict) { + _LOGW("error getting modem properties: no v_dict"); + return; + } + + /* + * TODO: + * 1) optimize by looking up properties ( Online, Interfaces ), instead + * of iterating + * + * 2) reduce code duplication between all of the get_properties_done + * functions in this class. + */ + + g_variant_iter_init(&i, v_dict); + while (g_variant_iter_next(&i, "{&sv}", &property, &v)) { + handle_modem_property(NULL, property, v, self); + g_variant_unref(v); + } +} + +static void +stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *v = NULL; + + v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error); + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_clear_object(&priv->context_proxy_cancellable); + + nm_clear_pointer(&priv->connect_properties, g_hash_table_destroy); + + if (error) { + _LOGW("connection failed: %s", error->message); + + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY); + /* + * FIXME: add code to check for InProgress so that the + * connection doesn't continue to try and activate, + * leading to the connection being disabled, and a 5m + * timeout... + */ + } +} + +static void +context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data) +{ + NMModemOfono * self = NM_MODEM_OFONO(user_data); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + NMPlatformIP4Address addr; + gboolean ret = FALSE; + gs_unref_variant GVariant *v_dict = NULL; + const char * interface; + const char * s; + const char ** array, **iter; + guint32 address_network, gateway_network; + guint32 ip4_route_table, ip4_route_metric; + int ifindex; + GError * error = NULL; + + _LOGD("PropertyChanged: %s", property); + + /* + * TODO: might be a good idea and re-factor this to mimic bluez-device, + * ie. have this function just check the key, and call a sub-func to + * handle the action. + */ + + if (g_strcmp0(property, "Settings") != 0) + return; + + v_dict = g_variant_get_child_value(v, 0); + if (!v_dict) { + _LOGW("error getting IPv4 Settings: no v_dict"); + goto out; + } + + _LOGI("IPv4 static Settings:"); + + if (!g_variant_lookup(v_dict, "Interface", "&s", &interface)) { + _LOGW("Settings 'Interface' missing"); + goto out; + } + + _LOGD("Interface: %s", interface); + if (!nm_modem_set_data_port(NM_MODEM(self), + NM_PLATFORM_GET, + interface, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_UNKNOWN, + 0, + &error)) { + _LOGW("failed to connect to modem: %s", error->message); + g_clear_error(&error); + goto out; + } + + ifindex = nm_modem_get_ip_ifindex(NM_MODEM(self)); + nm_assert(ifindex > 0); + + /* TODO: verify handling of ip4_config; check other places it's used... */ + g_clear_object(&priv->ip4_config); + + priv->ip4_config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), ifindex); + + if (!g_variant_lookup(v_dict, "Address", "&s", &s)) { + _LOGW("Settings 'Address' missing"); + goto out; + } + if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) { + _LOGW("can't convert 'Address' %s to addr", s ?: ""); + goto out; + } + memset(&addr, 0, sizeof(addr)); + addr.ifindex = ifindex; + addr.address = address_network; + addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN; + + if (!g_variant_lookup(v_dict, "Netmask", "&s", &s)) { + _LOGW("Settings 'Netmask' missing"); + goto out; + } + if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) { + _LOGW("invalid 'Netmask': %s", s ?: ""); + goto out; + } + addr.plen = nm_utils_ip4_netmask_to_prefix(address_network); + + _LOGI("Address: %s", nm_platform_ip4_address_to_string(&addr, NULL, 0)); + nm_ip4_config_add_address(priv->ip4_config, &addr); + + if (!g_variant_lookup(v_dict, "Gateway", "&s", &s) || !s) { + _LOGW("Settings 'Gateway' missing"); + goto out; + } + if (!nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &gateway_network)) { + _LOGW("invalid 'Gateway': %s", s); + goto out; + } + nm_modem_get_route_parameters(NM_MODEM(self), &ip4_route_table, &ip4_route_metric, NULL, NULL); + { + const NMPlatformIP4Route r = { + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = gateway_network, + .table_coerced = nm_platform_route_table_coerce(ip4_route_table), + .metric = ip4_route_metric, + }; + + _LOGI("Gateway: %s", s); + nm_ip4_config_add_route(priv->ip4_config, &r, NULL); + } + + if (!g_variant_lookup(v_dict, "DomainNameServers", "^a&s", &array)) { + _LOGW("Settings 'DomainNameServers' missing"); + goto out; + } + if (array) { + for (iter = array; *iter; iter++) { + if (nm_utils_parse_inaddr_bin(AF_INET, *iter, NULL, &address_network) + && address_network) { + _LOGI("DNS: %s", *iter); + nm_ip4_config_add_nameserver(priv->ip4_config, address_network); + } else { + _LOGW("invalid NameServer: %s", *iter); + } + } + + if (iter == array) { + _LOGW("Settings: 'DomainNameServers': none specified"); + g_free(array); + goto out; + } + g_free(array); + } + + if (g_variant_lookup(v_dict, "MessageProxy", "&s", &s)) { + _LOGI("MessageProxy: %s", s); + if (s && nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) { + nm_modem_get_route_parameters(NM_MODEM(self), + &ip4_route_table, + &ip4_route_metric, + NULL, + NULL); + + { + const NMPlatformIP4Route mms_route = { + .network = address_network, + .plen = 32, + .gateway = gateway_network, + .table_coerced = nm_platform_route_table_coerce(ip4_route_table), + .metric = ip4_route_metric, + }; + + nm_ip4_config_add_route(priv->ip4_config, &mms_route, NULL); + } + } else { + _LOGW("invalid MessageProxy: %s", s); + } + } + + ret = TRUE; + +out: + if (nm_modem_get_state(NM_MODEM(self)) != NM_MODEM_STATE_CONNECTED) { + _LOGI("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE"); + nm_modem_emit_prepare_result(NM_MODEM(self), + ret, + ret ? NM_DEVICE_STATE_REASON_NONE + : NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + } else { + _LOGW("MODEM_PPP_FAILED"); + nm_modem_emit_ppp_failed(NM_MODEM(self), NM_DEVICE_STATE_REASON_PPP_FAILED); + } +} + +static NMActStageReturn +static_stage3_ip4_config_start(NMModem * modem, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + GError * error = NULL; + + if (!priv->ip4_config) { + _LOGD("IP4 config not ready(?)"); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + _LOGD("IP4 config is done; setting modem_state -> CONNECTED"); + g_signal_emit_by_name(self, NM_MODEM_IP4_CONFIG_RESULT, priv->ip4_config, error); + + /* Signal listener takes ownership of the IP4Config */ + priv->ip4_config = NULL; + + nm_modem_set_state(NM_MODEM(self), + NM_MODEM_STATE_CONNECTED, + nm_modem_state_to_string(NM_MODEM_STATE_CONNECTED)); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGE("failed to create ofono ConnectionContext DBus proxy: %s", error->message); + g_clear_object(&priv->context_proxy_cancellable); + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY); + return; + } + + priv->context_proxy = proxy; + + if (!priv->gprs_attached) { + g_clear_object(&priv->context_proxy_cancellable); + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return; + } + + /* We have an old copy of the settings from a previous activation, + * clear it so that we can gate getting the IP config from oFono + * on whether or not we have already received them + */ + g_clear_object(&priv->ip4_config); + + _nm_dbus_signal_connect(priv->context_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(context_property_changed), + self); + + g_dbus_proxy_call(priv->context_proxy, + "SetProperty", + g_variant_new("(sv)", "Active", g_variant_new("b", TRUE)), + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->context_proxy_cancellable, + stage1_prepare_done, + self); +} + +static void +do_context_activate(NMModemOfono *self) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + g_return_if_fail(NM_IS_MODEM_OFONO(self)); + + nm_clear_g_cancellable(&priv->context_proxy_cancellable); + g_clear_object(&priv->context_proxy); + + priv->context_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + OFONO_DBUS_SERVICE, + priv->context_path, + OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT, + priv->context_proxy_cancellable, + context_proxy_new_cb, + self); +} + +static GHashTable * +create_connect_properties(NMConnection *connection) +{ + NMSettingGsm *setting; + GHashTable * properties; + const char * str; + + setting = nm_connection_get_setting_gsm(connection); + properties = g_hash_table_new(nm_str_hash, g_str_equal); + + str = nm_setting_gsm_get_apn(setting); + if (str) + g_hash_table_insert(properties, "AccessPointName", g_strdup(str)); + + str = nm_setting_gsm_get_username(setting); + if (str) + g_hash_table_insert(properties, "Username", g_strdup(str)); + + str = nm_setting_gsm_get_password(setting); + if (str) + g_hash_table_insert(properties, "Password", g_strdup(str)); + + return properties; +} + +static NMActStageReturn +modem_act_stage1_prepare(NMModem * modem, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason) +{ + NMModemOfono * self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + const char * context_id; + char ** id = NULL; + + context_id = nm_connection_get_id(connection); + id = g_strsplit(context_id, "/", 0); + g_return_val_if_fail(id[2], NM_ACT_STAGE_RETURN_FAILURE); + + _LOGD("trying %s %s", id[1], id[2]); + + g_free(priv->context_path); + priv->context_path = g_strdup_printf("%s/%s", nm_modem_get_path(modem), id[2]); + g_strfreev(id); + + if (!priv->context_path) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_GSM_APN_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (priv->connect_properties) + g_hash_table_destroy(priv->connect_properties); + + priv->connect_properties = create_connect_properties(connection); + + _LOGI("activating context %s", priv->context_path); + + if (nm_modem_get_state(modem) == NM_MODEM_STATE_REGISTERED) { + do_context_activate(self); + } else { + _LOGW("could not activate context: modem is not registered."); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +static void +modem_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMModemOfono * self; + NMModemOfonoPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy * proxy; + + proxy = g_dbus_proxy_new_for_bus_finish(result, &error); + if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_MODEM_OFONO(user_data); + priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + if (!proxy) { + _LOGE("failed to create ofono modem DBus proxy: %s", error->message); + g_clear_object(&priv->modem_proxy_cancellable); + return; + } + + priv->modem_proxy = proxy; + + _nm_dbus_signal_connect(priv->modem_proxy, + "PropertyChanged", + G_VARIANT_TYPE("(sv)"), + G_CALLBACK(modem_property_changed), + self); + + g_dbus_proxy_call(priv->modem_proxy, + "GetProperties", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->modem_proxy_cancellable, + modem_get_properties_done, + self); +} + +/*****************************************************************************/ + +static void +nm_modem_ofono_init(NMModemOfono *self) +{} + +static void +constructed(GObject *object) +{ + NMModemOfono * self = NM_MODEM_OFONO(object); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + priv->modem_proxy_cancellable = g_cancellable_new(); + + g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + OFONO_DBUS_SERVICE, + nm_modem_get_path(NM_MODEM(self)), + OFONO_DBUS_INTERFACE_MODEM, + priv->modem_proxy_cancellable, + modem_proxy_new_cb, + self); + + G_OBJECT_CLASS(nm_modem_ofono_parent_class)->constructed(object); +} + +NMModem * +nm_modem_ofono_new(const char *path) +{ + gs_free char *basename = NULL; + + g_return_val_if_fail(path != NULL, NULL); + + nm_log_info(LOGD_MB, "ofono: creating new Ofono modem path %s", path); + + /* Use short modem name (not its object path) as the NM device name (which + * comes from NM_MODEM_UID)and the device ID. + */ + basename = g_path_get_basename(path); + + return g_object_new(NM_TYPE_MODEM_OFONO, + NM_MODEM_PATH, + path, + NM_MODEM_UID, + basename, + NM_MODEM_DEVICE_ID, + basename, + NM_MODEM_CONTROL_PORT, + "ofono", /* mandatory */ + NM_MODEM_DRIVER, + "ofono", + NM_MODEM_STATE, + (int) NM_MODEM_STATE_INITIALIZING, + NULL); +} + +static void +dispose(GObject *object) +{ + NMModemOfono * self = NM_MODEM_OFONO(object); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->modem_proxy_cancellable); + nm_clear_g_cancellable(&priv->connman_proxy_cancellable); + nm_clear_g_cancellable(&priv->context_proxy_cancellable); + nm_clear_g_cancellable(&priv->sim_proxy_cancellable); + + if (priv->connect_properties) { + g_hash_table_destroy(priv->connect_properties); + priv->connect_properties = NULL; + } + + g_clear_object(&priv->ip4_config); + + if (priv->modem_proxy) { + g_signal_handlers_disconnect_by_data(priv->modem_proxy, self); + g_clear_object(&priv->modem_proxy); + } + + if (priv->connman_proxy) { + g_signal_handlers_disconnect_by_data(priv->connman_proxy, self); + g_clear_object(&priv->connman_proxy); + } + + if (priv->context_proxy) { + g_signal_handlers_disconnect_by_data(priv->context_proxy, self); + g_clear_object(&priv->context_proxy); + } + + if (priv->sim_proxy) { + g_signal_handlers_disconnect_by_data(priv->sim_proxy, self); + g_clear_object(&priv->sim_proxy); + } + + g_free(priv->imsi); + priv->imsi = NULL; + + G_OBJECT_CLASS(nm_modem_ofono_parent_class)->dispose(object); +} + +static void +nm_modem_ofono_class_init(NMModemOfonoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMModemClass *modem_class = NM_MODEM_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + + modem_class->get_capabilities = get_capabilities; + modem_class->disconnect = disconnect; + modem_class->deactivate_cleanup = deactivate_cleanup; + modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem; + + modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare; + modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start; +} diff --git a/src/core/devices/wwan/nm-modem-ofono.h b/src/core/devices/wwan/nm-modem-ofono.h new file mode 100644 index 00000000..260e3954 --- /dev/null +++ b/src/core/devices/wwan/nm-modem-ofono.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 - Canonical Ltd. + */ + +#ifndef NM_MODEM_OFONO_H +#define NM_MODEM_OFONO_H + +#include "nm-modem.h" + +#define NM_TYPE_MODEM_OFONO (nm_modem_ofono_get_type()) +#define NM_MODEM_OFONO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_OFONO, NMModemOfono)) +#define NM_IS_MODEM_OFONO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_OFONO)) +#define NM_MODEM_OFONO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM_OFONO, NMModemOfonoClass)) +#define NM_IS_MODEM_OFONO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM_OFONO)) +#define NM_MODEM_OFONO_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM_OFONO, NMModemOfonoClass)) + +#define OFONO_DBUS_SERVICE "org.ofono" +#define OFONO_DBUS_PATH "/" +#define OFONO_DBUS_INTERFACE "org.ofono.Manager" +#define OFONO_DBUS_INTERFACE_MODEM "org.ofono.Modem" +#define OFONO_DBUS_INTERFACE_CONNECTION_MANAGER "org.ofono.ConnectionManager" +#define OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT "org.ofono.ConnectionContext" +#define OFONO_DBUS_INTERFACE_SIM_MANAGER "org.ofono.SimManager" + +typedef struct _NMModemOfono NMModemOfono; +typedef struct _NMModemOfonoClass NMModemOfonoClass; + +GType nm_modem_ofono_get_type(void); + +NMModem *nm_modem_ofono_new(const char *path); + +#endif /* NM_MODEM_OFONO_H */ diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c new file mode 100644 index 00000000..0d334fa4 --- /dev/null +++ b/src/core/devices/wwan/nm-modem.c @@ -0,0 +1,2037 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2014 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-modem.h" + +#include <fcntl.h> +#include <termios.h> +#include <linux/rtnetlink.h> + +#include "nm-core-internal.h" +#include "platform/nm-platform.h" +#include "nm-setting-connection.h" +#include "NetworkManagerUtils.h" +#include "devices/nm-device-private.h" +#include "nm-netns.h" +#include "nm-act-request.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "ppp/nm-ppp-manager-call.h" +#include "ppp/nm-ppp-status.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMModem, + PROP_CONTROL_PORT, + PROP_IP_IFINDEX, + PROP_PATH, + PROP_UID, + PROP_DRIVER, + PROP_STATE, + PROP_DEVICE_ID, + PROP_SIM_ID, + PROP_IP_TYPES, + PROP_SIM_OPERATOR_ID, + PROP_OPERATOR_CODE, + PROP_APN, ); + +enum { + PPP_STATS, + PPP_FAILED, + PREPARE_RESULT, + IP4_CONFIG_RESULT, + IP6_CONFIG_RESULT, + AUTH_REQUESTED, + AUTH_RESULT, + REMOVED, + STATE_CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct _NMModemPrivate { + char *uid; + char *path; + char *driver; + char *control_port; + char *data_port; + + /* TODO: ip_iface is solely used for nm_modem_owns_port(). + * We should rework the code that it's not necessary */ + char *ip_iface; + + int ip_ifindex; + NMModemIPMethod ip4_method; + NMModemIPMethod ip6_method; + NMUtilsIPv6IfaceId iid; + NMModemState state; + NMModemState prev_state; /* revert to this state if enable/disable fails */ + char * device_id; + char * sim_id; + NMModemIPType ip_types; + char * sim_operator_id; + char * operator_code; + char * apn; + + NMPPPManager *ppp_manager; + + NMActRequest * act_request; + guint32 secrets_tries; + NMActRequestGetSecretsCallId *secrets_id; + + guint mm_ip_timeout; + + guint32 ip4_route_table; + guint32 ip4_route_metric; + guint32 ip6_route_table; + guint32 ip6_route_metric; + + /* PPP stats */ + guint32 in_bytes; + guint32 out_bytes; + + bool claimed : 1; +} NMModemPrivate; + +G_DEFINE_TYPE(NMModem, nm_modem, G_TYPE_OBJECT) + +#define NM_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMModem, NM_IS_MODEM) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_BUFLEN 64 +#define _NMLOG_PREFIX_NAME "modem" +#define _NMLOG_DOMAIN LOGD_MB + +static const char * +_nmlog_prefix(char *prefix, NMModem *self) +{ + const char *uuid; + int c; + + if (!self) + return ""; + + uuid = nm_modem_get_uid(self); + + if (uuid) { + char pp[_NMLOG_PREFIX_BUFLEN - 5]; + + c = g_snprintf(prefix, _NMLOG_PREFIX_BUFLEN, "[%s]", nm_strquote(pp, sizeof(pp), uuid)); + } else + c = g_snprintf(prefix, _NMLOG_PREFIX_BUFLEN, "[%p]", self); + nm_assert(c < _NMLOG_PREFIX_BUFLEN); + + return prefix; +} + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _prefix[_NMLOG_PREFIX_BUFLEN]; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + _nmlog_prefix(_prefix, (self)) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _set_ip_ifindex(NMModem *self, int ifindex, const char *ifname); + +/*****************************************************************************/ +/* State/enabled/connected */ + +static const char *state_table[] = { + [NM_MODEM_STATE_UNKNOWN] = "unknown", + [NM_MODEM_STATE_FAILED] = "failed", + [NM_MODEM_STATE_INITIALIZING] = "initializing", + [NM_MODEM_STATE_LOCKED] = "locked", + [NM_MODEM_STATE_DISABLED] = "disabled", + [NM_MODEM_STATE_DISABLING] = "disabling", + [NM_MODEM_STATE_ENABLING] = "enabling", + [NM_MODEM_STATE_ENABLED] = "enabled", + [NM_MODEM_STATE_SEARCHING] = "searching", + [NM_MODEM_STATE_REGISTERED] = "registered", + [NM_MODEM_STATE_DISCONNECTING] = "disconnecting", + [NM_MODEM_STATE_CONNECTING] = "connecting", + [NM_MODEM_STATE_CONNECTED] = "connected", +}; + +const char * +nm_modem_state_to_string(NMModemState state) +{ + if ((gsize) state < G_N_ELEMENTS(state_table)) + return state_table[state]; + return NULL; +} + +/*****************************************************************************/ + +gboolean +nm_modem_is_claimed(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), FALSE); + + return NM_MODEM_GET_PRIVATE(self)->claimed; +} + +NMModem * +nm_modem_claim(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + priv = NM_MODEM_GET_PRIVATE(self); + + g_return_val_if_fail(!priv->claimed, NULL); + + priv->claimed = TRUE; + return g_object_ref(self); +} + +void +nm_modem_unclaim(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + + g_return_if_fail(priv->claimed); + + /* we don't actually unclaim the instance. This instance should not be re-used + * by another owner, that is because we only claim modems as we receive them. + * There is no mechanism that somebody else would later re-use them again. + * + * // priv->claimed = FALSE; */ + + g_object_unref(self); +} + +/*****************************************************************************/ + +NMModemState +nm_modem_get_state(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->state; +} + +void +nm_modem_set_state(NMModem *self, NMModemState new_state, const char *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + NMModemState old_state = priv->state; + + priv->prev_state = NM_MODEM_STATE_UNKNOWN; + + if (new_state != old_state) { + _LOGI("modem state changed, '%s' --> '%s' (reason: %s)", + nm_modem_state_to_string(old_state), + nm_modem_state_to_string(new_state), + reason ?: "none"); + + priv->state = new_state; + _notify(self, PROP_STATE); + g_signal_emit(self, signals[STATE_CHANGED], 0, (int) new_state, (int) old_state); + } +} + +void +nm_modem_set_prev_state(NMModem *self, const char *reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + /* Reset modem to previous state if the state hasn't already changed */ + if (priv->prev_state != NM_MODEM_STATE_UNKNOWN) + nm_modem_set_state(self, priv->prev_state, reason); +} + +void +nm_modem_set_mm_enabled(NMModem *self, gboolean enabled) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + NMModemState prev_state = priv->state; + + if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) { + _LOGD("cannot enable modem: already enabled"); + return; + } + if (!enabled && priv->state <= NM_MODEM_STATE_DISABLING) { + _LOGD("cannot disable modem: already disabled"); + return; + } + + if (priv->state <= NM_MODEM_STATE_INITIALIZING) { + _LOGD("cannot enable/disable modem: initializing or failed"); + return; + } else if (priv->state == NM_MODEM_STATE_LOCKED) { + /* Don't try to enable if the modem is locked since that will fail */ + _LOGW("cannot enable/disable modem: locked"); + + /* Try to unlock the modem if it's being enabled */ + if (enabled) + g_signal_emit(self, signals[AUTH_REQUESTED], 0); + return; + } + + /* Not all modem classes support set_mm_enabled */ + if (NM_MODEM_GET_CLASS(self)->set_mm_enabled) + NM_MODEM_GET_CLASS(self)->set_mm_enabled(self, enabled); + + /* Pre-empt the state change signal */ + nm_modem_set_state(self, + enabled ? NM_MODEM_STATE_ENABLING : NM_MODEM_STATE_DISABLING, + "user preference"); + priv->prev_state = prev_state; +} + +void +nm_modem_emit_removed(NMModem *self) +{ + g_signal_emit(self, signals[REMOVED], 0); +} + +void +nm_modem_emit_prepare_result(NMModem *self, gboolean success, NMDeviceStateReason reason) +{ + nm_assert(NM_IS_MODEM(self)); + + g_signal_emit(self, signals[PREPARE_RESULT], 0, success, (guint) reason); +} + +void +nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason) +{ + nm_assert(NM_IS_MODEM(self)); + + g_signal_emit(self, signals[PPP_FAILED], 0, (guint) reason); +} + +NMModemIPType +nm_modem_get_supported_ip_types(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->ip_types; +} + +const char * +nm_modem_ip_type_to_string(NMModemIPType ip_type) +{ + switch (ip_type) { + case NM_MODEM_IP_TYPE_IPV4: + return "ipv4"; + case NM_MODEM_IP_TYPE_IPV6: + return "ipv6"; + case NM_MODEM_IP_TYPE_IPV4V6: + return "ipv4v6"; + default: + g_return_val_if_reached("unknown"); + } +} + +static GArray * +build_single_ip_type_array(NMModemIPType type) +{ + return g_array_append_val(g_array_sized_new(FALSE, FALSE, sizeof(NMModemIPType), 1), type); +} + +/** + * nm_modem_get_connection_ip_type: + * @self: the #NMModem + * @connection: the #NMConnection to determine IP type to use + * + * Given a modem and a connection, determine which #NMModemIPTypes to use + * when connecting. + * + * Returns: an array of #NMModemIpType values, in the order in which they + * should be tried. + */ +GArray * +nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + NMSettingIPConfig *s_ip4, *s_ip6; + const char * method; + gboolean ip4 = TRUE, ip6 = TRUE; + gboolean ip4_may_fail = TRUE, ip6_may_fail = TRUE; + + s_ip4 = nm_connection_get_setting_ip4_config(connection); + if (s_ip4) { + method = nm_setting_ip_config_get_method(s_ip4); + if (g_strcmp0(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0) + ip4 = FALSE; + ip4_may_fail = nm_setting_ip_config_get_may_fail(s_ip4); + } + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + if (s_ip6) { + method = nm_setting_ip_config_get_method(s_ip6); + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) + ip6 = FALSE; + ip6_may_fail = nm_setting_ip_config_get_may_fail(s_ip6); + } + + if (ip4 && !ip6) { + if (!(priv->ip_types & NM_MODEM_IP_TYPE_IPV4)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection requested IPv4 but IPv4 is " + "unsupported by the modem."); + return NULL; + } + return build_single_ip_type_array(NM_MODEM_IP_TYPE_IPV4); + } + + if (ip6 && !ip4) { + if (!(priv->ip_types & NM_MODEM_IP_TYPE_IPV6)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection requested IPv6 but IPv6 is " + "unsupported by the modem."); + return NULL; + } + return build_single_ip_type_array(NM_MODEM_IP_TYPE_IPV6); + } + + if (ip4 && ip6) { + NMModemIPType type; + GArray * out; + + out = g_array_sized_new(FALSE, FALSE, sizeof(NMModemIPType), 3); + + /* Modem supports dual-stack? */ + if (priv->ip_types & NM_MODEM_IP_TYPE_IPV4V6) { + type = NM_MODEM_IP_TYPE_IPV4V6; + g_array_append_val(out, type); + } + + /* If IPv6 may-fail=false, we should NOT try IPv4 as fallback */ + if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV4) && ip6_may_fail) { + type = NM_MODEM_IP_TYPE_IPV4; + g_array_append_val(out, type); + } + + /* If IPv4 may-fail=false, we should NOT try IPv6 as fallback */ + if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV6) && ip4_may_fail) { + type = NM_MODEM_IP_TYPE_IPV6; + g_array_append_val(out, type); + } + + if (out->len > 0) + return out; + + /* Error... */ + g_array_unref(out); + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection requested both IPv4 and IPv6 " + "but dual-stack addressing is unsupported " + "by the modem."); + return NULL; + } + + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION, + "Connection specified no IP configuration!"); + return NULL; +} + +const char * +nm_modem_get_device_id(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->device_id; +} + +const char * +nm_modem_get_sim_id(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->sim_id; +} + +const char * +nm_modem_get_sim_operator_id(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->sim_operator_id; +} + +const char * +nm_modem_get_operator_code(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->operator_code; +} + +const char * +nm_modem_get_apn(NMModem *self) +{ + return NM_MODEM_GET_PRIVATE(self)->apn; +} + +/*****************************************************************************/ +/* IP method PPP */ + +static void +ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data) +{ + switch (status) { + case NM_PPP_STATUS_DISCONNECT: + nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_DISCONNECT); + break; + case NM_PPP_STATUS_DEAD: + nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_FAILED); + break; + default: + break; + } +} + +static void +ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data) +{ + NMModem *self = NM_MODEM(user_data); + + nm_assert(ifindex >= 0); + nm_assert(NM_MODEM_GET_PRIVATE(self)->ppp_manager == ppp_manager); + + if (ifindex <= 0 && iface) { + /* this might happen, if the ifname was already deleted + * and we failed to resolve ifindex. + * + * Forget about the name. */ + iface = NULL; + } + _set_ip_ifindex(self, ifindex, iface); +} + +static void +ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data) +{ + NMModem *self = NM_MODEM(user_data); + guint32 i, num; + guint32 bad_dns1 = htonl(0x0A0B0C0D); + guint32 good_dns1 = htonl(0x04020201); /* GTE nameserver */ + guint32 bad_dns2 = htonl(0x0A0B0C0E); + guint32 good_dns2 = htonl(0x04020202); /* GTE nameserver */ + gboolean dns_workaround = FALSE; + + /* Work around a PPP bug (#1732) which causes many mobile broadband + * providers to return 10.11.12.13 and 10.11.12.14 for the DNS servers. + * Apparently fixed in ppp-2.4.5 but we've had some reports that this is + * not the case. + * + * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=2e09ef6886bbf00bc5a9a641110f801e372ffde6 + * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=f8191bf07df374f119a07910a79217c7618f113e + */ + + num = nm_ip4_config_get_num_nameservers(config); + if (num == 2) { + gboolean found1 = FALSE, found2 = FALSE; + + for (i = 0; i < num; i++) { + guint32 ns = nm_ip4_config_get_nameserver(config, i); + + if (ns == bad_dns1) + found1 = TRUE; + else if (ns == bad_dns2) + found2 = TRUE; + } + + /* Be somewhat conservative about substitutions; the "bad" nameservers + * could actually be valid in some cases, so only substitute if ppp + * returns *only* the two bad nameservers. + */ + dns_workaround = (found1 && found2); + } + + if (!num || dns_workaround) { + _LOGW("compensating for invalid PPP-provided nameservers"); + nm_ip4_config_reset_nameservers(config); + nm_ip4_config_add_nameserver(config, good_dns1); + nm_ip4_config_add_nameserver(config, good_dns2); + } + + g_signal_emit(self, signals[IP4_CONFIG_RESULT], 0, config, NULL); +} + +static void +ppp_ip6_config(NMPPPManager * ppp_manager, + const NMUtilsIPv6IfaceId *iid, + NMIP6Config * config, + gpointer user_data) +{ + NMModem *self = NM_MODEM(user_data); + + NM_MODEM_GET_PRIVATE(self)->iid = *iid; + + nm_modem_emit_ip6_config_result(self, config, NULL); +} + +static void +ppp_stats(NMPPPManager *ppp_manager, guint i_in_bytes, guint i_out_bytes, gpointer user_data) +{ + NMModem * self = NM_MODEM(user_data); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + guint32 in_bytes = i_in_bytes; + guint32 out_bytes = i_out_bytes; + + if (priv->in_bytes != in_bytes || priv->out_bytes != out_bytes) { + priv->in_bytes = in_bytes; + priv->out_bytes = out_bytes; + g_signal_emit(self, signals[PPP_STATS], 0, (guint) in_bytes, (guint) out_bytes); + } +} + +static gboolean +port_speed_is_zero(const char *port) +{ + struct termios options; + nm_auto_close int fd = -1; + gs_free char * path = NULL; + + nm_assert(port); + + if (port[0] != '/') { + if (!port[0] || strchr(port, '/') || NM_IN_STRSET(port, ".", "..")) + return FALSE; + path = g_build_path("/sys/class/tty", port, NULL); + port = path; + } + + fd = open(port, O_RDWR | O_NONBLOCK | O_NOCTTY | O_CLOEXEC); + if (fd < 0) + return FALSE; + + memset(&options, 0, sizeof(struct termios)); + if (tcgetattr(fd, &options) != 0) + return FALSE; + + return cfgetospeed(&options) == B0; +} + +static NMActStageReturn +ppp_stage3_ip_config_start(NMModem * self, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + const char * ppp_name = NULL; + GError * error = NULL; + guint ip_timeout = 30; + guint baud_override = 0; + + g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE); + + /* If we're already running PPP don't restart it; for example, if both + * IPv4 and IPv6 are requested, IPv4 gets started first, but we use the + * same pppd for both v4 and v6. + */ + if (priv->ppp_manager) + return NM_ACT_STAGE_RETURN_POSTPONE; + + if (NM_MODEM_GET_CLASS(self)->get_user_pass) { + NMConnection *connection = nm_act_request_get_applied_connection(req); + + g_assert(connection); + if (!NM_MODEM_GET_CLASS(self)->get_user_pass(self, connection, &ppp_name, NULL)) + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (!priv->data_port) { + _LOGE("error starting PPP (no data port)"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + /* Check if ModemManager requested a specific IP timeout to be used. If 0 reported, + * use the default one (30s) */ + if (priv->mm_ip_timeout > 0) { + _LOGI("using modem-specified IP timeout: %u seconds", priv->mm_ip_timeout); + ip_timeout = priv->mm_ip_timeout; + } + + /* Some tty drivers and modems ignore port speed, but pppd requires the + * port speed to be > 0 or it exits. If the port speed is 0 pass an + * explicit speed to pppd to prevent the exit. + * https://bugzilla.redhat.com/show_bug.cgi?id=1281731 + */ + if (port_speed_is_zero(priv->data_port)) + baud_override = 57600; + + priv->ppp_manager = nm_ppp_manager_create(priv->data_port, &error); + + if (priv->ppp_manager) { + nm_ppp_manager_set_route_parameters(priv->ppp_manager, + priv->ip4_route_table, + priv->ip4_route_metric, + priv->ip6_route_table, + priv->ip6_route_metric); + } + + if (!priv->ppp_manager + || !nm_ppp_manager_start(priv->ppp_manager, + req, + ppp_name, + ip_timeout, + baud_override, + &error)) { + _LOGE("error starting PPP: %s", error->message); + g_error_free(error); + g_clear_object(&priv->ppp_manager); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_STATE_CHANGED, + G_CALLBACK(ppp_state_changed), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IFINDEX_SET, + G_CALLBACK(ppp_ifindex_set), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IP4_CONFIG, + G_CALLBACK(ppp_ip4_config), + self); + g_signal_connect(priv->ppp_manager, + NM_PPP_MANAGER_SIGNAL_IP6_CONFIG, + G_CALLBACK(ppp_ip6_config), + self); + g_signal_connect(priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATS, G_CALLBACK(ppp_stats), self); + + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +NMActStageReturn +nm_modem_stage3_ip4_config_start(NMModem * self, + NMDevice * device, + NMDeviceClass * device_class, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate * priv; + NMActRequest * req; + NMConnection * connection; + const char * method; + NMActStageReturn ret; + + _LOGD("ip4_config_start"); + + g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail(NM_IS_DEVICE(device), NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail(NM_IS_DEVICE_CLASS(device_class), NM_ACT_STAGE_RETURN_FAILURE); + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + nm_modem_set_route_parameters_from_device(self, device); + + method = nm_utils_get_ip_config_method(connection, AF_INET); + + /* Only Disabled and Auto methods make sense for WWAN */ + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + return NM_ACT_STAGE_RETURN_SUCCESS; + + if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + _LOGE("unhandled WWAN IPv4 method '%s'; will fail", method); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv = NM_MODEM_GET_PRIVATE(self); + switch (priv->ip4_method) { + case NM_MODEM_IP_METHOD_PPP: + ret = ppp_stage3_ip_config_start(self, req, out_failure_reason); + break; + case NM_MODEM_IP_METHOD_STATIC: + _LOGD("MODEM_IP_METHOD_STATIC"); + ret = + NM_MODEM_GET_CLASS(self)->static_stage3_ip4_config_start(self, req, out_failure_reason); + break; + case NM_MODEM_IP_METHOD_AUTO: + _LOGD("MODEM_IP_METHOD_AUTO"); + ret = device_class->act_stage3_ip_config_start(device, AF_INET, NULL, out_failure_reason); + break; + default: + _LOGI("IPv4 configuration disabled"); + ret = NM_ACT_STAGE_RETURN_IP_FAIL; + break; + } + + return ret; +} + +void +nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(modem); + + /* If the modem has an ethernet-type data interface (ie, not PPP and thus + * not point-to-point) and IP config has a /32 prefix, then we assume that + * ARP will be pointless and we turn it off. + */ + if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC + || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO) { + const NMPlatformIP4Address *address = nm_ip4_config_get_first_address(config); + + g_assert(address); + if (address->plen == 32) + nm_platform_link_set_noarp(nm_device_get_platform(device), + nm_device_get_ip_ifindex(device)); + } +} + +/*****************************************************************************/ + +void +nm_modem_emit_ip6_config_result(NMModem *self, NMIP6Config *config, GError *error) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + NMDedupMultiIter ipconf_iter; + const NMPlatformIP6Address *addr; + gboolean do_slaac = TRUE; + + if (error) { + g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, NULL, FALSE, error); + return; + } + + if (config) { + /* If the IPv6 configuration only included a Link-Local address, then + * we have to run SLAAC to get the full IPv6 configuration. + */ + nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, config, &addr) { + if (IN6_IS_ADDR_LINKLOCAL(&addr->address)) { + if (!priv->iid.id) + priv->iid.id = ((guint64 *) (&addr->address.s6_addr))[1]; + } else + do_slaac = FALSE; + } + } + g_assert(config || do_slaac); + + g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, config, do_slaac, NULL); +} + +static NMActStageReturn +stage3_ip6_config_request(NMModem *self, NMDeviceStateReason *out_failure_reason) +{ + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return NM_ACT_STAGE_RETURN_FAILURE; +} + +NMActStageReturn +nm_modem_stage3_ip6_config_start(NMModem * self, + NMDevice * device, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate * priv; + NMActRequest * req; + NMActStageReturn ret; + NMConnection * connection; + const char * method; + + g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE); + + req = nm_device_get_act_request(device); + g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + nm_modem_set_route_parameters_from_device(self, device); + + method = nm_utils_get_ip_config_method(connection, AF_INET6); + + /* Only Ignore, Disabled and Auto methods make sense for WWAN */ + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) + return NM_ACT_STAGE_RETURN_IP_DONE; + + if (!nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) { + _LOGW("unhandled WWAN IPv6 method '%s'; will fail", method); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv = NM_MODEM_GET_PRIVATE(self); + switch (priv->ip6_method) { + case NM_MODEM_IP_METHOD_PPP: + ret = ppp_stage3_ip_config_start(self, req, out_failure_reason); + break; + case NM_MODEM_IP_METHOD_STATIC: + case NM_MODEM_IP_METHOD_AUTO: + /* Both static and DHCP/Auto retrieve a base IP config from the modem + * which in the static case is the full config, and the DHCP/Auto case + * is just the IPv6LL address to use for SLAAC. + */ + ret = NM_MODEM_GET_CLASS(self)->stage3_ip6_config_request(self, out_failure_reason); + break; + default: + _LOGI("IPv6 configuration disabled"); + ret = NM_ACT_STAGE_RETURN_IP_FAIL; + break; + } + + return ret; +} + +guint32 +nm_modem_get_configured_mtu(NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + NMConnection *connection; + NMSetting * setting; + gint64 mtu_default; + guint mtu = 0; + const char * property_name; + + nm_assert(NM_IS_DEVICE(self)); + nm_assert(out_source); + + connection = nm_device_get_applied_connection(self); + if (!connection) + g_return_val_if_reached(0); + + setting = (NMSetting *) nm_connection_get_setting_gsm(connection); + if (!setting) + setting = (NMSetting *) nm_connection_get_setting_cdma(connection); + + if (setting) { + g_object_get(setting, "mtu", &mtu, NULL); + if (mtu) { + *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION; + return mtu; + } + + property_name = NM_IS_SETTING_GSM(setting) ? "gsm.mtu" : "cdma.mtu"; + mtu_default = + nm_device_get_configured_mtu_from_connection_default(self, property_name, G_MAXUINT32); + if (mtu_default >= 0) { + *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION; + return (guint32) mtu_default; + } + } + + *out_source = NM_DEVICE_MTU_SOURCE_NONE; + return 0; +} + +/*****************************************************************************/ + +static void +cancel_get_secrets(NMModem *self) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (priv->secrets_id) + nm_act_request_cancel_secrets(priv->act_request, priv->secrets_id); +} + +static void +modem_secrets_cb(NMActRequest * req, + NMActRequestGetSecretsCallId *call_id, + NMSettingsConnection * connection, + GError * error, + gpointer user_data) +{ + NMModem * self = NM_MODEM(user_data); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + g_return_if_fail(call_id == priv->secrets_id); + + priv->secrets_id = NULL; + + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) + || g_error_matches(error, NM_AGENT_MANAGER_ERROR, NM_AGENT_MANAGER_ERROR_NO_SECRETS)) + return; + + if (error) + _LOGW("modem-secrets: %s", error->message); + + g_signal_emit(self, signals[AUTH_RESULT], 0, error); +} + +void +nm_modem_get_secrets(NMModem * self, + const char *setting_name, + gboolean request_new, + const char *hint) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + + cancel_get_secrets(self); + + if (request_new) + flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + priv->secrets_id = nm_act_request_get_secrets(priv->act_request, + FALSE, + setting_name, + flags, + NM_MAKE_STRV(hint), + modem_secrets_cb, + self); + g_return_if_fail(priv->secrets_id); + g_signal_emit(self, signals[AUTH_REQUESTED], 0); +} + +/*****************************************************************************/ + +static NMActStageReturn +modem_act_stage1_prepare(NMModem * modem, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason) +{ + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_UNKNOWN); + return NM_ACT_STAGE_RETURN_FAILURE; +} + +NMActStageReturn +nm_modem_act_stage1_prepare(NMModem * self, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *hints = NULL; + const char * setting_name = NULL; + NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + NMConnection * connection; + + g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE); + + if (priv->act_request) + g_object_unref(priv->act_request); + priv->act_request = g_object_ref(req); + + connection = nm_act_request_get_applied_connection(req); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + setting_name = nm_connection_need_secrets(connection, &hints); + if (!setting_name) { + nm_assert(!hints); + return NM_MODEM_GET_CLASS(self)->modem_act_stage1_prepare(self, + connection, + out_failure_reason); + } + + /* Secrets required... */ + if (priv->secrets_tries++) + flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; + + if (hints) + g_ptr_array_add(hints, NULL); + + priv->secrets_id = nm_act_request_get_secrets(req, + FALSE, + setting_name, + flags, + hints ? (const char *const *) hints->pdata : NULL, + modem_secrets_cb, + self); + g_return_val_if_fail(priv->secrets_id, NM_ACT_STAGE_RETURN_FAILURE); + g_signal_emit(self, signals[AUTH_REQUESTED], 0); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +void +nm_modem_act_stage2_config(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + /* Clear secrets tries counter since secrets were successfully used + * already if we get here. + */ + priv->secrets_tries = 0; +} + +/*****************************************************************************/ + +gboolean +nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GError **error) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_GSM_SETTING_NAME)) { + NMSettingGsm *s_gsm; + const char * str; + + s_gsm = _nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, error); + if (!s_gsm) + return FALSE; + + str = nm_setting_gsm_get_device_id(s_gsm); + if (str) { + if (!priv->device_id) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "GSM profile has device-id, device does not"); + return FALSE; + } + if (!nm_streq(str, priv->device_id)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has differing device-id than GSM profile"); + return FALSE; + } + } + + /* SIM properties may not be available before the SIM is unlocked, so + * to ensure that autoconnect works, the connection's SIM properties + * are only compared if present on the device. + */ + + if (priv->sim_id && (str = nm_setting_gsm_get_sim_id(s_gsm))) { + if (!nm_streq(str, priv->sim_id)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has differing sim-id than GSM profile"); + return FALSE; + } + } + + if (priv->sim_operator_id && (str = nm_setting_gsm_get_sim_operator_id(s_gsm))) { + if (!nm_streq(str, priv->sim_operator_id)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device has differing sim-operator-id than GSM profile"); + return FALSE; + } + } + } + + return NM_MODEM_GET_CLASS(self)->check_connection_compatible_with_modem(self, + connection, + error); +} + +/*****************************************************************************/ + +gboolean +nm_modem_complete_connection(NMModem * self, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error) +{ + NMModemClass *klass; + + klass = NM_MODEM_GET_CLASS(self); + if (!klass->complete_connection) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "Modem class %s had no complete_connection method", + G_OBJECT_TYPE_NAME(self)); + return FALSE; + } + + return klass->complete_connection(self, iface, connection, existing_connections, error); +} + +/*****************************************************************************/ + +static void +deactivate_cleanup(NMModem *self, NMDevice *device, gboolean stop_ppp_manager) +{ + NMModemPrivate *priv; + int ifindex; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + + priv->secrets_tries = 0; + + if (priv->act_request) { + cancel_get_secrets(self); + g_object_unref(priv->act_request); + priv->act_request = NULL; + } + + priv->in_bytes = priv->out_bytes = 0; + + if (priv->ppp_manager) { + g_signal_handlers_disconnect_by_data(priv->ppp_manager, self); + if (stop_ppp_manager) + nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL); + g_clear_object(&priv->ppp_manager); + } + + if (device) { + g_return_if_fail(NM_IS_DEVICE(device)); + + if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC + || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO + || priv->ip6_method == NM_MODEM_IP_METHOD_STATIC + || priv->ip6_method == NM_MODEM_IP_METHOD_AUTO) { + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex > 0) { + NMPlatform *platform = nm_device_get_platform(device); + + nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex); + nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex); + nm_platform_link_set_down(platform, ifindex); + } + } + } + + nm_clear_g_free(&priv->data_port); + priv->mm_ip_timeout = 0; + priv->ip4_method = NM_MODEM_IP_METHOD_UNKNOWN; + priv->ip6_method = NM_MODEM_IP_METHOD_UNKNOWN; + _set_ip_ifindex(self, -1, NULL); +} + +/*****************************************************************************/ + +typedef struct { + NMModem * self; + NMDevice * device; + GCancellable * cancellable; + NMModemDeactivateCallback callback; + gpointer callback_user_data; +} DeactivateContext; + +static void +deactivate_context_complete(DeactivateContext *ctx, GError *error) +{ + NMModem *self = ctx->self; + + _LOGD("modem deactivation finished %s%s%s", + NM_PRINT_FMT_QUOTED(error, "with failure: ", error->message, "", "successfully")); + + if (ctx->callback) + ctx->callback(ctx->self, error, ctx->callback_user_data); + nm_g_object_unref(ctx->cancellable); + g_object_unref(ctx->device); + g_object_unref(ctx->self); + g_slice_free(DeactivateContext, ctx); +} + +static void +_deactivate_call_disconnect_cb(NMModem *self, GError *error, gpointer user_data) +{ + deactivate_context_complete(user_data, error); +} + +static void +_deactivate_call_disconnect(DeactivateContext *ctx) +{ + NM_MODEM_GET_CLASS(ctx->self)->disconnect(ctx->self, + FALSE, + ctx->cancellable, + _deactivate_call_disconnect_cb, + ctx); +} + +static void +_deactivate_ppp_manager_stop_cb(NMPPPManager * ppp_manager, + NMPPPManagerStopHandle *handle, + gboolean was_cancelled, + gpointer user_data) +{ + DeactivateContext *ctx = user_data; + + g_object_unref(ppp_manager); + + if (was_cancelled) { + gs_free_error GError *error = NULL; + + if (!g_cancellable_set_error_if_cancelled(ctx->cancellable, &error)) + nm_assert_not_reached(); + deactivate_context_complete(ctx, error); + return; + } + + nm_assert(!g_cancellable_is_cancelled(ctx->cancellable)); + _deactivate_call_disconnect(ctx); +} + +void +nm_modem_deactivate_async(NMModem * self, + NMDevice * device, + GCancellable * cancellable, + NMModemDeactivateCallback callback, + gpointer user_data) +{ + NMModemPrivate * priv = NM_MODEM_GET_PRIVATE(self); + DeactivateContext *ctx; + NMPPPManager * ppp_manager; + + g_return_if_fail(NM_IS_MODEM(self)); + g_return_if_fail(NM_IS_DEVICE(device)); + g_return_if_fail(G_IS_CANCELLABLE(cancellable)); + + ctx = g_slice_new(DeactivateContext); + ctx->self = g_object_ref(self); + ctx->device = g_object_ref(device); + ctx->cancellable = g_object_ref(cancellable); + ctx->callback = callback; + ctx->callback_user_data = user_data; + + ppp_manager = nm_g_object_ref(priv->ppp_manager); + + NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, ctx->device, FALSE); + + if (ppp_manager) { + /* If we have a PPP manager, stop it. + * + * Pass on the reference in @ppp_manager. */ + nm_ppp_manager_stop(ppp_manager, ctx->cancellable, _deactivate_ppp_manager_stop_cb, ctx); + return; + } + + _deactivate_call_disconnect(ctx); +} + +/*****************************************************************************/ + +void +nm_modem_deactivate(NMModem *self, NMDevice *device) +{ + /* First cleanup */ + NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, device, TRUE); + /* Then disconnect without waiting */ + NM_MODEM_GET_CLASS(self)->disconnect(self, FALSE, NULL, NULL, NULL); +} + +/*****************************************************************************/ + +void +nm_modem_device_state_changed(NMModem *self, NMDeviceState new_state, NMDeviceState old_state) +{ + gboolean was_connected = FALSE, warn = TRUE; + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + if (old_state >= NM_DEVICE_STATE_PREPARE && old_state <= NM_DEVICE_STATE_DEACTIVATING) + was_connected = TRUE; + + priv = NM_MODEM_GET_PRIVATE(self); + + /* Make sure we don't leave the serial device open */ + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + case NM_DEVICE_STATE_UNAVAILABLE: + case NM_DEVICE_STATE_FAILED: + case NM_DEVICE_STATE_DISCONNECTED: + if (priv->act_request) { + cancel_get_secrets(self); + g_object_unref(priv->act_request); + priv->act_request = NULL; + } + + if (was_connected) { + /* Don't bother warning on FAILED since the modem is already gone */ + if (new_state == NM_DEVICE_STATE_FAILED || new_state == NM_DEVICE_STATE_DISCONNECTED) + warn = FALSE; + /* First cleanup */ + NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, NULL, TRUE); + NM_MODEM_GET_CLASS(self)->disconnect(self, warn, NULL, NULL, NULL); + } + break; + default: + break; + } +} + +/*****************************************************************************/ + +const char * +nm_modem_get_uid(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->uid; +} + +const char * +nm_modem_get_path(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->path; +} + +const char * +nm_modem_get_driver(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->driver; +} + +const char * +nm_modem_get_control_port(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), NULL); + + return NM_MODEM_GET_PRIVATE(self)->control_port; +} + +int +nm_modem_get_ip_ifindex(NMModem *self) +{ + NMModemPrivate *priv; + + g_return_val_if_fail(NM_IS_MODEM(self), 0); + + priv = NM_MODEM_GET_PRIVATE(self); + + /* internally we track an unset ip_ifindex as -1. + * For the caller of nm_modem_get_ip_ifindex(), this + * shall be zero too. */ + return priv->ip_ifindex != -1 ? priv->ip_ifindex : 0; +} + +static void +_set_ip_ifindex(NMModem *self, int ifindex, const char *ifname) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + nm_assert(ifindex >= -1); + nm_assert((ifindex > 0) == !!ifname); + + if (!nm_streq0(priv->ip_iface, ifname)) { + g_free(priv->ip_iface); + priv->ip_iface = g_strdup(ifname); + } + + if (priv->ip_ifindex != ifindex) { + priv->ip_ifindex = ifindex; + _notify(self, PROP_IP_IFINDEX); + } +} + +gboolean +nm_modem_set_data_port(NMModem * self, + NMPlatform * platform, + const char * data_port, + NMModemIPMethod ip4_method, + NMModemIPMethod ip6_method, + guint timeout, + GError ** error) +{ + NMModemPrivate *priv; + gboolean is_ppp; + int ifindex = -1; + + g_return_val_if_fail(NM_IS_MODEM(self), FALSE); + g_return_val_if_fail(NM_IS_PLATFORM(platform), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_MODEM_GET_PRIVATE(self); + + if (priv->ppp_manager || priv->data_port || priv->ip_ifindex != -1) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot set data port in activated state"); + /* this really shouldn't happen. Assert. */ + g_return_val_if_reached(FALSE); + } + + if (!data_port) { + g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "missing data port"); + return FALSE; + } + + is_ppp = (ip4_method == NM_MODEM_IP_METHOD_PPP) || (ip6_method == NM_MODEM_IP_METHOD_PPP); + if (is_ppp) { + if (!NM_IN_SET(ip4_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP) + || !NM_IN_SET(ip6_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP)) { + g_set_error_literal(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "conflicting ip methods"); + return FALSE; + } + } else if (!NM_IN_SET(ip4_method, + NM_MODEM_IP_METHOD_UNKNOWN, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_AUTO) + || !NM_IN_SET(ip6_method, + NM_MODEM_IP_METHOD_UNKNOWN, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_AUTO) + || (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN + && ip6_method == NM_MODEM_IP_METHOD_UNKNOWN)) { + g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "invalid ip methods"); + return FALSE; + } + + if (!is_ppp) { + ifindex = nm_platform_if_nametoindex(platform, data_port); + if (ifindex <= 0) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot find network interface %s", + data_port); + return FALSE; + } + if (!nm_platform_process_events_ensure_link(platform, ifindex, data_port)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "cannot find network interface %s in platform cache", + data_port); + return FALSE; + } + } + + priv->mm_ip_timeout = timeout; + priv->ip4_method = ip4_method; + priv->ip6_method = ip6_method; + if (is_ppp) { + priv->data_port = g_strdup(data_port); + _set_ip_ifindex(self, -1, NULL); + } else { + priv->data_port = NULL; + _set_ip_ifindex(self, ifindex, data_port); + } + return TRUE; +} + +gboolean +nm_modem_owns_port(NMModem *self, const char *iface) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + g_return_val_if_fail(iface != NULL, FALSE); + + if (NM_MODEM_GET_CLASS(self)->owns_port) + return NM_MODEM_GET_CLASS(self)->owns_port(self, iface); + + return NM_IN_STRSET(iface, priv->ip_iface, priv->data_port, priv->control_port); +} + +gboolean +nm_modem_get_iid(NMModem *self, NMUtilsIPv6IfaceId *out_iid) +{ + g_return_val_if_fail(NM_IS_MODEM(self), FALSE); + + *out_iid = NM_MODEM_GET_PRIVATE(self)->iid; + return TRUE; +} + +/*****************************************************************************/ + +void +nm_modem_get_route_parameters(NMModem *self, + guint32 *out_ip4_route_table, + guint32 *out_ip4_route_metric, + guint32 *out_ip6_route_table, + guint32 *out_ip6_route_metric) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + NM_SET_OUT(out_ip4_route_table, priv->ip4_route_table); + NM_SET_OUT(out_ip4_route_metric, priv->ip4_route_metric); + NM_SET_OUT(out_ip6_route_table, priv->ip6_route_table); + NM_SET_OUT(out_ip6_route_metric, priv->ip6_route_metric); +} + +void +nm_modem_set_route_parameters(NMModem *self, + guint32 ip4_route_table, + guint32 ip4_route_metric, + guint32 ip6_route_table, + guint32 ip6_route_metric) +{ + NMModemPrivate *priv; + + g_return_if_fail(NM_IS_MODEM(self)); + + priv = NM_MODEM_GET_PRIVATE(self); + if (priv->ip4_route_table != ip4_route_table || priv->ip4_route_metric != ip4_route_metric + || priv->ip6_route_table != ip6_route_table || priv->ip6_route_metric != ip6_route_metric) { + priv->ip4_route_table = ip4_route_table; + priv->ip4_route_metric = ip4_route_metric; + priv->ip6_route_table = ip6_route_table; + priv->ip6_route_metric = ip6_route_metric; + + _LOGT("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u", + priv->ip4_route_table, + priv->ip4_route_metric, + priv->ip6_route_table, + priv->ip6_route_metric); + } + + if (priv->ppp_manager) { + nm_ppp_manager_set_route_parameters(priv->ppp_manager, + priv->ip4_route_table, + priv->ip4_route_metric, + priv->ip6_route_table, + priv->ip6_route_metric); + } +} + +void +nm_modem_set_route_parameters_from_device(NMModem *self, NMDevice *device) +{ + g_return_if_fail(NM_IS_DEVICE(device)); + + nm_modem_set_route_parameters(self, + nm_device_get_route_table(device, AF_INET), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_table(device, AF_INET6), + nm_device_get_route_metric(device, AF_INET6)); +} + +/*****************************************************************************/ + +void +nm_modem_get_capabilities(NMModem * self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps) +{ + g_return_if_fail(NM_IS_MODEM(self)); + + NM_MODEM_GET_CLASS(self)->get_capabilities(self, modem_caps, current_caps); +} + +/*****************************************************************************/ + +void +_nm_modem_set_operator_code(NMModem *self, const char *operator_code) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (g_strcmp0(priv->operator_code, operator_code) != 0) { + g_free(priv->operator_code); + priv->operator_code = g_strdup(operator_code); + _notify(self, PROP_OPERATOR_CODE); + } +} + +void +_nm_modem_set_apn(NMModem *self, const char *apn) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + if (g_strcmp0(priv->apn, apn) != 0) { + g_free(priv->apn); + priv->apn = g_strdup(apn); + _notify(self, PROP_APN); + } +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMModem * self = NM_MODEM(object); + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string(value, priv->path); + break; + case PROP_DRIVER: + g_value_set_string(value, priv->driver); + break; + case PROP_CONTROL_PORT: + g_value_set_string(value, priv->control_port); + break; + case PROP_IP_IFINDEX: + g_value_set_int(value, nm_modem_get_ip_ifindex(self)); + break; + case PROP_UID: + g_value_set_string(value, priv->uid); + break; + case PROP_STATE: + g_value_set_int(value, priv->state); + break; + case PROP_DEVICE_ID: + g_value_set_string(value, priv->device_id); + break; + case PROP_SIM_ID: + g_value_set_string(value, priv->sim_id); + break; + case PROP_IP_TYPES: + g_value_set_uint(value, priv->ip_types); + break; + case PROP_SIM_OPERATOR_ID: + g_value_set_string(value, priv->sim_operator_id); + break; + case PROP_OPERATOR_CODE: + g_value_set_string(value, priv->operator_code); + break; + case PROP_APN: + g_value_set_string(value, priv->apn); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object); + const char * s; + + switch (prop_id) { + case PROP_PATH: + /* construct-only */ + priv->path = g_value_dup_string(value); + g_return_if_fail(priv->path); + break; + case PROP_DRIVER: + /* construct-only */ + priv->driver = g_value_dup_string(value); + break; + case PROP_CONTROL_PORT: + /* construct-only */ + priv->control_port = g_value_dup_string(value); + break; + case PROP_UID: + /* construct-only */ + priv->uid = g_value_dup_string(value); + break; + case PROP_STATE: + /* construct-only */ + priv->state = g_value_get_int(value); + break; + case PROP_DEVICE_ID: + /* construct-only */ + priv->device_id = g_value_dup_string(value); + break; + case PROP_SIM_ID: + g_free(priv->sim_id); + priv->sim_id = g_value_dup_string(value); + break; + case PROP_IP_TYPES: + priv->ip_types = g_value_get_uint(value); + break; + case PROP_SIM_OPERATOR_ID: + nm_clear_g_free(&priv->sim_operator_id); + s = g_value_get_string(value); + if (s && s[0]) + priv->sim_operator_id = g_strdup(s); + break; + case PROP_OPERATOR_CODE: + /* construct-only */ + priv->operator_code = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_modem_init(NMModem *self) +{ + NMModemPrivate *priv; + + self->_priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_MODEM, NMModemPrivate); + priv = self->_priv; + + priv->ip_ifindex = -1; + priv->ip4_route_table = RT_TABLE_MAIN; + priv->ip4_route_metric = 700; + priv->ip6_route_table = RT_TABLE_MAIN; + priv->ip6_route_metric = 700; +} + +static void +constructed(GObject *object) +{ + NMModemPrivate *priv; + + G_OBJECT_CLASS(nm_modem_parent_class)->constructed(object); + + priv = NM_MODEM_GET_PRIVATE(NM_MODEM(object)); + + g_return_if_fail(priv->control_port); +} + +/*****************************************************************************/ + +static void +dispose(GObject *object) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object); + + g_clear_object(&priv->act_request); + + G_OBJECT_CLASS(nm_modem_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object); + + g_free(priv->uid); + g_free(priv->path); + g_free(priv->driver); + g_free(priv->control_port); + g_free(priv->data_port); + g_free(priv->ip_iface); + g_free(priv->device_id); + g_free(priv->sim_id); + g_free(priv->sim_operator_id); + g_free(priv->operator_code); + g_free(priv->apn); + + G_OBJECT_CLASS(nm_modem_parent_class)->finalize(object); +} + +static void +nm_modem_class_init(NMModemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMModemPrivate)); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + klass->modem_act_stage1_prepare = modem_act_stage1_prepare; + klass->stage3_ip6_config_request = stage3_ip6_config_request; + klass->deactivate_cleanup = deactivate_cleanup; + + obj_properties[PROP_UID] = + g_param_spec_string(NM_MODEM_UID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PATH] = + g_param_spec_string(NM_MODEM_PATH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DRIVER] = + g_param_spec_string(NM_MODEM_DRIVER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONTROL_PORT] = + g_param_spec_string(NM_MODEM_CONTROL_PORT, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IP_IFINDEX] = g_param_spec_int(NM_MODEM_IP_IFINDEX, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STATE] = + g_param_spec_int(NM_MODEM_STATE, + "", + "", + NM_MODEM_STATE_UNKNOWN, + _NM_MODEM_STATE_LAST, + NM_MODEM_STATE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEVICE_ID] = + g_param_spec_string(NM_MODEM_DEVICE_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SIM_ID] = + g_param_spec_string(NM_MODEM_SIM_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_IP_TYPES] = + g_param_spec_uint(NM_MODEM_IP_TYPES, + "IP Types", + "Supported IP types", + 0, + G_MAXUINT32, + NM_MODEM_IP_TYPE_IPV4, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SIM_OPERATOR_ID] = + g_param_spec_string(NM_MODEM_SIM_OPERATOR_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_OPERATOR_CODE] = + g_param_spec_string(NM_MODEM_OPERATOR_CODE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_APN] = + g_param_spec_string(NM_MODEM_APN, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[PPP_STATS] = g_signal_new(NM_MODEM_PPP_STATS, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_UINT /*guint32 in_bytes*/, + G_TYPE_UINT /*guint32 out_bytes*/); + + signals[PPP_FAILED] = g_signal_new(NM_MODEM_PPP_FAILED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[IP4_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP4_CONFIG_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_OBJECT, + G_TYPE_POINTER); + + /** + * NMModem::ip6-config-result: + * @modem: the #NMModem on which the signal is emitted + * @config: the #NMIP6Config to apply to the modem's data port + * @do_slaac: %TRUE if IPv6 SLAAC should be started + * @error: a #GError if any error occurred during IP configuration + * + * This signal is emitted when IPv6 configuration has completed or failed. + * If @error is set the configuration failed. If @config is set, then + * the details should be applied to the data port before any further + * configuration (like SLAAC) is done. @do_slaac indicates whether SLAAC + * should be started after applying @config to the data port. + */ + signals[IP6_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP6_CONFIG_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_OBJECT, + G_TYPE_BOOLEAN, + G_TYPE_POINTER); + + signals[PREPARE_RESULT] = g_signal_new(NM_MODEM_PREPARE_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_BOOLEAN, + G_TYPE_UINT); + + signals[AUTH_REQUESTED] = g_signal_new(NM_MODEM_AUTH_REQUESTED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[AUTH_RESULT] = g_signal_new(NM_MODEM_AUTH_RESULT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[REMOVED] = g_signal_new(NM_MODEM_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[STATE_CHANGED] = g_signal_new(NM_MODEM_STATE_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_INT); +} diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h new file mode 100644 index 00000000..87162cfc --- /dev/null +++ b/src/core/devices/wwan/nm-modem.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2011 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_MODEM_H__ +#define __NETWORKMANAGER_MODEM_H__ + +#include "ppp/nm-ppp-manager.h" +#include "devices/nm-device.h" + +#define NM_TYPE_MODEM (nm_modem_get_type()) +#define NM_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM, NMModem)) +#define NM_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM, NMModemClass)) +#define NM_IS_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM)) +#define NM_IS_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM)) +#define NM_MODEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_MODEM, NMModemClass)) + +/* Properties */ +#define NM_MODEM_UID "uid" +#define NM_MODEM_PATH "path" +#define NM_MODEM_DRIVER "driver" +#define NM_MODEM_CONTROL_PORT "control-port" +#define NM_MODEM_IP_IFINDEX "ip-ifindex" +#define NM_MODEM_STATE "state" +#define NM_MODEM_DEVICE_ID "device-id" +#define NM_MODEM_SIM_ID "sim-id" +#define NM_MODEM_IP_TYPES "ip-types" /* Supported IP types */ +#define NM_MODEM_SIM_OPERATOR_ID "sim-operator-id" +#define NM_MODEM_OPERATOR_CODE "operator-code" +#define NM_MODEM_APN "apn" + +/* Signals */ +#define NM_MODEM_PPP_STATS "ppp-stats" +#define NM_MODEM_PPP_FAILED "ppp-failed" +#define NM_MODEM_PREPARE_RESULT "prepare-result" +#define NM_MODEM_IP4_CONFIG_RESULT "ip4-config-result" +#define NM_MODEM_IP6_CONFIG_RESULT "ip6-config-result" +#define NM_MODEM_AUTH_REQUESTED "auth-requested" +#define NM_MODEM_AUTH_RESULT "auth-result" +#define NM_MODEM_REMOVED "removed" +#define NM_MODEM_STATE_CHANGED "state-changed" + +typedef enum { + NM_MODEM_IP_METHOD_UNKNOWN = 0, + NM_MODEM_IP_METHOD_PPP, + NM_MODEM_IP_METHOD_STATIC, + NM_MODEM_IP_METHOD_AUTO, /* DHCP and/or SLAAC */ +} NMModemIPMethod; + +/** + * NMModemIPType: + * @NM_MODEM_IP_TYPE_UNKNOWN: unknown or no IP support + * @NM_MODEM_IP_TYPE_IPV4: IPv4-only bearers are supported + * @NM_MODEM_IP_TYPE_IPV6: IPv6-only bearers are supported + * @NM_MODEM_IP_TYPE_IPV4V6: dual-stack IPv4 + IPv6 bearers are supported + * + * Indicates what IP protocols the modem supports for an IP bearer. Any + * combination of flags is possible. For example, (%NM_MODEM_IP_TYPE_IPV4 | + * %NM_MODEM_IP_TYPE_IPV6) indicates that the modem supports IPv4 and IPv6 + * but not simultaneously on the same bearer. + */ +typedef enum { + NM_MODEM_IP_TYPE_UNKNOWN = 0x0, + NM_MODEM_IP_TYPE_IPV4 = 0x1, + NM_MODEM_IP_TYPE_IPV6 = 0x2, + NM_MODEM_IP_TYPE_IPV4V6 = 0x4 +} NMModemIPType; + +typedef enum { /*< underscore_name=nm_modem_state >*/ + NM_MODEM_STATE_UNKNOWN = 0, + NM_MODEM_STATE_FAILED = 1, + NM_MODEM_STATE_INITIALIZING = 2, + NM_MODEM_STATE_LOCKED = 3, + NM_MODEM_STATE_DISABLED = 4, + NM_MODEM_STATE_DISABLING = 5, + NM_MODEM_STATE_ENABLING = 6, + NM_MODEM_STATE_ENABLED = 7, + NM_MODEM_STATE_SEARCHING = 8, + NM_MODEM_STATE_REGISTERED = 9, + NM_MODEM_STATE_DISCONNECTING = 10, + NM_MODEM_STATE_CONNECTING = 11, + NM_MODEM_STATE_CONNECTED = 12, + + _NM_MODEM_STATE_LAST0, + _NM_MODEM_STATE_LAST = _NM_MODEM_STATE_LAST0 - 1, +} NMModemState; + +struct _NMModemPrivate; + +struct _NMModem { + GObject parent; + struct _NMModemPrivate *_priv; +}; + +typedef struct _NMModem NMModem; + +typedef void (*_NMModemDisconnectCallback)(NMModem *modem, GError *error, gpointer user_data); + +typedef struct { + GObjectClass parent; + + void (*get_capabilities)(NMModem * self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps); + + gboolean (*get_user_pass)(NMModem * modem, + NMConnection *connection, + const char ** user, + const char ** pass); + + gboolean (*check_connection_compatible_with_modem)(NMModem * modem, + NMConnection *connection, + GError ** error); + + gboolean (*complete_connection)(NMModem * modem, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error); + + NMActStageReturn (*modem_act_stage1_prepare)(NMModem * modem, + NMConnection * connection, + NMDeviceStateReason *out_failure_reason); + + NMActStageReturn (*static_stage3_ip4_config_start)(NMModem * self, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason); + + /* Request the IP6 config; when the config returns the modem + * subclass should emit the ip6_config_result signal. + */ + NMActStageReturn (*stage3_ip6_config_request)(NMModem * self, + NMDeviceStateReason *out_failure_reason); + + void (*set_mm_enabled)(NMModem *self, gboolean enabled); + + void (*disconnect)(NMModem * self, + gboolean warn, + GCancellable * cancellable, + _NMModemDisconnectCallback callback, + gpointer user_data); + + void (*deactivate_cleanup)(NMModem *self, NMDevice *device, gboolean stop_ppp_manager); + + gboolean (*owns_port)(NMModem *self, const char *iface); +} NMModemClass; + +GType nm_modem_get_type(void); + +gboolean nm_modem_is_claimed(NMModem *modem); +NMModem *nm_modem_claim(NMModem *modem); +void nm_modem_unclaim(NMModem *modem); + +const char *nm_modem_get_path(NMModem *modem); +const char *nm_modem_get_uid(NMModem *modem); +const char *nm_modem_get_control_port(NMModem *modem); +int nm_modem_get_ip_ifindex(NMModem *modem); +const char *nm_modem_get_driver(NMModem *modem); +const char *nm_modem_get_device_id(NMModem *modem); +const char *nm_modem_get_sim_id(NMModem *modem); +const char *nm_modem_get_sim_operator_id(NMModem *modem); +gboolean nm_modem_get_iid(NMModem *modem, NMUtilsIPv6IfaceId *out_iid); +const char *nm_modem_get_operator_code(NMModem *modem); +const char *nm_modem_get_apn(NMModem *modem); + +gboolean nm_modem_set_data_port(NMModem * self, + NMPlatform * platform, + const char * data_port, + NMModemIPMethod ip4_method, + NMModemIPMethod ip6_method, + guint timeout, + GError ** error); + +gboolean nm_modem_owns_port(NMModem *modem, const char *iface); + +void nm_modem_get_capabilities(NMModem * self, + NMDeviceModemCapabilities *modem_caps, + NMDeviceModemCapabilities *current_caps); + +gboolean +nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GError **error); + +gboolean nm_modem_complete_connection(NMModem * self, + const char * iface, + NMConnection * connection, + NMConnection *const *existing_connections, + GError ** error); + +void nm_modem_get_route_parameters(NMModem *self, + guint32 *out_ip4_route_table, + guint32 *out_ip4_route_metric, + guint32 *out_ip6_route_table, + guint32 *out_ip6_route_metric); + +void nm_modem_set_route_parameters(NMModem *self, + guint32 ip4_route_table, + guint32 ip4_route_metric, + guint32 ip6_route_table, + guint32 ip6_route_metric); + +void nm_modem_set_route_parameters_from_device(NMModem *modem, NMDevice *device); + +NMActStageReturn nm_modem_act_stage1_prepare(NMModem * modem, + NMActRequest * req, + NMDeviceStateReason *out_failure_reason); + +void nm_modem_act_stage2_config(NMModem *modem); + +NMActStageReturn nm_modem_stage3_ip4_config_start(NMModem * modem, + NMDevice * device, + NMDeviceClass * device_class, + NMDeviceStateReason *out_failure_reason); + +NMActStageReturn nm_modem_stage3_ip6_config_start(NMModem * modem, + NMDevice * device, + NMDeviceStateReason *out_failure_reason); + +void nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config); + +void nm_modem_get_secrets(NMModem * modem, + const char *setting_name, + gboolean request_new, + const char *hint); + +void nm_modem_deactivate(NMModem *modem, NMDevice *device); + +typedef void (*NMModemDeactivateCallback)(NMModem *self, GError *error, gpointer user_data); + +void nm_modem_deactivate_async(NMModem * self, + NMDevice * device, + GCancellable * cancellable, + NMModemDeactivateCallback callback, + gpointer user_data); + +void +nm_modem_device_state_changed(NMModem *modem, NMDeviceState new_state, NMDeviceState old_state); + +void nm_modem_set_mm_enabled(NMModem *self, gboolean enabled); + +NMModemState nm_modem_get_state(NMModem *self); +void nm_modem_set_state(NMModem *self, NMModemState new_state, const char *reason); +void nm_modem_set_prev_state(NMModem *self, const char *reason); +const char * nm_modem_state_to_string(NMModemState state); + +NMModemIPType nm_modem_get_supported_ip_types(NMModem *self); + +/* For the modem-manager only */ +void nm_modem_emit_removed(NMModem *self); + +void nm_modem_emit_prepare_result(NMModem *self, gboolean success, NMDeviceStateReason reason); + +void nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason); + +GArray *nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error); + +/* For subclasses */ +void nm_modem_emit_ip6_config_result(NMModem *self, NMIP6Config *config, GError *error); + +const char *nm_modem_ip_type_to_string(NMModemIPType ip_type); + +guint32 +nm_modem_get_configured_mtu(NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force); + +void _nm_modem_set_operator_code(NMModem *self, const char *operator_code); +void _nm_modem_set_apn(NMModem *self, const char *apn); + +#endif /* __NETWORKMANAGER_MODEM_H__ */ diff --git a/src/core/devices/wwan/nm-service-providers.c b/src/core/devices/wwan/nm-service-providers.c new file mode 100644 index 00000000..89add6c5 --- /dev/null +++ b/src/core/devices/wwan/nm-service-providers.c @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2009 Novell, Inc. + * Author: Tambet Ingo (tambet@gmail.com). + * Copyright (C) 2009 - 2019 Red Hat, Inc. + * Copyright (C) 2012 Lanedo GmbH + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-service-providers.h" + +typedef enum { + PARSER_TOPLEVEL = 0, + PARSER_COUNTRY, + PARSER_PROVIDER, + PARSER_METHOD_GSM, + PARSER_METHOD_GSM_APN, + PARSER_METHOD_CDMA, + PARSER_DONE, + PARSER_ERROR +} ParseContextState; + +typedef struct { + char * mccmnc; + NMServiceProvidersGsmApnCallback callback; + gpointer user_data; + GCancellable * cancellable; + GMarkupParseContext * ctx; + char buffer[4096]; + + char * text_buffer; + ParseContextState state; + + gboolean mccmnc_matched; + gboolean found_internet_apn; + char * apn; + char * username; + char * password; + char * gateway; + char * auth_method; + GSList * dns; +} ParseContext; + +/*****************************************************************************/ + +static void +parser_toplevel_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + int i; + + if (strcmp(name, "serviceproviders") == 0) { + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "format") == 0) { + if (strcmp(attribute_values[i], "2.0")) { + g_warning("%s: mobile broadband provider database format '%s'" + " not supported.", + __func__, + attribute_values[i]); + parse_context->state = PARSER_ERROR; + break; + } + } + } + } else if (strcmp(name, "country") == 0) { + parse_context->state = PARSER_COUNTRY; + } +} + +static void +parser_country_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + if (strcmp(name, "provider") == 0) + parse_context->state = PARSER_PROVIDER; +} + +static void +parser_provider_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + parse_context->mccmnc_matched = FALSE; + if (strcmp(name, "gsm") == 0) + parse_context->state = PARSER_METHOD_GSM; + else if (strcmp(name, "cdma") == 0) + parse_context->state = PARSER_METHOD_CDMA; +} + +static void +parser_gsm_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + int i; + + if (strcmp(name, "network-id") == 0) { + const char *mcc = NULL, *mnc = NULL; + + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "mcc") == 0) + mcc = attribute_values[i]; + else if (strcmp(attribute_names[i], "mnc") == 0) + mnc = attribute_values[i]; + if (mcc && strlen(mcc) && mnc && strlen(mnc)) { + char *mccmnc = g_strdup_printf("%s%s", mcc, mnc); + + if (strcmp(mccmnc, parse_context->mccmnc) == 0) + parse_context->mccmnc_matched = TRUE; + g_free(mccmnc); + break; + } + } + } else if (strcmp(name, "apn") == 0) { + parse_context->found_internet_apn = FALSE; + nm_clear_g_free(&parse_context->apn); + nm_clear_g_free(&parse_context->username); + nm_clear_g_free(&parse_context->password); + nm_clear_g_free(&parse_context->gateway); + nm_clear_g_free(&parse_context->auth_method); + g_slist_free_full(parse_context->dns, g_free); + parse_context->dns = NULL; + + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "value") == 0) { + parse_context->state = PARSER_METHOD_GSM_APN; + parse_context->apn = g_strstrip(g_strdup(attribute_values[i])); + break; + } + } + } +} + +static void +parser_gsm_apn_start(ParseContext *parse_context, + const char * name, + const char ** attribute_names, + const char ** attribute_values) +{ + int i; + + if (strcmp(name, "usage") == 0) { + for (i = 0; attribute_names && attribute_names[i]; i++) { + if ((strcmp(attribute_names[i], "type") == 0) + && (strcmp(attribute_values[i], "internet") == 0)) { + parse_context->found_internet_apn = TRUE; + break; + } + } + } else if (strcmp(name, "authentication") == 0) { + for (i = 0; attribute_names && attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "method") == 0) { + nm_clear_g_free(&parse_context->auth_method); + parse_context->auth_method = g_strstrip(g_strdup(attribute_values[i])); + break; + } + } + } +} + +static void +parser_start_element(GMarkupParseContext *context, + const char * element_name, + const char ** attribute_names, + const char ** attribute_values, + gpointer user_data, + GError ** error) +{ + ParseContext *parse_context = user_data; + + nm_clear_g_free(&parse_context->text_buffer); + + switch (parse_context->state) { + case PARSER_TOPLEVEL: + parser_toplevel_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_COUNTRY: + parser_country_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_PROVIDER: + parser_provider_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_METHOD_GSM: + parser_gsm_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_METHOD_GSM_APN: + parser_gsm_apn_start(parse_context, element_name, attribute_names, attribute_values); + break; + case PARSER_METHOD_CDMA: + break; + case PARSER_ERROR: + break; + case PARSER_DONE: + break; + } +} + +static void +parser_country_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "country") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_TOPLEVEL; + } +} + +static void +parser_provider_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "provider") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_COUNTRY; + } +} + +static void +parser_gsm_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "gsm") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_PROVIDER; + } +} + +static void +parser_gsm_apn_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "username") == 0) { + nm_clear_g_free(&parse_context->username); + parse_context->username = g_steal_pointer(&parse_context->text_buffer); + } else if (strcmp(name, "password") == 0) { + nm_clear_g_free(&parse_context->password); + parse_context->password = g_steal_pointer(&parse_context->text_buffer); + } else if (strcmp(name, "dns") == 0) { + parse_context->dns = + g_slist_prepend(parse_context->dns, g_steal_pointer(&parse_context->text_buffer)); + } else if (strcmp(name, "gateway") == 0) { + nm_clear_g_free(&parse_context->gateway); + parse_context->gateway = g_steal_pointer(&parse_context->text_buffer); + } else if (strcmp(name, "apn") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + + if (parse_context->mccmnc_matched && parse_context->found_internet_apn) + parse_context->state = PARSER_DONE; + else + parse_context->state = PARSER_METHOD_GSM; + } +} + +static void +parser_cdma_end(ParseContext *parse_context, const char *name) +{ + if (strcmp(name, "cdma") == 0) { + nm_clear_g_free(&parse_context->text_buffer); + parse_context->state = PARSER_PROVIDER; + } +} + +static void +parser_end_element(GMarkupParseContext *context, + const char * element_name, + gpointer user_data, + GError ** error) +{ + ParseContext *parse_context = user_data; + + switch (parse_context->state) { + case PARSER_TOPLEVEL: + break; + case PARSER_COUNTRY: + parser_country_end(parse_context, element_name); + break; + case PARSER_PROVIDER: + parser_provider_end(parse_context, element_name); + break; + case PARSER_METHOD_GSM: + parser_gsm_end(parse_context, element_name); + break; + case PARSER_METHOD_GSM_APN: + parser_gsm_apn_end(parse_context, element_name); + break; + case PARSER_METHOD_CDMA: + parser_cdma_end(parse_context, element_name); + break; + case PARSER_ERROR: + break; + case PARSER_DONE: + break; + } +} + +static void +parser_text(GMarkupParseContext *context, + const char * text, + gsize text_len, + gpointer user_data, + GError ** error) +{ + ParseContext *parse_context = user_data; + + g_free(parse_context->text_buffer); + parse_context->text_buffer = g_strdup(text); +} + +static const GMarkupParser parser = { + .start_element = parser_start_element, + .end_element = parser_end_element, + .text = parser_text, + .passthrough = NULL, + .error = NULL, +}; + +/*****************************************************************************/ + +static void +finish_parse_context(ParseContext *parse_context, GError *error) +{ + if (parse_context->callback) { + if (error) { + parse_context + ->callback(NULL, NULL, NULL, NULL, NULL, NULL, error, parse_context->user_data); + } else { + parse_context->callback(parse_context->apn, + parse_context->username, + parse_context->password, + parse_context->gateway, + parse_context->auth_method, + parse_context->dns, + error, + parse_context->user_data); + } + } + + g_free(parse_context->mccmnc); + g_markup_parse_context_free(parse_context->ctx); + + g_free(parse_context->text_buffer); + g_free(parse_context->apn); + g_free(parse_context->username); + g_free(parse_context->password); + g_free(parse_context->gateway); + g_free(parse_context->auth_method); + g_slist_free_full(parse_context->dns, g_free); + + g_slice_free(ParseContext, parse_context); +} + +static void read_next_chunk(GInputStream *stream, ParseContext *parse_context); + +static void +stream_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM(source_object); + ParseContext *parse_context = user_data; + gssize len; + GError * error = NULL; + + len = g_input_stream_read_finish(stream, res, &error); + if (len == -1) { + g_prefix_error(&error, "Error reading service provider database: "); + finish_parse_context(parse_context, error); + g_clear_error(&error); + return; + } + + if (len == 0) { + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Operator ID '%s' not found in service provider database", + parse_context->mccmnc); + finish_parse_context(parse_context, error); + g_clear_error(&error); + return; + } + + if (!g_markup_parse_context_parse(parse_context->ctx, parse_context->buffer, len, &error)) { + g_prefix_error(&error, "Error parsing service provider database: "); + finish_parse_context(parse_context, error); + g_clear_error(&error); + return; + } + + if (parse_context->state == PARSER_DONE) { + finish_parse_context(parse_context, NULL); + return; + } + + read_next_chunk(stream, parse_context); +} + +static void +read_next_chunk(GInputStream *stream, ParseContext *parse_context) +{ + g_input_stream_read_async(stream, + parse_context->buffer, + sizeof(parse_context->buffer), + G_PRIORITY_DEFAULT, + parse_context->cancellable, + stream_read_cb, + parse_context); +} + +static void +file_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GFile * file = G_FILE(source_object); + ParseContext * parse_context = user_data; + GFileInputStream *stream; + gs_free_error GError *error = NULL; + + stream = g_file_read_finish(file, res, &error); + if (!stream) { + g_prefix_error(&error, "Error opening service provider database: "); + finish_parse_context(parse_context, error); + return; + } + + read_next_chunk(G_INPUT_STREAM(stream), parse_context); + + g_object_unref(stream); +} + +/*****************************************************************************/ + +void +nm_service_providers_find_gsm_apn(const char * service_providers, + const char * mccmnc, + GCancellable * cancellable, + NMServiceProvidersGsmApnCallback callback, + gpointer user_data) +{ + GFile * file; + ParseContext *parse_context; + + parse_context = g_slice_new0(ParseContext); + parse_context->mccmnc = g_strdup(mccmnc); + parse_context->cancellable = cancellable; + parse_context->callback = callback; + parse_context->user_data = user_data; + parse_context->ctx = g_markup_parse_context_new(&parser, 0, parse_context, NULL); + + file = g_file_new_for_path(service_providers); + + g_file_read_async(file, G_PRIORITY_DEFAULT, cancellable, file_read_cb, parse_context); + + g_object_unref(file); +} diff --git a/src/core/devices/wwan/nm-service-providers.h b/src/core/devices/wwan/nm-service-providers.h new file mode 100644 index 00000000..959f660a --- /dev/null +++ b/src/core/devices/wwan/nm-service-providers.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SERVICE_PROVIDERS_H__ +#define __NETWORKMANAGER_SERVICE_PROVIDERS_H__ + +typedef void (*NMServiceProvidersGsmApnCallback)(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data); + +void nm_service_providers_find_gsm_apn(const char * service_providers, + const char * mccmnc, + GCancellable * cancellable, + NMServiceProvidersGsmApnCallback callback, + gpointer user_data); + +#endif /* __NETWORKMANAGER_SERVICE_PROVIDERS_H__ */ diff --git a/src/core/devices/wwan/nm-wwan-factory.c b/src/core/devices/wwan/nm-wwan-factory.c new file mode 100644 index 00000000..5d2ce2b3 --- /dev/null +++ b/src/core/devices/wwan/nm-wwan-factory.c @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include <gmodule.h> + +#include "devices/nm-device-factory.h" +#include "nm-setting-gsm.h" +#include "nm-setting-cdma.h" +#include "nm-modem-manager.h" +#include "nm-device-modem.h" +#include "platform/nm-platform.h" + +/*****************************************************************************/ + +#define NM_TYPE_WWAN_FACTORY (nm_wwan_factory_get_type()) +#define NM_WWAN_FACTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactory)) +#define NM_WWAN_FACTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WWAN_FACTORY, NMWwanFactoryClass)) +#define NM_IS_WWAN_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WWAN_FACTORY)) +#define NM_IS_WWAN_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WWAN_FACTORY)) +#define NM_WWAN_FACTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactoryClass)) + +typedef struct { + NMModemManager *mm; +} NMWwanFactoryPrivate; + +typedef struct { + NMDeviceFactory parent; + NMWwanFactoryPrivate _priv; +} NMWwanFactory; + +typedef struct { + NMDeviceFactoryClass parent; +} NMWwanFactoryClass; + +static GType nm_wwan_factory_get_type(void); + +G_DEFINE_TYPE(NMWwanFactory, nm_wwan_factory, NM_TYPE_DEVICE_FACTORY) + +#define NM_WWAN_FACTORY_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMWwanFactory, NM_IS_WWAN_FACTORY) + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES( + NM_LINK_TYPE_WWAN_NET) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_CDMA_SETTING_NAME)) + +G_MODULE_EXPORT NMDeviceFactory * + nm_device_factory_create(GError **error) +{ + return g_object_new(NM_TYPE_WWAN_FACTORY, NULL); +} + +/*****************************************************************************/ + +static void +modem_added_cb(NMModemManager *manager, NMModem *modem, gpointer user_data) +{ + NMWwanFactory * self = NM_WWAN_FACTORY(user_data); + gs_unref_object NMDevice *device = NULL; + const char * driver; + + if (nm_modem_is_claimed(modem)) + return; + + driver = nm_modem_get_driver(modem); + + /* If it was a Bluetooth modem and no bluetooth device claimed it, ignore + * it. The rfcomm port (and thus the modem) gets created automatically + * by the Bluetooth code during the connection process. + */ + if (driver && strstr(driver, "bluetooth")) { + nm_log_dbg( + LOGD_MB, + "WWAN factory ignores bluetooth modem '%s' which should be handled by bluetooth plugin", + nm_modem_get_control_port(modem)); + return; + } + + /* Make the new modem device */ + device = nm_device_modem_new(modem); + g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, device); +} + +static NMDevice * +create_device(NMDeviceFactory * factory, + const char * iface, + const NMPlatformLink *plink, + NMConnection * connection, + gboolean * out_ignore) +{ + g_return_val_if_fail(plink, NULL); + g_return_val_if_fail(plink->type == NM_LINK_TYPE_WWAN_NET, NULL); + *out_ignore = TRUE; + return NULL; +} + +static void +start(NMDeviceFactory *factory) +{ + NMWwanFactory * self = NM_WWAN_FACTORY(factory); + NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE(self); + + priv->mm = g_object_ref(nm_modem_manager_get()); + + g_signal_connect(priv->mm, NM_MODEM_MANAGER_MODEM_ADDED, G_CALLBACK(modem_added_cb), self); +} + +/*****************************************************************************/ + +static void +nm_wwan_factory_init(NMWwanFactory *self) +{} + +static void +dispose(GObject *object) +{ + NMWwanFactory * self = NM_WWAN_FACTORY(object); + NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE(self); + + if (priv->mm) + g_signal_handlers_disconnect_by_func(priv->mm, modem_added_cb, self); + g_clear_object(&priv->mm); + + /* Chain up to the parent class */ + G_OBJECT_CLASS(nm_wwan_factory_parent_class)->dispose(object); +} + +static void +nm_wwan_factory_class_init(NMWwanFactoryClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass); + + object_class->dispose = dispose; + + factory_class->get_supported_types = get_supported_types; + factory_class->create_device = create_device; + factory_class->start = start; +} diff --git a/src/core/devices/wwan/tests/test-service-providers.c b/src/core/devices/wwan/tests/test-service-providers.c new file mode 100644 index 00000000..f95cccf8 --- /dev/null +++ b/src/core/devices/wwan/tests/test-service-providers.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-service-providers.h" + +#include "nm-test-utils-core.h" + +static void +test_positive_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_main_loop_quit(loop); + g_assert_no_error(error); + g_assert_cmpstr(apn, ==, "gprs.example.com"); + g_assert_cmpstr(username, ==, "praise"); + g_assert_cmpstr(password, ==, "santa"); + g_assert_cmpstr(gateway, ==, "192.0.2.3"); + g_assert_cmpstr(auth_method, ==, "pap"); + + g_assert_nonnull(dns); + g_assert_cmpstr(dns->data, ==, "192.0.2.2"); + dns = dns->next; + g_assert_nonnull(dns); + g_assert_cmpstr(dns->data, ==, "192.0.2.1"); + g_assert_null(dns->next); +} + +static void +test_positive(void) +{ + GMainLoop *loop = g_main_loop_new(NULL, FALSE); + + nm_service_providers_find_gsm_apn(NM_BUILD_SRCDIR + "/src/core/devices/wwan/tests/test-service-providers.xml", + "13337", + NULL, + test_positive_cb, + loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +static void +test_negative_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_main_loop_quit(loop); + g_assert_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN); +} + +static void +test_negative(void) +{ + GMainLoop *loop = g_main_loop_new(NULL, FALSE); + + nm_service_providers_find_gsm_apn(NM_BUILD_SRCDIR + "/src/core/devices/wwan/tests/test-service-providers.xml", + "78130", + NULL, + test_negative_cb, + loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +static void +test_nonexistent_cb(const char * apn, + const char * username, + const char * password, + const char * gateway, + const char * auth_method, + const GSList *dns, + GError * error, + gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_main_loop_quit(loop); + g_assert_error(error, G_IO_ERROR, G_IO_ERROR_AGAIN); +} + +static void +test_nonexistent(void) +{ + GMainLoop *loop = g_main_loop_new(NULL, FALSE); + + nm_service_providers_find_gsm_apn("nonexistent.xml", "13337", NULL, test_nonexistent_cb, loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func("/service-providers/positive", test_positive); + g_test_add_func("/service-providers/negative", test_negative); + g_test_add_func("/service-providers/nonexistent", test_nonexistent); + + return g_test_run(); +} diff --git a/src/core/devices/wwan/tests/test-service-providers.xml b/src/core/devices/wwan/tests/test-service-providers.xml new file mode 100644 index 00000000..f0ca2deb --- /dev/null +++ b/src/core/devices/wwan/tests/test-service-providers.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding='utf-8'?> +<!DOCTYPE serviceproviders SYSTEM "serviceproviders.2.dtd"> + +<serviceproviders format="2.0"> + +<country code="feh"> + <provider> + <name>Sophia</name> + <gsm> + <network-id mcc="666" mnc="999"/> + <apn value="access.example.com"> + <plan type="postpaid"/> + <usage type="internet"/> + <name>APN</name> + <dns>192.0.2.1</dns> + <dns>192.0.2.2</dns> + </apn> + </gsm> + </provider> +</country> + +<country code="meh"> + <provider> + <name>Demiurge</name> + <gsm> + <network-id mcc="133" mnc="37"/> + <network-id mcc="133" mnc="666"/> + <apn value="mms"> + <usage type="mms"/> + <name>Unsolicited Nudes MMS</name> + <username>mms</username> + <password>mms</password> + <mmsc>http://mms.example.com/</mmsc> + <mmsproxy>192.0.2.1:8080</mmsproxy> + </apn> + <apn value="gprs.example.com"> + <plan type="postpaid"/> + <usage type="internet"/> + <name>GPRS</name> + <username>praise</username> + <password>santa</password> + <dns>192.0.2.1</dns> + <dns>192.0.2.2</dns> + <gateway>192.0.2.3</gateway> + <authentication method="pap"/> + </apn> + <apn value="second.example.com"> + <plan type="postpaid"/> + <usage type="internet"/> + <name>Second</name> + <username>worship</username> + <password>doom</password> + </apn> + </gsm> + </provider> + + <provider> + <name>Personal</name> + <gsm> + <network-id mcc="666" mnc="999"/> + <apn value="access.example.com"> + <plan type="postpaid"/> + <usage type="internet"/> + <name>APN</name> + <dns>192.0.2.1</dns> + <dns>192.0.2.2</dns> + </apn> + </gsm> + </provider> +</country> + +</serviceproviders> + |