diff options
author | Aleksander Morgado <aleksander@aleksander.es> | 2021-02-23 20:43:38 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2021-03-10 12:58:12 +0100 |
commit | 1643c3d3db73a2e731e92efa26466c1501c8899d (patch) | |
tree | c8ad104ca89e6687c8dc23f0404add8f8caf44db /src/mm-bearer-qmi.c | |
parent | db5cb302c9de6f551f77ec3d7665c8be61ae1da2 (diff) |
bearer-qmi: create data port link if multiplex is requested/required
If the user requests or requires to use multiplexing support, try to
create a data port link and bind the WDS client(s) to it.
If the multiplex is requested but not required, we allow link creation
failures or setups where multiplexing isn't supported; we just go on
with the default connection setup without multiplexing, as long as
there is no bearer connected on the same data network interface.
If the multiplex is required and for any reason it cannot be fully
setup, we will fail the connection attempt right away.
The data port link is created in the system via the QmiDevice in the
open MMPortQmi, but it MUST be notified by the kernel (e.g. via udev
or via kernel events) before the connection attempt goes on.
Diffstat (limited to 'src/mm-bearer-qmi.c')
-rw-r--r-- | src/mm-bearer-qmi.c | 324 |
1 files changed, 285 insertions, 39 deletions
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c index 40307a51..ab3059fe 100644 --- a/src/mm-bearer-qmi.c +++ b/src/mm-bearer-qmi.c @@ -56,6 +56,8 @@ struct _MMBearerQmiPrivate { guint event_report_ipv6_indication_id; MMPort *data; + MMPort *link; + guint mux_id; guint32 packet_data_handle_ipv4; guint32 packet_data_handle_ipv6; }; @@ -313,10 +315,15 @@ connection_status_context_step (GTask *task) switch (ctx->step) { case CONNECTION_STATUS_CONTEXT_STEP_FIRST: /* Connection status polling is an optional feature that must be - * enabled explicitly via udev tags. If not set, out as unsupported */ - if (self->priv->data && - !mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (self->priv->data), - "ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE")) { + * enabled explicitly via udev tags. If not set, out as unsupported. + * Note that when connected via a muxed link, the udev tag should be + * checked on the master interface (lower device) */ + if ((self->priv->data && + !mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (self->priv->data), + "ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE")) || + (self->priv->link && + !mm_kernel_device_get_global_property_as_boolean (mm_kernel_device_peek_lower_device (mm_port_peek_kernel_device (self->priv->link)), + "ID_MM_QMI_CONNECTION_STATUS_POLLING_ENABLE"))) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Connection status polling not required"); g_object_unref (task); @@ -388,6 +395,8 @@ load_connection_status (MMBaseBearer *self, /*****************************************************************************/ /* Connect */ +#define WAIT_LINK_PORT_TIMEOUT_MS 2500 + static void common_setup_cleanup_packet_service_status_unsolicited_events (MMBearerQmi *self, QmiClientWds *client, gboolean enable, @@ -407,6 +416,7 @@ typedef enum { CONNECT_STEP_FIRST, CONNECT_STEP_OPEN_QMI_PORT, CONNECT_STEP_SETUP_DATA_FORMAT, + CONNECT_STEP_SETUP_LINK, CONNECT_STEP_IP_METHOD, CONNECT_STEP_IPV4, CONNECT_STEP_WDS_CLIENT_IPV4, @@ -431,6 +441,7 @@ typedef struct { MMPort *data; MMPortQmi *qmi; QmiSioPort sio_port; + MMBearerMultiplexSupport multiplex; gboolean explicit_qmi_open; gchar *user; gchar *password; @@ -438,6 +449,12 @@ typedef struct { QmiWdsAuthentication auth; gboolean no_ip_family_preference; + QmiWdaDataAggregationProtocol dap; + guint mux_id; + gchar *link_prefix_hint; + gchar *link_name; + MMPort *link; + MMBearerIpMethod ip_method; gboolean ipv4; @@ -510,6 +527,13 @@ connect_context_free (ConnectContext *ctx) g_clear_object (&ctx->client_ipv6); } + if (ctx->link_name) { + mm_port_qmi_cleanup_link (ctx->qmi, ctx->link_name, ctx->mux_id, NULL, NULL); + g_free (ctx->link_name); + } + g_clear_object (&ctx->link); + g_free (ctx->link_prefix_hint); + if (ctx->explicit_qmi_open) mm_port_qmi_close (ctx->qmi, NULL, NULL); @@ -517,7 +541,8 @@ connect_context_free (ConnectContext *ctx) g_clear_error (&ctx->error_ipv6); g_clear_object (&ctx->ipv4_config); g_clear_object (&ctx->ipv6_config); - g_object_unref (ctx->data); + + g_clear_object (&ctx->data); g_object_unref (ctx->qmi); g_object_unref (ctx->self); g_slice_free (ConnectContext, ctx); @@ -1019,6 +1044,32 @@ bind_data_port_ready (QmiClientWds *client, } static void +bind_mux_data_port_ready (QmiClientWds *client, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + GError *error = NULL; + g_autoptr(QmiMessageWdsBindMuxDataPortOutput) output = NULL; + + ctx = g_task_get_task_data (task); + + g_assert (ctx->running_ipv4 || ctx->running_ipv6); + g_assert (!(ctx->running_ipv4 && ctx->running_ipv6)); + + output = qmi_client_wds_bind_mux_data_port_finish (client, res, &error); + if (!output || !qmi_message_wds_bind_mux_data_port_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't bind mux data port: "); + complete_connect (task, NULL, error); + return; + } + + /* Keep on */ + ctx->step++; + connect_context_step (task); +} + +static void set_ip_family_ready (QmiClientWds *client, GAsyncResult *res, GTask *task) @@ -1285,6 +1336,64 @@ qmi_port_allocate_client_ready (MMPortQmi *qmi, } static void +wait_link_port_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + ctx->link = mm_base_modem_wait_link_port_finish (modem, res, &error); + if (!ctx->link) { + complete_connect (task, NULL, error); + return; + } + + /* Keep on */ + ctx->step++; + connect_context_step (task); +} + +static void +setup_link_ready (MMPortQmi *qmi, + GAsyncResult *res, + GTask *task) +{ + ConnectContext *ctx; + GError *error = NULL; + g_autoptr(MMBaseModem) modem = NULL; + + ctx = g_task_get_task_data (task); + + ctx->link_name = mm_port_qmi_setup_link_finish (qmi, res, &ctx->mux_id, &error); + if (!ctx->link_name) { + g_prefix_error (&error, "failed to create net link for device: "); + complete_connect (task, NULL, error); + return; + } + + /* From now on link_name will be set, and we'll use that to know + * whether we should cleanup the link upon a connection failure */ + mm_obj_info (ctx->self, "net link %s created (mux id %u)", ctx->link_name, ctx->mux_id); + + /* Wait for the data port with the given interface name, which will be + * added asynchronously */ + g_object_get (ctx->self, + MM_BASE_BEARER_MODEM, &modem, + NULL); + g_assert (modem); + + mm_base_modem_wait_link_port (modem, + "net", + ctx->link_name, + WAIT_LINK_PORT_TIMEOUT_MS, + (GAsyncReadyCallback) wait_link_port_ready, + task); +} + +static void setup_data_format_ready (MMPortQmi *qmi, GAsyncResult *res, GTask *task) @@ -1296,11 +1405,30 @@ setup_data_format_ready (MMPortQmi *qmi, if (!mm_port_qmi_setup_data_format_finish (qmi, res, &error)) { /* a failure here could indicate no support for WDA Set Data Format, - * if so, just go on with the plain CTL based support */ + * if so, just go on with the plain CTL based support (and data aggregation + * protocol disabled) */ if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) { complete_connect (task, NULL, g_steal_pointer (&error)); return; } + ctx->dap = QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED; + } else + ctx->dap = mm_port_qmi_get_data_aggregation_protocol (ctx->qmi); + + if ((ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) && + (ctx->dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED)) { + complete_connect (task, NULL, + g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot enable multiplex support")); + return; + } + + if ((ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_NONE) && + (ctx->dap != QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED)) { + complete_connect (task, NULL, + g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Cannot disable multiplex support")); + return; } /* Keep on */ @@ -1384,13 +1512,44 @@ connect_context_step (GTask *task) ctx->step++; /* fall through */ - case CONNECT_STEP_SETUP_DATA_FORMAT: + case CONNECT_STEP_SETUP_DATA_FORMAT: { + MMPortQmiSetupDataFormatAction action; + + switch (ctx->multiplex) { + case MM_BEARER_MULTIPLEX_SUPPORT_NONE: + action = MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_DEFAULT; + break; + case MM_BEARER_MULTIPLEX_SUPPORT_REQUESTED: + case MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED: + action = MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_MULTIPLEX; + break; + case MM_BEARER_MULTIPLEX_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + } mm_port_qmi_setup_data_format (ctx->qmi, ctx->data, - MM_PORT_QMI_SETUP_DATA_FORMAT_ACTION_SET_DEFAULT, + action, (GAsyncReadyCallback) setup_data_format_ready, task); return; + } + + case CONNECT_STEP_SETUP_LINK: + + /* if muxing has been enabled in the port, we need to create a new link + * interface. */ + if (ctx->dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAPV5 || + ctx->dap == QMI_WDA_DATA_AGGREGATION_PROTOCOL_QMAP) { + mm_port_qmi_setup_link (ctx->qmi, + ctx->data, + ctx->link_prefix_hint, + (GAsyncReadyCallback) setup_link_ready, + task); + return; + } + ctx->step++; + /* fall through */ case CONNECT_STEP_IP_METHOD: /* Once the QMI port is open, we decide the IP method we're going @@ -1480,6 +1639,28 @@ connect_context_step (GTask *task) return; } + /* If mux id given, bind mux data port */ + if (ctx->mux_id != QMI_DEVICE_MUX_ID_UNBOUND) { + g_autoptr(QmiMessageWdsBindMuxDataPortInput) input = NULL; + + mm_obj_dbg (self, "binding to mux id %d", ctx->mux_id); + input = qmi_message_wds_bind_mux_data_port_input_new (); + qmi_message_wds_bind_mux_data_port_input_set_endpoint_info ( + input, + mm_port_qmi_get_endpoint_type (ctx->qmi), + mm_port_qmi_get_endpoint_interface_number (ctx->qmi), + NULL); + qmi_message_wds_bind_mux_data_port_input_set_mux_id (input, ctx->mux_id, NULL); + + qmi_client_wds_bind_mux_data_port (ctx->client_ipv4, + input, + 10, + g_task_get_cancellable (task), + (GAsyncReadyCallback)bind_mux_data_port_ready, + task); + return; + } + ctx->step++; /* fall through */ @@ -1591,6 +1772,28 @@ connect_context_step (GTask *task) return; } + /* If mux id given, bind mux data port */ + if (ctx->mux_id != QMI_DEVICE_MUX_ID_UNBOUND) { + g_autoptr(QmiMessageWdsBindMuxDataPortInput) input = NULL; + + mm_obj_dbg (self, "binding to mux id %d", ctx->mux_id); + input = qmi_message_wds_bind_mux_data_port_input_new (); + qmi_message_wds_bind_mux_data_port_input_set_endpoint_info ( + input, + mm_port_qmi_get_endpoint_type (ctx->qmi), + mm_port_qmi_get_endpoint_interface_number (ctx->qmi), + NULL); + qmi_message_wds_bind_mux_data_port_input_set_mux_id (input, ctx->mux_id, NULL); + + qmi_client_wds_bind_mux_data_port (ctx->client_ipv6, + input, + 10, + g_task_get_cancellable (task), + (GAsyncReadyCallback)bind_mux_data_port_ready, + task); + return; + } + ctx->step++; /* fall through */ @@ -1631,7 +1834,9 @@ connect_context_step (GTask *task) ctx->step++; /* fall through */ - case CONNECT_STEP_LAST: + case CONNECT_STEP_LAST: { + MMBearerConnectResult *connect_result; + /* If one of IPv4 or IPv6 succeeds, we're connected */ if (!ctx->packet_data_handle_ipv4 && !ctx->packet_data_handle_ipv6) { GError *error; @@ -1650,7 +1855,7 @@ connect_context_step (GTask *task) } /* Port is connected; update the state */ - mm_port_set_connected (MM_PORT (ctx->data), TRUE); + mm_port_set_connected (ctx->link ? ctx->link : ctx->data, TRUE); /* Keep connection related data */ @@ -1662,7 +1867,14 @@ connect_context_step (GTask *task) } g_assert (ctx->self->priv->data == NULL); - ctx->self->priv->data = g_object_ref (ctx->data); + ctx->self->priv->data = ctx->data ? g_object_ref (ctx->data) : NULL; + g_assert (ctx->self->priv->link == NULL); + ctx->self->priv->link = ctx->link ? g_object_ref (ctx->link) : NULL; + g_assert (ctx->self->priv->mux_id == QMI_DEVICE_MUX_ID_UNBOUND); + ctx->self->priv->mux_id = ctx->mux_id; + + /* reset the link name to avoid cleaning up the link on context free */ + g_clear_pointer (&ctx->link_name, g_free); g_assert (ctx->self->priv->packet_data_handle_ipv4 == 0); g_assert (ctx->self->priv->client_ipv4 == NULL); @@ -1688,12 +1900,14 @@ connect_context_step (GTask *task) ctx->self->priv->client_ipv6 = g_object_ref (ctx->client_ipv6); } - complete_connect (task, - mm_bearer_connect_result_new (ctx->data, - ctx->ipv4_config, - ctx->ipv6_config), - NULL); + connect_result = mm_bearer_connect_result_new (ctx->link ? ctx->link : ctx->data, + ctx->ipv4_config, + ctx->ipv6_config); + mm_bearer_connect_result_set_multiplexed (connect_result, !!ctx->link); + + complete_connect (task, connect_result, NULL); return; + } default: g_assert_not_reached (); @@ -1725,14 +1939,6 @@ _connect (MMBaseBearer *_self, GTask *task; GCancellable *operation_cancellable = NULL; - if (mm_bearer_properties_get_multiplex (mm_base_bearer_peek_config (_self)) == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) { - g_task_report_new_error ( - self, callback, user_data, _connect, - MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, - "Multiplex support not available"); - goto out; - } - g_object_get (self, MM_BASE_BEARER_MODEM, &modem, NULL); @@ -1793,18 +1999,19 @@ _connect (MMBaseBearer *_self, goto out; } - mm_obj_dbg (self, "launching connection with QMI port (%s) and data port (%s)", - mm_port_get_device (MM_PORT (qmi)), - mm_port_get_device (data)); - ctx = g_slice_new0 (ConnectContext); ctx->self = g_object_ref (self); ctx->qmi = g_object_ref (qmi); ctx->sio_port = sio_port; + ctx->dap = mm_port_qmi_get_data_aggregation_protocol (ctx->qmi); + ctx->mux_id = QMI_DEVICE_MUX_ID_UNBOUND; ctx->data = g_object_ref (data); ctx->step = CONNECT_STEP_FIRST; ctx->ip_method = MM_BEARER_IP_METHOD_UNKNOWN; + /* If no multiplex setting given by the user, assume none */ + ctx->multiplex = MM_BEARER_MULTIPLEX_SUPPORT_NONE; + g_object_get (self, MM_BASE_BEARER_CONFIG, &properties, NULL); @@ -1815,8 +2022,9 @@ _connect (MMBaseBearer *_self, g_task_set_task_data (task, ctx, (GDestroyNotify)connect_context_free); if (properties) { - MMBearerAllowedAuth auth; - MMBearerIpFamily ip_family; + MMBearerAllowedAuth auth; + MMBearerIpFamily ip_family; + MMBearerMultiplexSupport multiplex; ctx->apn = g_strdup (mm_bearer_properties_get_apn (properties)); ctx->user = g_strdup (mm_bearer_properties_get_user (properties)); @@ -1883,6 +2091,16 @@ _connect (MMBaseBearer *_self, g_free (str); goto out; } + + multiplex = mm_bearer_properties_get_multiplex (properties); + if (multiplex != MM_BEARER_MULTIPLEX_SUPPORT_UNKNOWN) + ctx->multiplex = multiplex; + } + + if (ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUESTED || + ctx->multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) { + /* the link prefix hint given must be modem-specific */ + ctx->link_prefix_hint = g_strdup_printf ("qmapmux%u.", mm_base_modem_get_dbus_id (MM_BASE_MODEM (modem))); } /* setup network cancellable */ @@ -1902,6 +2120,10 @@ _connect (MMBaseBearer *_self, g_object_unref); /* Run! */ + mm_obj_dbg (self, "launching connection with QMI port (%s) and data port (%s) (multiplex %s)", + mm_port_get_device (MM_PORT (qmi)), + mm_port_get_device (data), + mm_bearer_multiplex_support_get_string (ctx->multiplex)); connect_context_step (task); out: @@ -1958,6 +2180,18 @@ disconnect_finish (MMBaseBearer *self, } static void +cleanup_link_ready (MMPortQmi *qmi, + GAsyncResult *res, + MMBearerQmi *self) /* full reference */ +{ + g_autoptr(GError) error = NULL; + + if (!mm_port_qmi_cleanup_link_finish (qmi, res, &error)) + mm_obj_warn (self, "couldn't cleanup link: %s", error->message); + g_object_unref (self); +} + +static void reset_bearer_connection (MMBearerQmi *self, gboolean reset_ipv4, gboolean reset_ipv6) @@ -1995,6 +2229,24 @@ reset_bearer_connection (MMBearerQmi *self, } 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); + } + if (self->priv->link) { + g_assert (self->priv->qmi); + /* Link is disconnected; update the state */ + mm_port_set_connected (self->priv->link, FALSE); + mm_port_qmi_cleanup_link (self->priv->qmi, + mm_port_get_device (self->priv->link), + self->priv->mux_id, + (GAsyncReadyCallback) cleanup_link_ready, + g_object_ref (self)); + g_clear_object (&self->priv->link); + } + self->priv->mux_id = QMI_DEVICE_MUX_ID_UNBOUND; + /* Close port if we had it explicitly open for this connection */ if (self->priv->qmi) { if (self->priv->explicit_qmi_open) { @@ -2003,11 +2255,6 @@ reset_bearer_connection (MMBearerQmi *self, } g_clear_object (&self->priv->qmi); } - if (self->priv->data) { - /* Port is disconnected; update the state */ - mm_port_set_connected (self->priv->data, FALSE); - g_clear_object (&self->priv->data); - } } } @@ -2173,7 +2420,7 @@ disconnect (MMBaseBearer *_self, if ((!self->priv->packet_data_handle_ipv4 && !self->priv->packet_data_handle_ipv6) || (!self->priv->client_ipv4 && !self->priv->client_ipv6) || - !self->priv->data || + (!self->priv->data && !self->priv->link) || !self->priv->qmi) { mm_obj_dbg (self, "no need to disconnect: QMI bearer is already disconnected"); g_task_return_boolean (task, TRUE); @@ -2239,9 +2486,8 @@ static void mm_bearer_qmi_init (MMBearerQmi *self) { /* Initialize private data */ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - MM_TYPE_BEARER_QMI, - MMBearerQmiPrivate); + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BEARER_QMI, MMBearerQmiPrivate); + self->priv->mux_id = QMI_DEVICE_MUX_ID_UNBOUND; } static void |