summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2019-09-11 14:34:48 +0200
committerLubomir Rintel <lkundrak@v3.sk>2019-09-11 14:34:48 +0200
commitbeab85520afa091392947b8cab128d513de194b7 (patch)
treee005df8fb2ef256f63d4f28cab8898d046623c1e
parent5730b0ff463f3530db407dac8e159a345b2bf867 (diff)
parent7c53930ceb1561182deb8f47dd9927e049fc396d (diff)
downloadNetworkManager-beab85520afa091392947b8cab128d513de194b7.tar.gz
merge: branch 'lr/gsm-default-apn'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/merge_requests/98
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am31
-rw-r--r--clients/common/nm-meta-setting-desc.c3
-rw-r--r--clients/common/settings-docs.h.in1
-rw-r--r--clients/tests/test-client.check-on-disk/test_003.expected10
-rw-r--r--config.h.meson3
-rw-r--r--configure.ac10
-rwxr-xr-xcontrib/fedora/REQUIRED_PACKAGES1
-rw-r--r--contrib/fedora/rpm/NetworkManager.spec3
-rw-r--r--libnm-core/nm-connection.c84
-rw-r--r--libnm-core/nm-setting-gsm.c63
-rw-r--r--libnm-core/nm-setting-gsm.h5
-rw-r--r--libnm-core/tests/test-general.c2
-rw-r--r--libnm/libnm.ver5
-rw-r--r--meson.build3
-rw-r--r--src/devices/wwan/meson.build9
-rw-r--r--src/devices/wwan/nm-modem-broadband.c150
-rw-r--r--src/devices/wwan/nm-service-providers.c460
-rw-r--r--src/devices/wwan/nm-service-providers.h24
-rw-r--r--src/devices/wwan/tests/meson.build13
-rw-r--r--src/devices/wwan/tests/test-service-providers.c124
-rw-r--r--src/devices/wwan/tests/test-service-providers.xml73
22 files changed, 1000 insertions, 78 deletions
diff --git a/.gitignore b/.gitignore
index d74b40c08d..55e8317e31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -209,6 +209,7 @@ test-*.trs
/src/devices/tests/test-acd
/src/devices/tests/test-lldp
/src/devices/wifi/tests/test-devices-wifi
+/src/devices/wwan/tests/test-service-providers
/src/dhcp/nm-dhcp-helper
/src/dhcp/tests/test-dhcp-dhclient
/src/dhcp/tests/test-dhcp-options
diff --git a/Makefile.am b/Makefile.am
index ef275bb440..7eb6a82674 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3315,7 +3315,10 @@ src_devices_wwan_libnm_wwan_la_SOURCES = \
src/devices/wwan/nm-modem-manager.c \
src/devices/wwan/nm-modem-manager.h \
src/devices/wwan/nm-modem.c \
- src/devices/wwan/nm-modem.h
+ src/devices/wwan/nm-modem.h \
+ src/devices/wwan/nm-service-providers.c \
+ src/devices/wwan/nm-service-providers.h \
+ $(NULL)
if WITH_OFONO
src_devices_wwan_libnm_wwan_la_SOURCES += \
@@ -3364,11 +3367,35 @@ check-local-devices-wwan: src/devices/wwan/libnm-device-plugin-wwan.la src/devic
check_local += check-local-devices-wwan
+src_devices_wwan_tests_test_service_providers_SOURCES = \
+ src/devices/wwan/tests/test-service-providers.c \
+ src/devices/wwan/nm-service-providers.c \
+ src/devices/wwan/nm-service-providers.h \
+ $(NULL)
+
+src_devices_wwan_tests_test_service_providers_CPPFLAGS = \
+ $(src_cppflags_base_test) \
+ -I$(srcdir)/src/devices/wwan \
+ $(NULL)
+
+src_devices_wwan_tests_test_service_providers_LDFLAGS = \
+ $(SANITIZER_EXEC_LDFLAGS) \
+ $(NULL)
+
+src_devices_wwan_tests_test_service_providers_LDADD = \
+ src/libNetworkManagerTest.la \
+ $(GLIB_LIBS) \
+ $(NULL)
+
+check_programs += src/devices/wwan/tests/test-service-providers
+
endif
EXTRA_DIST += \
src/devices/wwan/libnm-wwan.ver \
- src/devices/wwan/meson.build
+ src/devices/wwan/meson.build \
+ src/devices/wwan/tests/test-service-providers.xml \
+ $(NULL)
###############################################################################
# src/devices/bluetooth
diff --git a/clients/common/nm-meta-setting-desc.c b/clients/common/nm-meta-setting-desc.c
index 6451fe3378..40faa485ed 100644
--- a/clients/common/nm-meta-setting-desc.c
+++ b/clients/common/nm-meta-setting-desc.c
@@ -5365,6 +5365,9 @@ static const NMMetaPropertyInfo *const property_infos_ETHTOOL[] = {
#undef _CURRENT_NM_META_SETTING_TYPE
#define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_GSM
static const NMMetaPropertyInfo *const property_infos_GSM[] = {
+ PROPERTY_INFO_WITH_DESC (NM_SETTING_GSM_AUTO_CONFIG,
+ .property_type = &_pt_gobject_bool,
+ ),
PROPERTY_INFO_WITH_DESC (NM_SETTING_GSM_NUMBER,
.property_type = &_pt_gobject_string,
),
diff --git a/clients/common/settings-docs.h.in b/clients/common/settings-docs.h.in
index d49331993b..518381077b 100644
--- a/clients/common/settings-docs.h.in
+++ b/clients/common/settings-docs.h.in
@@ -170,6 +170,7 @@
#define DESCRIBE_DOC_NM_SETTING_DCB_PRIORITY_STRICT_BANDWIDTH N_("An array of 8 boolean values, where the array index corresponds to the User Priority (0 - 7) and the value indicates whether or not the priority may use all of the bandwidth allocated to its assigned group.")
#define DESCRIBE_DOC_NM_SETTING_DCB_PRIORITY_TRAFFIC_CLASS N_("An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the traffic class (0 - 7) to which the priority is mapped.")
#define DESCRIBE_DOC_NM_SETTING_GSM_APN N_("The GPRS Access Point Name specifying the APN used when establishing a data session with the GSM-based network. The APN often determines how the user will be billed for their network usage and whether the user has access to the Internet or just a provider-specific walled-garden, so it is important to use the correct APN for the user's mobile broadband plan. The APN may only be composed of the characters a-z, 0-9, ., and - per GSM 03.60 Section 14.9.")
+#define DESCRIBE_DOC_NM_SETTING_GSM_AUTO_CONFIG N_("When TRUE, the settings such as APN, username, or password will default to values that match the network the modem will register to in the Mobile Broadband Provider database.")
#define DESCRIBE_DOC_NM_SETTING_GSM_DEVICE_ID N_("The device unique identifier (as given by the WWAN management service) which this connection applies to. If given, the connection will only apply to the specified device.")
#define DESCRIBE_DOC_NM_SETTING_GSM_HOME_ONLY N_("When TRUE, only connections to the home network will be allowed. Connections to roaming networks will not be made.")
#define DESCRIBE_DOC_NM_SETTING_GSM_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames.")
diff --git a/clients/tests/test-client.check-on-disk/test_003.expected b/clients/tests/test-client.check-on-disk/test_003.expected
index c8cb4903b5..8bffc5b126 100644
--- a/clients/tests/test-client.check-on-disk/test_003.expected
+++ b/clients/tests/test-client.check-on-disk/test_003.expected
@@ -150,12 +150,12 @@ id
path
uuid
<<<
-size: 4116
+size: 4159
location: clients/tests/test-client.py:911:test_003()/12
cmd: $NMCLI con s con-gsm1
lang: C
returncode: 0
-stdout: 3982 bytes
+stdout: 4025 bytes
>>>
connection.id: con-gsm1
connection.uuid: UUID-con-gsm1-REPLACED-REPLACED-REPL
@@ -228,6 +228,7 @@ serial.bits: 8
serial.parity: even
serial.stopbits: 1
serial.send-delay: 100
+gsm.auto-config: no
gsm.number: --
gsm.username: --
gsm.password: <hidden>
@@ -247,12 +248,12 @@ proxy.pac-url: --
proxy.pac-script: --
<<<
-size: 4145
+size: 4189
location: clients/tests/test-client.py:911:test_003()/13
cmd: $NMCLI con s con-gsm1
lang: pl_PL.UTF-8
returncode: 0
-stdout: 4001 bytes
+stdout: 4045 bytes
>>>
connection.id: con-gsm1
connection.uuid: UUID-con-gsm1-REPLACED-REPLACED-REPL
@@ -325,6 +326,7 @@ serial.bits: 8
serial.parity: even
serial.stopbits: 1
serial.send-delay: 100
+gsm.auto-config: nie
gsm.number: --
gsm.username: --
gsm.password: <hidden>
diff --git a/config.h.meson b/config.h.meson
index a8c694011f..7ba9f9af56 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -73,6 +73,9 @@
/* Define to path of the kernel firmware directory */
#mesondefine KERNEL_FIRMWARE_DIR
+/* Mobile Broadband Service Provider Information Database location */
+#mesondefine MOBILE_BROADBAND_PROVIDER_INFO_DATABASE
+
/* Path to netconfig */
#mesondefine NETCONFIG_PATH
diff --git a/configure.ac b/configure.ac
index 951f51b9a0..68507e2884 100644
--- a/configure.ac
+++ b/configure.ac
@@ -747,10 +747,20 @@ if (test "${with_modem_manager_1}" != "no"); then
fi
else
with_modem_manager_1="yes"
+
+ PKG_CHECK_MODULES(MOBILE_BROADBAND_PROVIDER_INFO, [mobile-broadband-provider-info],
+ [MOBILE_BROADBAND_PROVIDER_INFO_DATABASE=`$PKG_CONFIG --variable=database mobile-broadband-provider-info`],
+ [MOBILE_BROADBAND_PROVIDER_INFO_DATABASE="$prefix/share/mobile-broadband-provider-info/serviceproviders.xml"])
+ AC_DEFINE_UNQUOTED([MOBILE_BROADBAND_PROVIDER_INFO_DATABASE],
+ ["$MOBILE_BROADBAND_PROVIDER_INFO_DATABASE"],
+ [Mobile Broadband Service Provider Information Database location])
fi
fi
AM_CONDITIONAL(WITH_MODEM_MANAGER_1, test "${with_modem_manager_1}" = "yes")
+
+
+
# Bluez5 DUN support
PKG_CHECK_MODULES(BLUEZ5, [bluez >= 5], [have_bluez5=yes],[have_bluez5=no])
AC_ARG_ENABLE(bluez5-dun,
diff --git a/contrib/fedora/REQUIRED_PACKAGES b/contrib/fedora/REQUIRED_PACKAGES
index 00164cc669..7579e9a983 100755
--- a/contrib/fedora/REQUIRED_PACKAGES
+++ b/contrib/fedora/REQUIRED_PACKAGES
@@ -22,6 +22,7 @@ install \
\
ModemManager-devel \
ModemManager-glib-devel \
+ mobile-broadband-provider-info-devel \
audit-libs-devel \
bash-completion \
bluez-libs-devel \
diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec
index b7957d602c..4eddef793a 100644
--- a/contrib/fedora/rpm/NetworkManager.spec
+++ b/contrib/fedora/rpm/NetworkManager.spec
@@ -219,6 +219,9 @@ BuildRequires: libndp-devel >= 1.0
%if 0%{?with_modem_manager_1}
BuildRequires: ModemManager-glib-devel >= 1.0
%endif
+%if %{with wwan}
+BuildRequires: mobile-broadband-provider-info-devel
+%endif
%if %{with nmtui}
BuildRequires: newt-devel
%endif
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index 2c69d2a998..36600a89c4 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -1041,7 +1041,7 @@ _normalize_ip_config (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_infiniband_mtu (NMConnection *self, GHashTable *parameters)
+_normalize_infiniband_mtu (NMConnection *self)
{
NMSettingInfiniband *s_infini = nm_connection_get_setting_infiniband (self);
@@ -1056,7 +1056,7 @@ _normalize_infiniband_mtu (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_bond_mode (NMConnection *self, GHashTable *parameters)
+_normalize_bond_mode (NMConnection *self)
{
NMSettingBond *s_bond = nm_connection_get_setting_bond (self);
@@ -1077,7 +1077,7 @@ _normalize_bond_mode (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_bond_options (NMConnection *self, GHashTable *parameters)
+_normalize_bond_options (NMConnection *self)
{
NMSettingBond *s_bond = nm_connection_get_setting_bond (self);
gboolean changed = FALSE;
@@ -1107,7 +1107,7 @@ again:
}
static gboolean
-_normalize_wireless_mac_address_randomization (NMConnection *self, GHashTable *parameters)
+_normalize_wireless_mac_address_randomization (NMConnection *self)
{
NMSettingWireless *s_wifi = nm_connection_get_setting_wireless (self);
const char *cloned_mac_address;
@@ -1153,7 +1153,7 @@ _normalize_wireless_mac_address_randomization (NMConnection *self, GHashTable *p
}
static gboolean
-_normalize_macsec (NMConnection *self, GHashTable *parameters)
+_normalize_macsec (NMConnection *self)
{
NMSettingMacsec *s_macsec = nm_connection_get_setting_macsec (self);
gboolean changed = FALSE;
@@ -1176,7 +1176,7 @@ _normalize_macsec (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_team_config (NMConnection *self, GHashTable *parameters)
+_normalize_team_config (NMConnection *self)
{
NMSettingTeam *s_team = nm_connection_get_setting_team (self);
@@ -1192,7 +1192,7 @@ _normalize_team_config (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_team_port_config (NMConnection *self, GHashTable *parameters)
+_normalize_team_port_config (NMConnection *self)
{
NMSettingTeamPort *s_team_port = nm_connection_get_setting_team_port (self);
@@ -1208,7 +1208,7 @@ _normalize_team_port_config (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_bluetooth_type (NMConnection *self, GHashTable *parameters)
+_normalize_bluetooth_type (NMConnection *self)
{
const char *type = _nm_connection_detect_bluetooth_type (self);
@@ -1222,7 +1222,7 @@ _normalize_bluetooth_type (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_ovs_interface_type (NMConnection *self, GHashTable *parameters)
+_normalize_ovs_interface_type (NMConnection *self)
{
NMSettingOvsInterface *s_ovs_interface = nm_connection_get_setting_ovs_interface (self);
gboolean modified;
@@ -1243,7 +1243,7 @@ _normalize_ovs_interface_type (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_ip_tunnel_wired_setting (NMConnection *self, GHashTable *parameters)
+_normalize_ip_tunnel_wired_setting (NMConnection *self)
{
NMSettingIPTunnel *s_ip_tunnel;
@@ -1263,7 +1263,7 @@ _normalize_ip_tunnel_wired_setting (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_sriov_vf_order (NMConnection *self, GHashTable *parameters)
+_normalize_sriov_vf_order (NMConnection *self)
{
NMSettingSriov *s_sriov;
@@ -1275,7 +1275,7 @@ _normalize_sriov_vf_order (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_bridge_vlan_order (NMConnection *self, GHashTable *parameters)
+_normalize_bridge_vlan_order (NMConnection *self)
{
NMSettingBridge *s_bridge;
@@ -1287,7 +1287,7 @@ _normalize_bridge_vlan_order (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_bridge_port_vlan_order (NMConnection *self, GHashTable *parameters)
+_normalize_bridge_port_vlan_order (NMConnection *self)
{
NMSettingBridgePort *s_port;
@@ -1299,7 +1299,30 @@ _normalize_bridge_port_vlan_order (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_required_settings (NMConnection *self, GHashTable *parameters)
+_normalize_gsm_auto_config (NMConnection *self)
+{
+ NMSettingGsm *s_gsm;
+
+ s_gsm = nm_connection_get_setting_gsm (self);
+ if (!s_gsm)
+ return FALSE;
+
+ if (!nm_setting_gsm_get_auto_config (s_gsm))
+ return FALSE;
+
+ if ( !nm_setting_gsm_get_apn (s_gsm)
+ && !nm_setting_gsm_get_username (s_gsm)
+ && !nm_setting_gsm_get_password (s_gsm))
+ return FALSE;
+
+ g_object_set (s_gsm,
+ NM_SETTING_GSM_AUTO_CONFIG, FALSE,
+ NULL);
+ return TRUE;
+}
+
+static gboolean
+_normalize_required_settings (NMConnection *self)
{
NMSettingBluetooth *s_bt = nm_connection_get_setting_bluetooth (self);
NMSetting *s_bridge;
@@ -1323,7 +1346,7 @@ _normalize_required_settings (NMConnection *self, GHashTable *parameters)
}
static gboolean
-_normalize_invalid_slave_port_settings (NMConnection *self, GHashTable *parameters)
+_normalize_invalid_slave_port_settings (NMConnection *self)
{
NMSettingConnection *s_con = nm_connection_get_setting_connection (self);
const char *slave_type;
@@ -1597,23 +1620,24 @@ _connection_normalize (NMConnection *connection,
was_modified |= _normalize_connection_uuid (connection);
was_modified |= _normalize_connection_type (connection);
was_modified |= _normalize_connection_slave_type (connection);
- was_modified |= _normalize_required_settings (connection, parameters);
- was_modified |= _normalize_invalid_slave_port_settings (connection, parameters);
+ was_modified |= _normalize_required_settings (connection);
+ was_modified |= _normalize_invalid_slave_port_settings (connection);
was_modified |= _normalize_ip_config (connection, parameters);
was_modified |= _normalize_ethernet_link_neg (connection);
- was_modified |= _normalize_infiniband_mtu (connection, parameters);
- was_modified |= _normalize_bond_mode (connection, parameters);
- was_modified |= _normalize_bond_options (connection, parameters);
- was_modified |= _normalize_wireless_mac_address_randomization (connection, parameters);
- was_modified |= _normalize_macsec (connection, parameters);
- was_modified |= _normalize_team_config (connection, parameters);
- was_modified |= _normalize_team_port_config (connection, parameters);
- was_modified |= _normalize_bluetooth_type (connection, parameters);
- was_modified |= _normalize_ovs_interface_type (connection, parameters);
- was_modified |= _normalize_ip_tunnel_wired_setting (connection, parameters);
- was_modified |= _normalize_sriov_vf_order (connection, parameters);
- was_modified |= _normalize_bridge_vlan_order (connection, parameters);
- was_modified |= _normalize_bridge_port_vlan_order (connection, parameters);
+ was_modified |= _normalize_infiniband_mtu (connection);
+ was_modified |= _normalize_bond_mode (connection);
+ was_modified |= _normalize_bond_options (connection);
+ was_modified |= _normalize_wireless_mac_address_randomization (connection);
+ was_modified |= _normalize_macsec (connection);
+ was_modified |= _normalize_team_config (connection);
+ was_modified |= _normalize_team_port_config (connection);
+ was_modified |= _normalize_bluetooth_type (connection);
+ was_modified |= _normalize_ovs_interface_type (connection);
+ was_modified |= _normalize_ip_tunnel_wired_setting (connection);
+ was_modified |= _normalize_sriov_vf_order (connection);
+ was_modified |= _normalize_bridge_vlan_order (connection);
+ was_modified |= _normalize_bridge_port_vlan_order (connection);
+ was_modified |= _normalize_gsm_auto_config (connection);
was_modified = !!was_modified;
diff --git a/libnm-core/nm-setting-gsm.c b/libnm-core/nm-setting-gsm.c
index dd5e5ee37a..b4ca97e6d8 100644
--- a/libnm-core/nm-setting-gsm.c
+++ b/libnm-core/nm-setting-gsm.c
@@ -24,6 +24,7 @@
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+ PROP_AUTO_CONFIG,
PROP_NUMBER,
PROP_USERNAME,
PROP_PASSWORD,
@@ -40,6 +41,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
);
typedef struct {
+ gboolean auto_config;
+
char *number; /* For dialing, duh */
char *username;
char *password;
@@ -67,6 +70,22 @@ G_DEFINE_TYPE (NMSettingGsm, nm_setting_gsm, NM_TYPE_SETTING)
/*****************************************************************************/
/**
+ * nm_setting_gsm_get_auto_config:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: the #NMSettingGsm:auto-config property of the setting
+ *
+ * Since: 1.22
+ **/
+gboolean
+nm_setting_gsm_get_auto_config (NMSettingGsm *setting)
+{
+ g_return_val_if_fail (NM_IS_SETTING_GSM (setting), FALSE);
+
+ return NM_SETTING_GSM_GET_PRIVATE (setting)->auto_config;
+}
+
+/**
* nm_setting_gsm_get_number:
* @setting: the #NMSettingGsm
*
@@ -273,10 +292,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
}
if (priv->apn) {
- guint32 apn_len = strlen (priv->apn);
- guint32 i;
+ gsize apn_len = strlen (priv->apn);
+ gsize i;
- if (apn_len < 1 || apn_len > 64) {
+ if (apn_len > 64) {
g_set_error (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -320,7 +339,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
}
}
- if (priv->username && !strlen (priv->username)) {
+ if ( priv->username
+ && priv->username[0] == '\0') {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -330,8 +350,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
}
if (priv->network_id) {
- guint32 nid_len = strlen (priv->network_id);
- guint32 i;
+ gsize nid_len = strlen (priv->network_id);
+ gsize i;
/* Accept both 5 and 6 digit MCC/MNC codes */
if ((nid_len < 5) || (nid_len > 6)) {
@@ -400,6 +420,16 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
}
}
+ if ( priv->auto_config
+ && (priv->apn || priv->username || priv->password)) {
+ g_set_error_literal (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("can't be enabled when manual configuration is present"));
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_AUTO_CONFIG);
+ return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
+ }
+
return TRUE;
}
@@ -440,6 +470,9 @@ get_property (GObject *object, guint prop_id,
NMSettingGsm *setting = NM_SETTING_GSM (object);
switch (prop_id) {
+ case PROP_AUTO_CONFIG:
+ g_value_set_boolean (value, nm_setting_gsm_get_auto_config (setting));
+ break;
case PROP_NUMBER:
g_value_set_string (value, nm_setting_gsm_get_number (setting));
break;
@@ -493,6 +526,9 @@ set_property (GObject *object, guint prop_id,
char *tmp;
switch (prop_id) {
+ case PROP_AUTO_CONFIG:
+ priv->auto_config = g_value_get_boolean (value);
+ break;
case PROP_NUMBER:
g_free (priv->number);
priv->number = g_value_dup_string (value);
@@ -609,6 +645,21 @@ nm_setting_gsm_class_init (NMSettingGsmClass *klass)
setting_class->need_secrets = need_secrets;
/**
+ * NMSettingGsm:auto-config:
+ *
+ * When %TRUE, the settings such as APN, username, or password will
+ * default to values that match the network the modem will register
+ * to in the Mobile Broadband Provider database.
+ *
+ * Since: 1.22
+ **/
+ obj_properties[PROP_AUTO_CONFIG] =
+ g_param_spec_boolean (NM_SETTING_GSM_AUTO_CONFIG, "", "",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
* NMSettingGsm:number:
*
* Legacy setting that used to help establishing PPP data sessions for
diff --git a/libnm-core/nm-setting-gsm.h b/libnm-core/nm-setting-gsm.h
index 057ccf9e75..8a6fabd7f8 100644
--- a/libnm-core/nm-setting-gsm.h
+++ b/libnm-core/nm-setting-gsm.h
@@ -24,6 +24,7 @@ G_BEGIN_DECLS
#define NM_SETTING_GSM_SETTING_NAME "gsm"
+#define NM_SETTING_GSM_AUTO_CONFIG "auto-config"
#define NM_SETTING_GSM_USERNAME "username"
#define NM_SETTING_GSM_PASSWORD "password"
#define NM_SETTING_GSM_PASSWORD_FLAGS "password-flags"
@@ -59,6 +60,10 @@ typedef struct {
GType nm_setting_gsm_get_type (void);
NMSetting *nm_setting_gsm_new (void);
+
+NM_AVAILABLE_IN_1_22
+gboolean nm_setting_gsm_get_auto_config (NMSettingGsm *setting);
+
const char *nm_setting_gsm_get_username (NMSettingGsm *setting);
const char *nm_setting_gsm_get_password (NMSettingGsm *setting);
const char *nm_setting_gsm_get_apn (NMSettingGsm *setting);
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 4df97b3469..c477844c55 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -1747,7 +1747,7 @@ test_setting_gsm_apn_bad_chars (void)
/* 0 characters long */
g_object_set (s_gsm, NM_SETTING_GSM_APN, "", NULL);
- g_assert (!nm_setting_verify (NM_SETTING (s_gsm), NULL, NULL));
+ g_assert (nm_setting_verify (NM_SETTING (s_gsm), NULL, NULL));
/* 65-character long */
g_object_set (s_gsm, NM_SETTING_GSM_APN, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl1", NULL);
diff --git a/libnm/libnm.ver b/libnm/libnm.ver
index 51e0d87241..68eaf745e9 100644
--- a/libnm/libnm.ver
+++ b/libnm/libnm.ver
@@ -1628,3 +1628,8 @@ global:
nm_setting_wireguard_get_ip6_auto_default_route;
nm_settings_add_connection2_flags_get_type;
} libnm_1_18_0;
+
+libnm_1_22_0 {
+global:
+ nm_setting_gsm_get_auto_config;
+} libnm_1_20_0;
diff --git a/meson.build b/meson.build
index 9fc9ad2684..db0fcbd29f 100644
--- a/meson.build
+++ b/meson.build
@@ -502,6 +502,9 @@ config_h.set10('WITH_PPP', enable_ppp)
enable_modem_manager = get_option('modem_manager')
if enable_modem_manager
mm_glib_dep = dependency('mm-glib', version: '>= 0.7.991')
+
+ service_provider_db = dependency('mobile-broadband-provider-info').get_pkgconfig_variable('database')
+ config_h.set_quoted('MOBILE_BROADBAND_PROVIDER_INFO_DATABASE', service_provider_db)
endif
# Bluez5 DUN support
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
index 482dc205fa..363e522bf1 100644
--- a/src/devices/wwan/meson.build
+++ b/src/devices/wwan/meson.build
@@ -2,6 +2,7 @@ sources = files(
'nm-modem-broadband.c',
'nm-modem.c',
'nm-modem-manager.c',
+ 'nm-service-providers.c',
)
deps = [
@@ -28,8 +29,10 @@ libnm_wwan = shared_module(
install_dir: nm_plugindir,
)
+wwan_inc = include_directories('.')
+
libnm_wwan_dep = declare_dependency(
- include_directories: include_directories('.'),
+ include_directories: wwan_inc,
link_with: libnm_wwan,
)
@@ -74,3 +77,7 @@ check-local-devices-wwan: src/devices/wwan/libnm-device-plugin-wwan.la src/devic
$(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-wwan.so "$(srcdir)/src/devices/wwan/libnm-wwan.ver"
$(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-wwan.so)
'''
+
+if enable_tests
+ subdir('tests')
+endif
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 056d302e96..820c343a31 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -7,6 +7,7 @@
#include "nm-default.h"
#include "nm-modem-broadband.h"
+#include "nm-service-providers.h"
#include <arpa/inet.h>
#include <libmm-glib.h>
@@ -261,7 +262,10 @@ create_cdma_connect_properties (NMConnection *connection)
}
static MMSimpleConnectProperties *
-create_gsm_connect_properties (NMConnection *connection)
+create_gsm_connect_properties (NMConnection *connection,
+ const char *apn,
+ const char *username,
+ const char *password)
{
NMSettingGsm *setting;
NMSettingPpp *s_ppp;
@@ -269,11 +273,14 @@ create_gsm_connect_properties (NMConnection *connection)
const char *str;
setting = nm_connection_get_setting_gsm (connection);
+
properties = mm_simple_connect_properties_new ();
- /* Blank APN ("") means the default subscription APN */
- str = nm_setting_gsm_get_apn (setting);
- mm_simple_connect_properties_set_apn (properties, str ?: "");
+ 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)
@@ -283,14 +290,6 @@ create_gsm_connect_properties (NMConnection *connection)
if (str)
mm_simple_connect_properties_set_pin (properties, str);
- str = nm_setting_gsm_get_username (setting);
- if (str)
- mm_simple_connect_properties_set_user (properties, str);
-
- str = nm_setting_gsm_get_password (setting);
- if (str)
- mm_simple_connect_properties_set_password (properties, str);
-
/* Roaming */
if (nm_setting_gsm_get_home_only (setting))
mm_simple_connect_properties_set_allow_roaming (properties, FALSE);
@@ -444,6 +443,95 @@ send_pin_ready (MMSim *sim, GAsyncResult *result, NMModemBroadband *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;
@@ -487,22 +575,8 @@ connect_context_step (NMModemBroadband *self)
if (mm_modem_get_state (self->_priv.modem_iface) <= MM_MODEM_STATE_LOCKED)
break;
- /* Create core connect properties based on the modem capabilities */
- g_assert (!ctx->connect_properties);
-
- if (MODEM_CAPS_3GPP (ctx->caps))
- ctx->connect_properties = create_gsm_connect_properties (ctx->connection);
- else if (MODEM_CAPS_3GPP2 (ctx->caps))
- ctx->connect_properties = create_cdma_connect_properties (ctx->connection);
- else {
- _LOGW ("failed to connect '%s': not a mobile broadband modem",
- nm_connection_get_id (ctx->connection));
-
- nm_modem_emit_prepare_result (NM_MODEM (self), FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
- connect_context_clear (self);
+ if (!try_create_connect_properties (self))
break;
- }
- g_assert (ctx->connect_properties);
/* 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);
@@ -521,6 +595,9 @@ connect_context_step (NMModemBroadband *self)
}
/* fall through */
case CONNECT_STEP_CONNECT:
+ if (!ctx->connect_properties)
+ break;
+
if (ctx->ip_types_i < ctx->ip_types->len) {
NMModemIPType current;
@@ -533,7 +610,7 @@ connect_context_step (NMModemBroadband *self)
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_assert_not_reached ();
+ g_return_if_reached ();
_nm_modem_set_apn (NM_MODEM (self), mm_simple_connect_properties_get_apn (ctx->connect_properties));
@@ -676,6 +753,9 @@ complete_connection (NMModem *modem,
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)) {
@@ -863,8 +943,8 @@ static_stage3_ip4_done (NMModemBroadband *self)
guint32 ip4_route_table, ip4_route_metric;
NMPlatformIP4Route *r;
- g_assert (self->_priv.ipv4_config);
- g_assert (self->_priv.bearer);
+ 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;
@@ -895,7 +975,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
}
data_port = mm_bearer_get_interface (self->_priv.bearer);
- g_assert (data_port);
+ 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));
@@ -970,7 +1050,7 @@ stage3_ip6_done (NMModemBroadband *self)
const char **dns;
guint i;
- g_assert (self->_priv.ipv6_config);
+ g_return_val_if_fail (self->_priv.ipv6_config, FALSE);
self->_priv.idle_id_ip6 = 0;
memset (&address, 0, sizeof (address));
@@ -1002,7 +1082,8 @@ stage3_ip6_done (NMModemBroadband *self)
_LOGI ("IPv6 base configuration:");
data_port = mm_bearer_get_interface (self->_priv.bearer);
- g_assert (data_port);
+ 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));
@@ -1370,8 +1451,9 @@ set_property (GObject *object,
/* 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_assert (self->_priv.modem_iface != NULL);
+
g_signal_connect (self->_priv.modem_iface,
"state-changed",
G_CALLBACK (modem_state_changed),
diff --git a/src/devices/wwan/nm-service-providers.c b/src/devices/wwan/nm-service-providers.c
new file mode 100644
index 0000000000..f363ebe506
--- /dev/null
+++ b/src/devices/wwan/nm-service-providers.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2009 Novell, Inc.
+ * Author: Tambet Ingo (tambet@gmail.com).
+ *
+ * Copyright (C) 2009 - 2019 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include "nm-default.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;
+ g_clear_pointer (&parse_context->apn, g_free);
+ g_clear_pointer (&parse_context->username, g_free);
+ g_clear_pointer (&parse_context->password, g_free);
+ g_clear_pointer (&parse_context->gateway, g_free);
+ g_clear_pointer (&parse_context->auth_method, g_free);
+ 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) {
+ g_clear_pointer (&parse_context->auth_method, g_free);
+ 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;
+
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+
+ 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) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ parse_context->state = PARSER_TOPLEVEL;
+ }
+}
+
+static void
+parser_provider_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "provider") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ parse_context->state = PARSER_COUNTRY;
+ }
+}
+
+static void
+parser_gsm_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "gsm") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ parse_context->state = PARSER_PROVIDER;
+ }
+}
+
+static void
+parser_gsm_apn_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "username") == 0) {
+ g_clear_pointer (&parse_context->username, g_free);
+ parse_context->username = g_steal_pointer (&parse_context->text_buffer);
+ } else if (strcmp (name, "password") == 0) {
+ g_clear_pointer (&parse_context->password, g_free);
+ 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) {
+ g_clear_pointer (&parse_context->gateway, g_free);
+ parse_context->gateway = g_steal_pointer (&parse_context->text_buffer);
+ } else if (strcmp (name, "apn") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+
+ 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) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ 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/devices/wwan/nm-service-providers.h b/src/devices/wwan/nm-service-providers.h
new file mode 100644
index 0000000000..35ad2fc120
--- /dev/null
+++ b/src/devices/wwan/nm-service-providers.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * 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/devices/wwan/tests/meson.build b/src/devices/wwan/tests/meson.build
new file mode 100644
index 0000000000..64dd76742a
--- /dev/null
+++ b/src/devices/wwan/tests/meson.build
@@ -0,0 +1,13 @@
+exe = executable(
+ 'test-service-providers',
+ sources: files('test-service-providers.c',
+ '../nm-service-providers.c'),
+ dependencies: test_nm_dep,
+ include_directories: wwan_inc,
+)
+test(
+ 'wwan/test-service-providers',
+ test_script,
+ timeout: default_test_timeout,
+ args: test_args + [exe.full_path()],
+)
diff --git a/src/devices/wwan/tests/test-service-providers.c b/src/devices/wwan/tests/test-service-providers.c
new file mode 100644
index 0000000000..33402cd52c
--- /dev/null
+++ b/src/devices/wwan/tests/test-service-providers.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2019 Red Hat
+ */
+
+#include "nm-default.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/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/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/devices/wwan/tests/test-service-providers.xml b/src/devices/wwan/tests/test-service-providers.xml
new file mode 100644
index 0000000000..f0ca2debce
--- /dev/null
+++ b/src/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>
+