summary refs log tree commit diff
path: root/src/devices/wwan/nm-modem-broadband.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/wwan/nm-modem-broadband.c')
-rw-r--r--src/devices/wwan/nm-modem-broadband.c283
1 files changed, 228 insertions, 55 deletions
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 807b8924..f8239b79 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -18,14 +18,19 @@
  * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
  */
 
+#include "config.h"
+
 #include <glib/gi18n.h>
 #include <string.h>
+#include <arpa/inet.h>
 #include <libmm-glib.h>
+
 #include "nm-modem-broadband.h"
-#include "nm-setting-connection.h"
+#include "nm-core-internal.h"
 #include "nm-logging.h"
 #include "NetworkManagerUtils.h"
 #include "nm-device-private.h"
+#include "nm-platform.h"
 
 G_DEFINE_TYPE (NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM)
 
@@ -156,13 +161,29 @@ ask_for_pin (NMModemBroadband *self)
 	                      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 void
 connect_ready (MMModemSimple *simple_iface,
                GAsyncResult *res,
                NMModemBroadband *self)
 {
 	GError *error = NULL;
-	guint ip_method;
+	NMModemIPMethod ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
+	NMModemIPMethod ip6_method = NM_MODEM_IP_METHOD_UNKNOWN;
 
 	g_clear_object (&self->priv->connect_properties);
 
@@ -195,35 +216,31 @@ connect_ready (MMModemSimple *simple_iface,
 
 	/* Grab IP configurations */
 	self->priv->ipv4_config = mm_bearer_get_ipv4_config (self->priv->bearer);
-	self->priv->ipv6_config = mm_bearer_get_ipv6_config (self->priv->bearer);
+	if (self->priv->ipv4_config)
+		ip4_method = get_bearer_ip_method (self->priv->ipv4_config);
 
-	switch (mm_bearer_ip_config_get_method (self->priv->ipv4_config)) {
-	case MM_BEARER_IP_METHOD_PPP:
-		ip_method = MM_MODEM_IP_METHOD_PPP;
-		break;
-	case MM_BEARER_IP_METHOD_STATIC:
-		ip_method = MM_MODEM_IP_METHOD_STATIC;
-		break;
-	case MM_BEARER_IP_METHOD_DHCP:
-		ip_method = MM_MODEM_IP_METHOD_DHCP;
-		break;
-	default:
-		error = g_error_new (NM_MODEM_ERROR,
-		                     NM_MODEM_ERROR_CONNECTION_INVALID,
-		                     "invalid IP config");
-		nm_log_warn (LOGD_MB, "(%s) failed to connect modem: %s",
-		             nm_modem_get_uid (NM_MODEM (self)),
-		             error->message);
-		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (error));
+	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 (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN &&
+	    ip6_method == NM_MODEM_IP_METHOD_UNKNOWN) {
+		nm_log_warn (LOGD_MB, "(%s) failed to connect modem: invalid bearer IP configuration",
+		             nm_modem_get_uid (NM_MODEM (self)));
+
+		error = g_error_new_literal (NM_DEVICE_ERROR,
+		                             NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                             "invalid bearer IP configuration");
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, error);
 		g_error_free (error);
 		g_object_unref (self);
 		return;
 	}
 
-	/* IPv4 for now only */
 	g_object_set (self,
 	              NM_MODEM_DATA_PORT,  mm_bearer_get_interface (self->priv->bearer),
-	              NM_MODEM_IP_METHOD,  ip_method,
+	              NM_MODEM_IP4_METHOD, ip4_method,
+	              NM_MODEM_IP6_METHOD, ip6_method,
 	              NM_MODEM_IP_TIMEOUT, mm_bearer_get_ip_timeout (self->priv->bearer),
 	              NULL);
 
@@ -249,12 +266,15 @@ create_cdma_connect_properties (NMConnection *connection)
 }
 
 static MMSimpleConnectProperties *
-create_gsm_connect_properties (NMConnection *connection)
+create_gsm_connect_properties (NMModem *modem,
+                               NMConnection *connection,
+                               GError **error)
 {
 	NMSettingGsm *setting;
-	NMSettingPPP *s_ppp;
+	NMSettingPpp *s_ppp;
 	MMSimpleConnectProperties *properties;
 	const gchar *str;
+	NMModemIPType ip_type;
 
 	setting = nm_connection_get_setting_gsm (connection);
 	properties = mm_simple_connect_properties_new ();
@@ -264,9 +284,9 @@ create_gsm_connect_properties (NMConnection *connection)
 	if (str)
 		mm_simple_connect_properties_set_number (properties, str);
 
+	/* Blank APN ("") means the default subscription APN */
 	str = nm_setting_gsm_get_apn (setting);
-	if (str)
-		mm_simple_connect_properties_set_apn (properties, str);
+	mm_simple_connect_properties_set_apn (properties, str ? str : "");
 
 	str = nm_setting_gsm_get_network_id (setting);
 	if (str)
@@ -309,6 +329,22 @@ create_gsm_connect_properties (NMConnection *connection)
 		mm_simple_connect_properties_set_allowed_auth (properties, allowed_auth);
 	}
 
+	/* Determine IP types to use when connecting */
+	ip_type = nm_modem_get_connection_ip_type (modem, connection, error);
+	if (ip_type == NM_MODEM_IP_TYPE_UNKNOWN) {
+		g_object_unref (properties);
+		return NULL;
+	}
+
+	if (ip_type == NM_MODEM_IP_TYPE_IPV4)
+		mm_simple_connect_properties_set_ip_type (properties, MM_BEARER_IP_FAMILY_IPV4);
+	else if (ip_type == NM_MODEM_IP_TYPE_IPV6)
+		mm_simple_connect_properties_set_ip_type (properties, MM_BEARER_IP_FAMILY_IPV6);
+	else if (ip_type == NM_MODEM_IP_TYPE_IPV4V6)
+		mm_simple_connect_properties_set_ip_type (properties, MM_BEARER_IP_FAMILY_IPV4V6);
+	else
+		g_assert_not_reached ();
+
 	return properties;
 }
 
@@ -319,17 +355,29 @@ act_stage1_prepare (NMModem *_self,
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
 	MMModemCapability caps;
+	GError *error = NULL;
 
 	g_clear_object (&self->priv->connect_properties);
 
 	caps = mm_modem_get_current_capabilities (self->priv->modem_iface);
 	if (MODEM_CAPS_3GPP (caps))
-		self->priv->connect_properties = create_gsm_connect_properties (connection);
+		self->priv->connect_properties = create_gsm_connect_properties (_self, connection, &error);
 	else if (MODEM_CAPS_3GPP2 (caps))
 		self->priv->connect_properties = create_cdma_connect_properties (connection);
 	else {
 		nm_log_warn (LOGD_MB, "(%s) not a mobile broadband modem",
 					 nm_modem_get_uid (NM_MODEM (self)));
+		*reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	if (error) {
+		nm_log_warn (LOGD_MB, "(%s): Failed to connect '%s': %s",
+		             nm_modem_get_uid (NM_MODEM (self)),
+		             nm_connection_get_id (connection),
+		             error->message);
+		g_clear_error (&error);
+		*reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED;
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
@@ -400,14 +448,14 @@ complete_connection (NMModem *_self,
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
 	MMModemCapability modem_caps;
-	NMSettingPPP *s_ppp;
+	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 ();
+		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,
@@ -419,12 +467,13 @@ complete_connection (NMModem *_self,
 		NMSettingGsm *s_gsm;
 
 		s_gsm = nm_connection_get_setting_gsm (connection);
-		if (!s_gsm || !nm_setting_gsm_get_apn (s_gsm)) {
-			/* Need an APN at least */
+		if (!s_gsm) {
+			/* Need a GSM setting at least */
 			g_set_error_literal (error,
-			                     NM_SETTING_GSM_ERROR,
-			                     NM_SETTING_GSM_ERROR_MISSING_PROPERTY,
-			                     NM_SETTING_GSM_APN);
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_MISSING_SETTING,
+			                     _("GSM mobile broadband connection requires a 'gsm' setting"));
+			g_prefix_error (error, "%s: ", NM_SETTING_GSM_SETTING_NAME);
 			return FALSE;
 		}
 
@@ -435,7 +484,8 @@ complete_connection (NMModem *_self,
 		nm_utils_complete_generic (connection,
 		                           NM_SETTING_GSM_SETTING_NAME,
 		                           existing_connections,
-		                           _("GSM connection %d"),
+		                           NULL,
+		                           _("GSM connection"),
 		                           NULL,
 		                           FALSE); /* No IPv6 yet by default */
 
@@ -457,7 +507,8 @@ complete_connection (NMModem *_self,
 		nm_utils_complete_generic (connection,
 		                           NM_SETTING_CDMA_SETTING_NAME,
 		                           existing_connections,
-		                           _("CDMA connection %d"),
+		                           NULL,
+		                           _("CDMA connection"),
 		                           NULL,
 		                           FALSE); /* No IPv6 yet by default */
 
@@ -465,8 +516,8 @@ complete_connection (NMModem *_self,
 	}
 
 	g_set_error (error,
-	             NM_MODEM_ERROR,
-	             NM_MODEM_ERROR_CONNECTION_INCOMPATIBLE,
+	             NM_DEVICE_ERROR,
+	             NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
 	             "Device is not a mobile broadband modem");
 	return FALSE;
 }
@@ -590,11 +641,10 @@ set_mm_enabled (NMModem *_self,
 }
 
 /*****************************************************************************/
-/* IP method static */
+/* IPv4 method static */
 
 static gboolean
-ip_string_to_network_address (const gchar *str,
-                              guint32 *out)
+ip4_string_to_num (const gchar *str, guint32 *out)
 {
 	guint32 addr = 0;
 	gboolean success = FALSE;
@@ -609,7 +659,7 @@ ip_string_to_network_address (const gchar *str,
 }
 
 static gboolean
-static_stage3_done (NMModemBroadband *self)
+static_stage3_ip4_done (NMModemBroadband *self)
 {
 	GError *error = NULL;
 	NMIP4Config *config = NULL;
@@ -628,9 +678,9 @@ static_stage3_done (NMModemBroadband *self)
 
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->priv->ipv4_config);
-	if (!ip_string_to_network_address (address_string, &address_network)) {
-		error = g_error_new (NM_MODEM_ERROR,
-		                     NM_MODEM_ERROR_CONNECTION_INVALID,
+	if (!ip4_string_to_num (address_string, &address_network)) {
+		error = g_error_new (NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "(%s) retrieving IP4 configuration failed: invalid address given '%s'",
 		                     nm_modem_get_uid (NM_MODEM (self)),
 		                     address_string);
@@ -639,14 +689,14 @@ static_stage3_done (NMModemBroadband *self)
 
 	/* Missing gateway not a hard failure */
 	gw_string = mm_bearer_ip_config_get_gateway (self->priv->ipv4_config);
-	ip_string_to_network_address (gw_string, &gw);
+	ip4_string_to_num (gw_string, &gw);
 
 	config = nm_ip4_config_new ();
 
 	memset (&address, 0, sizeof (address));
 	address.address = address_network;
 	address.plen = mm_bearer_ip_config_get_prefix (self->priv->ipv4_config);
-	address.source = NM_PLATFORM_SOURCE_WWAN;
+	address.source = NM_IP_CONFIG_SOURCE_WWAN;
 	nm_ip4_config_add_address (config, &address);
 
 	nm_log_info (LOGD_MB, "  address %s/%d", address_string, address.plen);
@@ -659,7 +709,7 @@ static_stage3_done (NMModemBroadband *self)
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->priv->ipv4_config);
 	for (i = 0; dns[i]; i++) {
-		if (   ip_string_to_network_address (dns[i], &address_network)
+		if (   ip4_string_to_num (dns[i], &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
 			nm_log_info (LOGD_MB, "  DNS %s", dns[i]);
@@ -681,7 +731,110 @@ static_stage3_ip4_config_start (NMModem *_self,
 
 	/* We schedule it in an idle just to follow the same logic as in the
 	 * generic modem implementation. */
-	g_idle_add ((GSourceFunc)static_stage3_done, self);
+	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 gchar *address_string;
+	NMPlatformIP6Address address;
+	NMModemIPMethod ip_method;
+	const gchar **dns;
+	guint i;
+
+	g_assert (self->priv->ipv6_config);
+
+	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;
+	}
+
+	nm_log_info (LOGD_MB, "(%s): IPv6 base configuration:",
+	             nm_modem_get_uid (NM_MODEM (self)));
+
+	config = nm_ip6_config_new ();
+
+	address.plen = mm_bearer_ip_config_get_prefix (self->priv->ipv6_config);
+	nm_ip6_config_add_address (config, &address);
+
+	nm_log_info (LOGD_MB, "  address %s/%d", address_string, address.plen);
+
+	address_string = mm_bearer_ip_config_get_gateway (self->priv->ipv6_config);
+	if (address_string) {
+		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 gateway given '%s'",
+				                 nm_modem_get_uid (NM_MODEM (self)),
+				                 address_string);
+			goto out;
+		}
+		nm_log_info (LOGD_MB, "  gateway %s", address_string);
+		nm_ip6_config_set_gateway (config, &address.address);
+	} 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[i]; i++) {
+		struct in6_addr addr;
+
+		if (inet_pton (AF_INET6, dns[i], &addr)) {
+			nm_ip6_config_add_nameserver (config, &addr);
+			nm_log_info (LOGD_MB, "  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 *_self, NMDeviceStateReason *reason)
+{
+	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+
+	/* We schedule it in an idle just to follow the same logic as in the
+	 * generic modem implementation. */
+	g_idle_add ((GSourceFunc) stage3_ip6_done, self);
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
@@ -720,11 +873,15 @@ simple_disconnect_ready (MMModemSimple *modem_iface,
 }
 
 static void
-disconnect (NMModem *self,
+disconnect (NMModem *modem,
             gboolean warn)
 {
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 	SimpleDisconnectContext *ctx;
 
+	if (!self->priv->simple_iface)
+		return;
+
 	ctx = g_slice_new (SimpleDisconnectContext);
 	ctx->self = g_object_ref (self);
 
@@ -806,6 +963,21 @@ modem_state_changed (MMModem *modem,
 
 /*****************************************************************************/
 
+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;
+}
+
 NMModem *
 nm_modem_broadband_new (GObject *object, GError **error)
 {
@@ -830,6 +1002,7 @@ nm_modem_broadband_new (GObject *object, GError **error)
 	                      NM_MODEM_UID, mm_modem_get_primary_port (modem_iface),
 	                      NM_MODEM_CONTROL_PORT, mm_modem_get_primary_port (modem_iface),
 	                      NM_MODEM_DATA_PORT, NULL, /* We don't know it until bearer created */
+	                      NM_MODEM_IP_TYPES, mm_ip_family_to_nm (mm_modem_get_supported_ip_families (modem_iface)),
 	                      NM_MODEM_STATE, 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,
@@ -967,6 +1140,7 @@ nm_modem_broadband_class_init (NMModemBroadbandClass *klass)
 
 	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 = deactivate;
 	modem_class->set_mm_enabled = set_mm_enabled;
@@ -979,9 +1153,8 @@ nm_modem_broadband_class_init (NMModemBroadbandClass *klass)
 	/* Properties */
 	g_object_class_install_property
 		(object_class, PROP_MODEM,
-		 g_param_spec_object (NM_MODEM_BROADBAND_MODEM,
-		                      "Modem",
-		                      "Broadband modem object",
+		 g_param_spec_object (NM_MODEM_BROADBAND_MODEM, "", "",
 		                      MM_GDBUS_TYPE_OBJECT,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+		                      G_PARAM_STATIC_STRINGS));
 }