aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@lanedo.com>2012-08-27 12:55:36 +0200
committerAleksander Morgado <aleksander@lanedo.com>2012-08-27 12:55:36 +0200
commite8e4f9f43f909888611ff9653638debafc763a6e (patch)
treefa546abf25f69217143bc484ee2979a1893482df
parent275e08ac7b8b76c64f4b0cb42ed586f2ce2c42bd (diff)
bearer-qmi: enable dual IPv4/IPv6 connections
We provide separate steps to connect/disconnect IPv4 and IPv6.
-rw-r--r--src/mm-bearer-qmi.c461
1 files changed, 326 insertions, 135 deletions
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c
index 56d36e93..8bf5fb89 100644
--- a/src/mm-bearer-qmi.c
+++ b/src/mm-bearer-qmi.c
@@ -35,7 +35,8 @@ struct _MMBearerQmiPrivate {
/* State kept while connected */
QmiClientWds *client;
MMPort *data;
- guint32 packet_data_handle;
+ guint32 packet_data_handle_ipv4;
+ guint32 packet_data_handle_ipv6;
};
/*****************************************************************************/
@@ -62,7 +63,8 @@ typedef enum {
CONNECT_STEP_FIRST,
CONNECT_STEP_OPEN_QMI_PORT,
CONNECT_STEP_WDS_CLIENT,
- CONNECT_STEP_START_NETWORK,
+ CONNECT_STEP_START_NETWORK_IPV4,
+ CONNECT_STEP_START_NETWORK_IPV6,
CONNECT_STEP_LAST
} ConnectStep;
@@ -74,19 +76,38 @@ typedef struct {
MMPort *data;
MMQmiPort *qmi;
QmiClientWds *client;
- guint32 packet_data_handle;
+ gchar *user;
+ gchar *password;
+ gchar *apn;
+
+ gboolean ipv4;
+ gboolean running_ipv4;
+ guint32 packet_data_handle_ipv4;
+ GError *error_ipv4;
+
+ gboolean ipv6;
+ gboolean running_ipv6;
+ guint32 packet_data_handle_ipv6;
+ GError *error_ipv6;
} ConnectContext;
static void
connect_context_complete_and_free (ConnectContext *ctx)
{
g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ g_free (ctx->apn);
+ g_free (ctx->user);
+ g_free (ctx->password);
+ if (ctx->error_ipv4)
+ g_error_free (ctx->error_ipv4);
+ if (ctx->error_ipv6)
+ g_error_free (ctx->error_ipv6);
if (ctx->client)
g_object_unref (ctx->client);
g_object_unref (ctx->data);
g_object_unref (ctx->qmi);
g_object_unref (ctx->cancellable);
- g_object_unref (ctx->result);
g_object_unref (ctx->self);
g_slice_free (ConnectContext, ctx);
}
@@ -122,14 +143,12 @@ start_network_ready (QmiClientWds *client,
GError *error = NULL;
QmiMessageWdsStartNetworkOutput *output;
- output = qmi_client_wds_start_network_finish (client, res, &error);
- if (!output) {
- g_simple_async_result_take_error (ctx->result, error);
- connect_context_complete_and_free (ctx);
- return;
- }
+ g_assert (ctx->running_ipv4 || ctx->running_ipv6);
+ g_assert (!(ctx->running_ipv4 && ctx->running_ipv6));
- if (!qmi_message_wds_start_network_output_get_result (output, &error)) {
+ output = qmi_client_wds_start_network_finish (client, res, &error);
+ if (output &&
+ !qmi_message_wds_start_network_output_get_result (output, &error)) {
/* No-effect errors should be ignored. The modem will keep the
* connection active as long as there is a WDS client which requested
* to start the network. If ModemManager crashed while a connection was
@@ -139,6 +158,7 @@ start_network_ready (QmiClientWds *client,
QMI_PROTOCOL_ERROR,
QMI_PROTOCOL_ERROR_NO_EFFECT)) {
g_error_free (error);
+ error = NULL;
/* Fall down to a successful connection */
} else {
@@ -165,16 +185,23 @@ start_network_ready (QmiClientWds *client,
verbose_cer_type,
verbose_cer_reason);
}
-
- g_simple_async_result_take_error (ctx->result, error);
- connect_context_complete_and_free (ctx);
- qmi_message_wds_start_network_output_unref (output);
- return;
}
}
- qmi_message_wds_start_network_output_get_packet_data_handle (output, &ctx->packet_data_handle, NULL);
- qmi_message_wds_start_network_output_unref (output);
+ if (error) {
+ if (ctx->running_ipv4)
+ ctx->error_ipv4 = error;
+ else
+ ctx->error_ipv6 = error;
+ } else {
+ if (ctx->running_ipv4)
+ qmi_message_wds_start_network_output_get_packet_data_handle (output, &ctx->packet_data_handle_ipv4, NULL);
+ else
+ qmi_message_wds_start_network_output_get_packet_data_handle (output, &ctx->packet_data_handle_ipv6, NULL);
+ }
+
+ if (output)
+ qmi_message_wds_start_network_output_unref (output);
/* Keep on */
ctx->step++;
@@ -182,50 +209,28 @@ start_network_ready (QmiClientWds *client,
}
static QmiMessageWdsStartNetworkInput *
-build_start_network_input (MMBearerQmi *self)
+build_start_network_input (ConnectContext *ctx)
{
QmiMessageWdsStartNetworkInput *input;
- MMBearerProperties *properties = NULL;
- const gchar *str;
- QmiWdsIpFamily ip_type;
- g_object_get (self,
- MM_BEARER_CONFIG, &properties,
- NULL);
-
- if (!properties)
- return NULL;
+ g_assert (ctx->running_ipv4 || ctx->running_ipv6);
+ g_assert (!(ctx->running_ipv4 && ctx->running_ipv6));
input = qmi_message_wds_start_network_input_new ();
- str = mm_bearer_properties_get_apn (properties);
- if (str)
- qmi_message_wds_start_network_input_set_apn (input, str, NULL);
-
- str = mm_bearer_properties_get_user (properties);
- if (str)
- qmi_message_wds_start_network_input_set_username (input, str, NULL);
-
- str = mm_bearer_properties_get_password (properties);
- if (str)
- qmi_message_wds_start_network_input_set_password (input, str, NULL);
-
- switch (mm_bearer_properties_get_ip_type (properties)) {
- case MM_BEARER_IP_FAMILY_IPV4:
- ip_type = QMI_WDS_IP_FAMILY_IPV4;
- break;
- case MM_BEARER_IP_FAMILY_IPV6:
- ip_type = QMI_WDS_IP_FAMILY_IPV6;
- break;
- case MM_BEARER_IP_FAMILY_IPV4V6:
- /* dual stack, we assume unspecified */
- case MM_BEARER_IP_FAMILY_UNKNOWN:
- default:
- ip_type = QMI_WDS_IP_FAMILY_UNSPECIFIED;
- break;
- }
+ if (ctx->apn)
+ qmi_message_wds_start_network_input_set_apn (input, ctx->apn, NULL);
+
+ if (ctx->user)
+ qmi_message_wds_start_network_input_set_username (input, ctx->user, NULL);
- qmi_message_wds_start_network_input_set_ip_family_preference (input, ip_type, NULL);
+ if (ctx->password)
+ qmi_message_wds_start_network_input_set_password (input, ctx->password, NULL);
+
+ qmi_message_wds_start_network_input_set_ip_family_preference (
+ input,
+ (ctx->running_ipv6 ? QMI_WDS_IP_FAMILY_IPV6 : QMI_WDS_IP_FAMILY_IPV4),
+ NULL);
return input;
}
@@ -283,6 +288,9 @@ connect_context_step (ConnectContext *ctx)
switch (ctx->step) {
case CONNECT_STEP_FIRST:
+
+ g_assert (ctx->ipv4 || ctx->ipv6);
+
/* Fall down */
ctx->step++;
@@ -314,52 +322,102 @@ connect_context_step (ConnectContext *ctx)
ctx->client = QMI_CLIENT_WDS (client);
}
- case CONNECT_STEP_START_NETWORK: {
- QmiMessageWdsStartNetworkInput *input;
-
- input = build_start_network_input (ctx->self);
- qmi_client_wds_start_network (ctx->client,
- input,
- 10,
- ctx->cancellable,
- (GAsyncReadyCallback)start_network_ready,
- ctx);
- if (input)
- qmi_message_wds_start_network_input_unref (input);
- return;
- }
+ case CONNECT_STEP_START_NETWORK_IPV4:
+ if (ctx->ipv4) {
+ QmiMessageWdsStartNetworkInput *input;
+
+ ctx->running_ipv4 = TRUE;
+ ctx->running_ipv6 = FALSE;
+ input = build_start_network_input (ctx);
+ qmi_client_wds_start_network (ctx->client,
+ input,
+ 10,
+ ctx->cancellable,
+ (GAsyncReadyCallback)start_network_ready,
+ ctx);
+ if (input)
+ qmi_message_wds_start_network_input_unref (input);
+ return;
+ }
+
+ /* No IPv4 setup needed, fall down */
+ ctx->step++;
+
+ case CONNECT_STEP_START_NETWORK_IPV6:
+ if (ctx->ipv6) {
+ QmiMessageWdsStartNetworkInput *input;
+
+ ctx->running_ipv4 = FALSE;
+ ctx->running_ipv6 = TRUE;
+ input = build_start_network_input (ctx);
+ qmi_client_wds_start_network (ctx->client,
+ input,
+ 10,
+ ctx->cancellable,
+ (GAsyncReadyCallback)start_network_ready,
+ ctx);
+ if (input)
+ qmi_message_wds_start_network_input_unref (input);
+ return;
+ }
+
+ /* No IPv6 setup needed, fall down */
+ ctx->step++;
+
+ case CONNECT_STEP_LAST:
+ /* If one of IPv4 or IPv6 succeeds, we're connected */
+ if (ctx->packet_data_handle_ipv4 || ctx->packet_data_handle_ipv6) {
+ MMBearerIpConfig *config;
+ ConnectResult *result;
+
+ /* Port is connected; update the state */
+ mm_port_set_connected (MM_PORT (ctx->data), TRUE);
+
+ /* Keep connection related data */
+ g_assert (ctx->self->priv->data == NULL);
+ ctx->self->priv->data = g_object_ref (ctx->data);
+ g_assert (ctx->self->priv->client == NULL);
+ ctx->self->priv->client = g_object_ref (ctx->client);
+ g_assert (ctx->self->priv->packet_data_handle_ipv4 == 0);
+ ctx->self->priv->packet_data_handle_ipv4 = ctx->packet_data_handle_ipv4;
+ g_assert (ctx->self->priv->packet_data_handle_ipv6 == 0);
+ ctx->self->priv->packet_data_handle_ipv6 = ctx->packet_data_handle_ipv6;
+
+ /* Build IP config; always DHCP based */
+ config = mm_bearer_ip_config_new ();
+ mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP);
+
+ /* Build result */
+ result = g_slice_new0 (ConnectResult);
+ result->data = g_object_ref (ctx->data);
+ if (ctx->packet_data_handle_ipv4)
+ result->ipv4_config = g_object_ref (config);
+ if (ctx->packet_data_handle_ipv6)
+ result->ipv6_config = g_object_ref (config);
+
+ g_object_unref (config);
+
+ /* Set operation result */
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ result,
+ (GDestroyNotify)connect_result_free);
+ } else {
+ GError *error;
+
+ /* No connection, set error. If both set, IPv4 error preferred */
+ if (ctx->error_ipv4) {
+ error = ctx->error_ipv4;
+ ctx->error_ipv4 = NULL;
+ } else {
+ error = ctx->error_ipv6;
+ ctx->error_ipv6 = NULL;
+ }
+
+ g_simple_async_result_take_error (ctx->result, error);
+ }
- case CONNECT_STEP_LAST: {
- MMBearerIpConfig *config;
- ConnectResult *result;
-
- /* Port is connected; update the state */
- mm_port_set_connected (MM_PORT (ctx->data), TRUE);
-
- /* Keep connection related data */
- g_assert (ctx->self->priv->data == NULL);
- ctx->self->priv->data = g_object_ref (ctx->data);
- g_assert (ctx->self->priv->client == NULL);
- ctx->self->priv->client = g_object_ref (ctx->client);
- g_assert (ctx->self->priv->packet_data_handle == 0);
- ctx->self->priv->packet_data_handle = ctx->packet_data_handle;
-
- /* Build IP config; always DHCP based */
- config = mm_bearer_ip_config_new ();
- mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP);
-
- /* Build result */
- result = g_slice_new0 (ConnectResult);
- result->data = g_object_ref (ctx->data);
- result->ipv4_config = config;
- result->ipv6_config = g_object_ref (config);
-
- /* Set operation result */
- g_simple_async_result_set_op_res_gpointer (ctx->result,
- result,
- (GDestroyNotify)connect_result_free);
connect_context_complete_and_free (ctx);
- }
+ return;
}
}
@@ -377,6 +435,7 @@ connect (MMBearer *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
+ MMBearerProperties *properties = NULL;
ConnectContext *ctx;
MMBaseModem *modem = NULL;
MMPort *data;
@@ -424,7 +483,6 @@ connect (MMBearer *self,
mm_port_subsys_get_string (mm_port_get_subsys (data)),
mm_port_get_device (data));
- /* In this context, we only keep the stuff we'll need later */
ctx = g_slice_new0 (ConnectContext);
ctx->self = g_object_ref (self);
ctx->qmi = qmi;
@@ -436,6 +494,38 @@ connect (MMBearer *self,
user_data,
connect);
+ g_object_get (self,
+ MM_BEARER_CONFIG, &properties,
+ NULL);
+
+ if (properties) {
+ ctx->apn = g_strdup (mm_bearer_properties_get_apn (properties));
+ ctx->user = g_strdup (mm_bearer_properties_get_user (properties));
+ ctx->password = g_strdup (mm_bearer_properties_get_password (properties));
+ switch (mm_bearer_properties_get_ip_type (properties)) {
+ case MM_BEARER_IP_FAMILY_IPV4:
+ ctx->ipv4 = TRUE;
+ ctx->ipv6 = FALSE;
+ break;
+ case MM_BEARER_IP_FAMILY_IPV6:
+ ctx->ipv4 = FALSE;
+ ctx->ipv6 = TRUE;
+ break;
+ case MM_BEARER_IP_FAMILY_IPV4V6:
+ ctx->ipv4 = TRUE;
+ ctx->ipv6 = TRUE;
+ break;
+ case MM_BEARER_IP_FAMILY_UNKNOWN:
+ default:
+ mm_dbg ("No specific IP family requested, defaulting to IPv4");
+ ctx->ipv4 = TRUE;
+ ctx->ipv6 = FALSE;
+ break;
+ }
+
+ g_object_unref (properties);
+ }
+
/* Run! */
connect_context_step (ctx);
g_object_unref (modem);
@@ -444,12 +534,27 @@ connect (MMBearer *self,
/*****************************************************************************/
/* Disconnect */
+typedef enum {
+ DISCONNECT_STEP_FIRST,
+ DISCONNECT_STEP_STOP_NETWORK_IPV4,
+ DISCONNECT_STEP_STOP_NETWORK_IPV6,
+ DISCONNECT_STEP_LAST
+} DisconnectStep;
+
typedef struct {
MMBearerQmi *self;
+ GSimpleAsyncResult *result;
QmiClientWds *client;
MMPort *data;
- guint32 packet_data_handle;
- GSimpleAsyncResult *result;
+ DisconnectStep step;
+
+ gboolean running_ipv4;
+ guint32 packet_data_handle_ipv4;
+ GError *error_ipv4;
+
+ gboolean running_ipv6;
+ guint32 packet_data_handle_ipv6;
+ GError *error_ipv6;
} DisconnectContext;
static void
@@ -457,6 +562,10 @@ disconnect_context_complete_and_free (DisconnectContext *ctx)
{
g_simple_async_result_complete_in_idle (ctx->result);
g_object_unref (ctx->result);
+ if (ctx->error_ipv4)
+ g_error_free (ctx->error_ipv4);
+ if (ctx->error_ipv6)
+ g_error_free (ctx->error_ipv6);
g_object_unref (ctx->client);
g_object_unref (ctx->data);
g_object_unref (ctx->self);
@@ -472,18 +581,30 @@ disconnect_finish (MMBearer *self,
}
static void
-reset_bearer_connection (MMBearerQmi *self)
+reset_bearer_connection (MMBearerQmi *self,
+ gboolean reset_ipv4,
+ gboolean reset_ipv6)
{
- if (self->priv->data) {
- /* Port is disconnected; update the state */
- mm_port_set_connected (self->priv->data, FALSE);
- g_clear_object (&self->priv->data);
- }
+ if (reset_ipv4)
+ self->priv->packet_data_handle_ipv4 = 0;
+
+ if (reset_ipv6)
+ self->priv->packet_data_handle_ipv6 = 0;
+
+ if (!self->priv->packet_data_handle_ipv4 &&
+ !self->priv->packet_data_handle_ipv6) {
+ if (self->priv->data) {
+ /* Port is disconnected; update the state */
+ mm_port_set_connected (self->priv->data, FALSE);
+ g_clear_object (&self->priv->data);
+ }
- g_clear_object (&self->priv->client);
- self->priv->packet_data_handle = 0;
+ g_clear_object (&self->priv->client);
+ }
}
+static void disconnect_context_step (DisconnectContext *ctx);
+
static void
stop_network_ready (QmiClientWds *client,
GAsyncResult *res,
@@ -493,25 +614,100 @@ stop_network_ready (QmiClientWds *client,
QmiMessageWdsStopNetworkOutput *output;
output = qmi_client_wds_stop_network_finish (client, res, &error);
- if (!output) {
- g_simple_async_result_take_error (ctx->result, error);
- disconnect_context_complete_and_free (ctx);
- return;
+ if (output)
+ qmi_message_wds_stop_network_output_get_result (output, &error);
+
+ if (error) {
+ if (ctx->running_ipv4)
+ ctx->error_ipv4 = error;
+ else
+ ctx->error_ipv6 = error;
+ } else {
+ /* Clear internal status */
+ reset_bearer_connection (ctx->self,
+ ctx->running_ipv4,
+ ctx->running_ipv6);
}
- if (!qmi_message_wds_stop_network_output_get_result (output, &error)) {
+ if (output)
qmi_message_wds_stop_network_output_unref (output);
- g_simple_async_result_take_error (ctx->result, error);
+
+ /* Keep on */
+ ctx->step++;
+ disconnect_context_step (ctx);
+}
+
+static void
+disconnect_context_step (DisconnectContext *ctx)
+{
+ switch (ctx->step) {
+ case DISCONNECT_STEP_FIRST:
+ /* Fall down */
+ ctx->step++;
+
+ case DISCONNECT_STEP_STOP_NETWORK_IPV4:
+ if (ctx->packet_data_handle_ipv4) {
+ QmiMessageWdsStopNetworkInput *input;
+
+ input = qmi_message_wds_stop_network_input_new ();
+ qmi_message_wds_stop_network_input_set_packet_data_handle (input, ctx->packet_data_handle_ipv4, NULL);
+
+ ctx->running_ipv4 = TRUE;
+ ctx->running_ipv6 = FALSE;
+ qmi_client_wds_stop_network (ctx->client,
+ input,
+ 10,
+ NULL,
+ (GAsyncReadyCallback)stop_network_ready,
+ ctx);
+ return;
+ }
+
+ /* Fall down */
+ ctx->step++;
+
+ case DISCONNECT_STEP_STOP_NETWORK_IPV6:
+ if (ctx->packet_data_handle_ipv6) {
+ QmiMessageWdsStopNetworkInput *input;
+
+ input = qmi_message_wds_stop_network_input_new ();
+ qmi_message_wds_stop_network_input_set_packet_data_handle (input, ctx->packet_data_handle_ipv6, NULL);
+
+ ctx->running_ipv4 = FALSE;
+ ctx->running_ipv6 = TRUE;
+ qmi_client_wds_stop_network (ctx->client,
+ input,
+ 10,
+ NULL,
+ (GAsyncReadyCallback)stop_network_ready,
+ ctx);
+ return;
+ }
+
+ /* Fall down */
+ ctx->step++;
+
+ case DISCONNECT_STEP_LAST:
+ if (!ctx->error_ipv4 && !ctx->error_ipv6)
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ else {
+ GError *error;
+
+ /* If both set, IPv4 error preferred */
+ if (ctx->error_ipv4) {
+ error = ctx->error_ipv4;
+ ctx->error_ipv4 = NULL;
+ } else {
+ error = ctx->error_ipv6;
+ ctx->error_ipv6 = NULL;
+ }
+
+ g_simple_async_result_take_error (ctx->result, error);
+ }
+
disconnect_context_complete_and_free (ctx);
return;
}
-
- /* Clear internal status */
- reset_bearer_connection (ctx->self);
-
- qmi_message_wds_stop_network_output_unref (output);
- g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
- disconnect_context_complete_and_free (ctx);
}
static void
@@ -520,10 +716,9 @@ disconnect (MMBearer *_self,
gpointer user_data)
{
MMBearerQmi *self = MM_BEARER_QMI (_self);
- QmiMessageWdsStopNetworkInput *input;
DisconnectContext *ctx;
- if (!self->priv->packet_data_handle ||
+ if ((!self->priv->packet_data_handle_ipv4 && !self->priv->packet_data_handle_ipv6) ||
!self->priv->client ||
!self->priv->data) {
g_simple_async_report_error_in_idle (
@@ -540,20 +735,16 @@ disconnect (MMBearer *_self,
ctx->self = g_object_ref (self);
ctx->data = g_object_ref (self->priv->data);
ctx->client = g_object_ref (self->priv->client);
- ctx->packet_data_handle = self->priv->packet_data_handle;
+ ctx->packet_data_handle_ipv4 = self->priv->packet_data_handle_ipv4;
+ ctx->packet_data_handle_ipv6 = self->priv->packet_data_handle_ipv6;
ctx->result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
disconnect);
+ ctx->step = DISCONNECT_STEP_FIRST;
- input = qmi_message_wds_stop_network_input_new ();
- qmi_message_wds_stop_network_input_set_packet_data_handle (input, ctx->packet_data_handle, NULL);
- qmi_client_wds_stop_network (ctx->client,
- input,
- 10,
- NULL,
- (GAsyncReadyCallback)stop_network_ready,
- ctx);
+ /* Run! */
+ disconnect_context_step (ctx);
}
/*****************************************************************************/
@@ -562,7 +753,7 @@ static void
report_disconnection (MMBearer *self)
{
/* Cleanup all connection related data */
- reset_bearer_connection (MM_BEARER_QMI (self));
+ reset_bearer_connection (MM_BEARER_QMI (self), TRUE, TRUE);
/* Chain up parent's report_disconection() */
MM_BEARER_CLASS (mm_bearer_qmi_parent_class)->report_disconnection (self);