| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details: |
| * |
| * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org> |
| */ |
| |
| #include <config.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| |
| #include <ModemManager.h> |
| #define _LIBMM_INSIDE_MM |
| #include <libmm-glib.h> |
| |
| #include "mm-iface-modem.h" |
| #include "mm-iface-modem-3gpp-profile-manager.h" |
| #include "mm-modem-helpers-mbim.h" |
| #include "mm-port-enums-types.h" |
| #include "mm-bearer-mbim.h" |
| #include "mm-log-object.h" |
| #include "mm-context.h" |
| |
| G_DEFINE_TYPE (MMBearerMbim, mm_bearer_mbim, MM_TYPE_BASE_BEARER) |
| |
| struct _MMBearerMbimPrivate { |
| MMPortMbim *mbim; |
| MMPort *data; |
| MMPort *link; |
| guint32 session_id; |
| gboolean remove_filters; |
| }; |
| |
| /*****************************************************************************/ |
| |
| static gboolean |
| peek_ports (gpointer self, |
| MMPortMbim **o_mbim, |
| MMPort **o_data, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_autoptr(MMBaseModem) modem = NULL; |
| |
| g_object_get (G_OBJECT (self), |
| MM_BASE_BEARER_MODEM, &modem, |
| NULL); |
| g_assert (MM_IS_BASE_MODEM (modem)); |
| |
| if (o_mbim) { |
| MMPortMbim *port; |
| |
| port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (modem)); |
| if (!port) { |
| g_task_report_new_error (self, |
| callback, |
| user_data, |
| peek_ports, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't peek MBIM port"); |
| return FALSE; |
| } |
| |
| *o_mbim = port; |
| } |
| |
| if (o_data) { |
| MMPort *port; |
| |
| /* Grab a data port */ |
| port = mm_base_modem_peek_best_data_port (modem, MM_PORT_TYPE_NET); |
| if (!port) { |
| g_task_report_new_error (self, |
| callback, |
| user_data, |
| peek_ports, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_NOT_FOUND, |
| "No valid data port found to launch connection"); |
| return FALSE; |
| } |
| |
| *o_data = port; |
| } |
| |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* Stats */ |
| |
| typedef struct { |
| guint64 rx_bytes; |
| guint64 tx_bytes; |
| } ReloadStatsResult; |
| |
| static gboolean |
| reload_stats_finish (MMBaseBearer *bearer, |
| guint64 *rx_bytes, |
| guint64 *tx_bytes, |
| GAsyncResult *res, |
| GError **error) |
| { |
| ReloadStatsResult *stats; |
| |
| stats = g_task_propagate_pointer (G_TASK (res), error); |
| if (!stats) |
| return FALSE; |
| |
| if (rx_bytes) |
| *rx_bytes = stats->rx_bytes; |
| if (tx_bytes) |
| *tx_bytes = stats->tx_bytes; |
| |
| g_free (stats); |
| return TRUE; |
| } |
| |
| static void |
| packet_statistics_query_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| guint64 in_octets = 0; |
| guint64 out_octets = 0; |
| |
| response = mbim_device_command_finish (device, res, &error); |
| if (response && |
| mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) && |
| mbim_message_packet_statistics_response_parse ( |
| response, |
| NULL, /* in_discards */ |
| NULL, /* in_errors */ |
| &in_octets, /* in_octets */ |
| NULL, /* in_packets */ |
| &out_octets, /* out_octets */ |
| NULL, /* out_packets */ |
| NULL, /* out_errors */ |
| NULL, /* out_discards */ |
| &error)) { |
| /* Store results */ |
| ReloadStatsResult *stats; |
| |
| stats = g_new (ReloadStatsResult, 1); |
| stats->rx_bytes = in_octets; |
| stats->tx_bytes = out_octets; |
| g_task_return_pointer (task, stats, g_free); |
| } else if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_OPERATION_NOT_ALLOWED)) { |
| g_clear_error (&error); |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "operation not allowed"); |
| } else |
| g_task_return_error (task, error); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| packet_statistics_query (GTask *task, |
| MbimDevice *device) |
| { |
| g_autoptr(MbimMessage) message = NULL; |
| |
| message = (mbim_message_packet_statistics_query_new (NULL)); |
| mbim_device_command (device, |
| message, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)packet_statistics_query_ready, |
| task); |
| } |
| |
| static void |
| packet_filters_set_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| g_autoptr(GError) error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| response = mbim_device_command_finish (device, res, &error); |
| if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) |
| mm_obj_dbg (self, "Couldn't reset packet filters: %s", error->message); |
| |
| packet_statistics_query (task, device); |
| } |
| |
| static void |
| ensure_removed_filters (GTask *task, |
| MbimDevice *device) |
| { |
| MMBearerMbim *self; |
| g_autoptr(MbimMessage) message = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| message = mbim_message_ip_packet_filters_set_new (self->priv->session_id, 0, NULL, NULL); |
| mbim_device_command (device, |
| message, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)packet_filters_set_ready, |
| task); |
| } |
| |
| static void |
| reload_stats (MMBaseBearer *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBearerMbim *self = MM_BEARER_MBIM (_self); |
| MMPortMbim *mbim; |
| GTask *task; |
| |
| if (!peek_ports (self, &mbim, NULL, callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (self->priv->remove_filters) |
| ensure_removed_filters (task, mm_port_mbim_peek_device (mbim)); |
| else |
| packet_statistics_query (task, mm_port_mbim_peek_device (mbim)); |
| } |
| |
| /*****************************************************************************/ |
| /* Disconnection message builder. |
| */ |
| |
| static MbimMessage * |
| build_disconnect_message (MMBearerMbim *self, |
| MMPortMbim *port, |
| guint32 session_id) |
| { |
| if (mbim_device_check_ms_mbimex_version (mm_port_mbim_peek_device (port), 3, 0)) |
| return mbim_message_ms_basic_connect_v3_connect_set_new (session_id, |
| MBIM_ACTIVATION_COMMAND_DEACTIVATE, |
| MBIM_COMPRESSION_NONE, |
| MBIM_AUTH_PROTOCOL_NONE, |
| MBIM_CONTEXT_IP_TYPE_DEFAULT, |
| mbim_uuid_from_context_type (MBIM_CONTEXT_TYPE_INTERNET), |
| MBIM_ACCESS_MEDIA_TYPE_UNKNOWN, |
| "", /* access string */ |
| "", /* user name */ |
| "", /* password */ |
| NULL, /* unnamed ies */ |
| NULL); |
| |
| return mbim_message_connect_set_new (session_id, |
| MBIM_ACTIVATION_COMMAND_DEACTIVATE, |
| "", /* access string */ |
| "", /* user name */ |
| "", /* password */ |
| MBIM_COMPRESSION_NONE, |
| MBIM_AUTH_PROTOCOL_NONE, |
| MBIM_CONTEXT_IP_TYPE_DEFAULT, |
| mbim_uuid_from_context_type (MBIM_CONTEXT_TYPE_INTERNET), |
| NULL); |
| } |
| |
| /*****************************************************************************/ |
| /* Connect */ |
| |
| #define WAIT_LINK_PORT_TIMEOUT_MS 2500 |
| |
| typedef enum { |
| CONNECT_STEP_FIRST, |
| CONNECT_STEP_LOAD_PROFILE_SETTINGS, |
| CONNECT_STEP_SETUP_LINK, |
| CONNECT_STEP_SETUP_LINK_MAIN_UP, |
| CONNECT_STEP_CHECK_DISCONNECTED, |
| CONNECT_STEP_ENSURE_DISCONNECTED, |
| CONNECT_STEP_CONNECT, |
| CONNECT_STEP_IP_CONFIGURATION, |
| CONNECT_STEP_LAST |
| } ConnectStep; |
| |
| typedef struct { |
| MMPortMbim *mbim; |
| MMBroadbandModemMbim *modem; |
| ConnectStep step; |
| MMPort *data; |
| MMBearerConnectResult *connect_result; |
| MbimMessage *abort_on_failure; |
| /* settings to use */ |
| gint profile_id; |
| gchar *apn; |
| MbimContextType context_type; |
| gchar *user; |
| gchar *password; |
| MbimAuthProtocol auth; |
| MbimContextIpType requested_ip_type; |
| MbimContextIpType activated_ip_type; |
| /* multiplex support */ |
| guint session_id; |
| gchar *link_prefix_hint; |
| gchar *link_name; |
| MMPort *link; |
| } ConnectContext; |
| |
| static void |
| connect_context_free (ConnectContext *ctx) |
| { |
| if (ctx->abort_on_failure) { |
| mbim_device_command (mm_port_mbim_peek_device (ctx->mbim), |
| ctx->abort_on_failure, |
| MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, |
| NULL, NULL, NULL); |
| mbim_message_unref (ctx->abort_on_failure); |
| } |
| |
| if (ctx->link_name) { |
| mm_port_mbim_cleanup_link (ctx->mbim, ctx->link_name, NULL, NULL); |
| g_free (ctx->link_name); |
| } |
| g_clear_object (&ctx->link); |
| g_free (ctx->link_prefix_hint); |
| |
| g_free (ctx->apn); |
| g_free (ctx->user); |
| g_free (ctx->password); |
| |
| g_clear_pointer (&ctx->connect_result, (GDestroyNotify)mm_bearer_connect_result_unref); |
| |
| g_clear_object (&ctx->data); |
| g_object_unref (ctx->mbim); |
| g_object_unref (ctx->modem); |
| |
| g_slice_free (ConnectContext, ctx); |
| } |
| |
| static MMBearerConnectResult * |
| connect_finish (MMBaseBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void connect_context_step (GTask *task); |
| |
| static void |
| ip_configuration_query_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| ConnectContext *ctx; |
| GError *error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| MbimIPConfigurationAvailableFlag ipv4configurationavailable; |
| MbimIPConfigurationAvailableFlag ipv6configurationavailable; |
| guint32 ipv4addresscount; |
| g_autoptr(MbimIPv4ElementArray) ipv4address = NULL; |
| guint32 ipv6addresscount; |
| g_autoptr(MbimIPv6ElementArray) ipv6address = NULL; |
| const MbimIPv4 *ipv4gateway; |
| const MbimIPv6 *ipv6gateway; |
| guint32 ipv4dnsservercount; |
| g_autofree MbimIPv4 *ipv4dnsserver = NULL; |
| guint32 ipv6dnsservercount; |
| g_autofree MbimIPv6 *ipv6dnsserver = NULL; |
| guint32 ipv4mtu; |
| guint32 ipv6mtu; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mbim_device_command_finish (device, res, &error); |
| if (response && |
| mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) && |
| mbim_message_ip_configuration_response_parse ( |
| response, |
| NULL, /* sessionid */ |
| &ipv4configurationavailable, |
| &ipv6configurationavailable, |
| &ipv4addresscount, |
| &ipv4address, |
| &ipv6addresscount, |
| &ipv6address, |
| &ipv4gateway, |
| &ipv6gateway, |
| &ipv4dnsservercount, |
| &ipv4dnsserver, |
| &ipv6dnsservercount, |
| &ipv6dnsserver, |
| &ipv4mtu, |
| &ipv6mtu, |
| &error)) { |
| g_autofree gchar *ipv4configurationavailable_str = NULL; |
| g_autofree gchar *ipv6configurationavailable_str = NULL; |
| g_autoptr(MMBearerIpConfig) ipv4_config = NULL; |
| g_autoptr(MMBearerIpConfig) ipv6_config = NULL; |
| guint64 uplink_speed = 0; |
| guint64 downlink_speed = 0; |
| |
| /* IPv4 info */ |
| |
| ipv4configurationavailable_str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv4configurationavailable); |
| mm_obj_dbg (self, "IPv4 configuration available: '%s'", ipv4configurationavailable_str); |
| |
| if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && ipv4addresscount) { |
| guint i; |
| |
| mm_obj_dbg (self, " IP addresses (%u)", ipv4addresscount); |
| for (i = 0; i < ipv4addresscount; i++) { |
| g_autoptr(GInetAddress) addr = NULL; |
| g_autofree gchar *str = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4address[i]->ipv4_address, G_SOCKET_FAMILY_IPV4); |
| str = g_inet_address_to_string (addr); |
| mm_obj_dbg (self, " IP [%u]: '%s/%u'", i, str, ipv4address[i]->on_link_prefix_length); |
| } |
| } |
| |
| if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) && ipv4gateway) { |
| g_autoptr(GInetAddress) addr = NULL; |
| g_autofree gchar *str = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4); |
| str = g_inet_address_to_string (addr); |
| mm_obj_dbg (self, " gateway: '%s'", str); |
| } |
| |
| if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && ipv4dnsservercount) { |
| guint i; |
| |
| mm_obj_dbg (self, " DNS addresses (%u)", ipv4dnsservercount); |
| for (i = 0; i < ipv4dnsservercount; i++) { |
| g_autoptr(GInetAddress) addr = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4); |
| if (!g_inet_address_get_is_any (addr)) { |
| g_autofree gchar *str = NULL; |
| |
| str = g_inet_address_to_string (addr); |
| mm_obj_dbg (self, " DNS [%u]: '%s'", i, str); |
| } |
| } |
| } |
| |
| if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) && ipv4mtu) |
| mm_obj_dbg (self, " MTU: '%u'", ipv4mtu); |
| |
| /* IPv6 info */ |
| |
| ipv6configurationavailable_str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv6configurationavailable); |
| mm_obj_dbg (self, "IPv6 configuration available: '%s'", ipv6configurationavailable_str); |
| |
| if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && ipv6addresscount) { |
| guint i; |
| |
| mm_obj_dbg (self, " IP addresses (%u)", ipv6addresscount); |
| for (i = 0; i < ipv6addresscount; i++) { |
| g_autoptr(GInetAddress) addr = NULL; |
| g_autofree gchar *str = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6address[i]->ipv6_address, G_SOCKET_FAMILY_IPV6); |
| str = g_inet_address_to_string (addr); |
| mm_obj_dbg (self, " IP [%u]: '%s/%u'", i, str, ipv6address[i]->on_link_prefix_length); |
| } |
| } |
| |
| if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) && ipv6gateway) { |
| g_autoptr(GInetAddress) addr = NULL; |
| g_autofree gchar *str = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6); |
| str = g_inet_address_to_string (addr); |
| mm_obj_dbg (self, " gateway: '%s'", str); |
| } |
| |
| if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && ipv6dnsservercount) { |
| guint i; |
| |
| mm_obj_dbg (self, " DNS addresses (%u)", ipv6dnsservercount); |
| for (i = 0; i < ipv6dnsservercount; i++) { |
| g_autoptr(GInetAddress) addr = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6dnsserver[i], G_SOCKET_FAMILY_IPV6); |
| if (!g_inet_address_get_is_any (addr)) { |
| g_autofree gchar *str = NULL; |
| |
| str = g_inet_address_to_string (addr); |
| mm_obj_dbg (self, " DNS [%u]: '%s'", i, str); |
| } |
| } |
| } |
| |
| if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) && ipv6mtu) |
| mm_obj_dbg (self, " MTU: '%u'", ipv6mtu); |
| |
| /* Build connection results */ |
| |
| /* Build IPv4 config */ |
| if (ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4 || |
| #if defined SUPPORT_MBIM_IPV6_WITH_IPV4_ROAMING //TODO(b/183029202): Remove hacks before merging to upstream |
| ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV6 || |
| #endif |
| ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 || |
| ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { |
| gboolean address_set = FALSE; |
| |
| ipv4_config = mm_bearer_ip_config_new (); |
| |
| /* We assume that if we have an IP we can use static configuration. |
| * Not all modems or providers will return DNS servers or even a |
| * gateway, and not all modems support DHCP either. The IP management |
| * daemon/script just has to deal with this... |
| */ |
| if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && (ipv4addresscount > 0)) { |
| g_autoptr(GInetAddress) addr = NULL; |
| g_autofree gchar *str = NULL; |
| |
| mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_STATIC); |
| |
| /* IP address, pick the first one */ |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4address[0]->ipv4_address, G_SOCKET_FAMILY_IPV4); |
| str = g_inet_address_to_string (addr); |
| mm_bearer_ip_config_set_address (ipv4_config, str); |
| address_set = TRUE; |
| |
| /* Netmask */ |
| mm_bearer_ip_config_set_prefix (ipv4_config, ipv4address[0]->on_link_prefix_length); |
| |
| /* Gateway */ |
| if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) { |
| g_autoptr(GInetAddress) gw_addr = NULL; |
| g_autofree gchar *gw_str = NULL; |
| |
| gw_addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4); |
| gw_str = g_inet_address_to_string (gw_addr); |
| mm_bearer_ip_config_set_gateway (ipv4_config, gw_str); |
| } |
| } else |
| mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); |
| |
| /* DNS */ |
| if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && (ipv4dnsservercount > 0)) { |
| g_auto(GStrv) strarr = NULL; |
| guint i; |
| guint n; |
| |
| strarr = g_new0 (gchar *, ipv4dnsservercount + 1); |
| for (i = 0, n = 0; i < ipv4dnsservercount; i++) { |
| g_autoptr(GInetAddress) addr = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4); |
| if (!g_inet_address_get_is_any (addr)) |
| strarr[n++] = g_inet_address_to_string (addr); |
| } |
| mm_bearer_ip_config_set_dns (ipv4_config, (const gchar **)strarr); |
| } |
| |
| /* MTU */ |
| if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) |
| mm_bearer_ip_config_set_mtu (ipv4_config, ipv4mtu); |
| |
| /* We requested IPv4, but it wasn't reported as activated. If there is no IP address |
| * provided by the modem, we assume the IPv4 bearer wasn't truly activated */ |
| if (!address_set && |
| ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4 && |
| ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4V6 && |
| ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { |
| mm_obj_dbg (self, "IPv4 requested but no IPv4 activated and no IPv4 address set: ignoring"); |
| g_clear_object (&ipv4_config); |
| } |
| } |
| |
| /* Build IPv6 config */ |
| if (ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV6 || |
| ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 || |
| ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { |
| gboolean address_set = FALSE; |
| gboolean gateway_set = FALSE; |
| gboolean dns_set = FALSE; |
| |
| ipv6_config = mm_bearer_ip_config_new (); |
| |
| if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && (ipv6addresscount > 0)) { |
| g_autoptr(GInetAddress) addr = NULL; |
| g_autofree gchar *str = NULL; |
| |
| /* IP address, pick the first one */ |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6address[0]->ipv6_address, G_SOCKET_FAMILY_IPV6); |
| str = g_inet_address_to_string (addr); |
| mm_bearer_ip_config_set_address (ipv6_config, str); |
| address_set = TRUE; |
| |
| /* If the address is a link-local one, then SLAAC or DHCP must be used |
| * to get the real prefix and address. |
| * FIXME: maybe the modem reported non-LL address in ipv6address[1] ? |
| */ |
| if (g_inet_address_get_is_link_local (addr)) |
| address_set = FALSE; |
| |
| /* Netmask */ |
| mm_bearer_ip_config_set_prefix (ipv6_config, ipv6address[0]->on_link_prefix_length); |
| |
| /* Gateway */ |
| if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) { |
| g_autoptr(GInetAddress) gw_addr = NULL; |
| g_autofree gchar *gw_str = NULL; |
| |
| gw_addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6); |
| gw_str = g_inet_address_to_string (gw_addr); |
| mm_bearer_ip_config_set_gateway (ipv6_config, gw_str); |
| gateway_set = TRUE; |
| } |
| } |
| |
| if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && (ipv6dnsservercount > 0)) { |
| g_auto(GStrv) strarr = NULL; |
| guint i; |
| guint n; |
| |
| /* DNS */ |
| strarr = g_new0 (gchar *, ipv6dnsservercount + 1); |
| for (i = 0, n = 0; i < ipv6dnsservercount; i++) { |
| g_autoptr(GInetAddress) addr = NULL; |
| |
| addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6dnsserver[i], G_SOCKET_FAMILY_IPV6); |
| if (!g_inet_address_get_is_any (addr)) |
| strarr[n++] = g_inet_address_to_string (addr); |
| } |
| mm_bearer_ip_config_set_dns (ipv6_config, (const gchar **)strarr); |
| dns_set = TRUE; |
| } |
| |
| /* MTU */ |
| if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) |
| mm_bearer_ip_config_set_mtu (ipv6_config, ipv6mtu); |
| |
| /* Only use the static method if all basic properties are available, |
| * otherwise use DHCP to indicate the missing ones should be |
| * retrieved from SLAAC or DHCPv6. |
| */ |
| #if defined SUPPORT_MBIM_IPV6_WITH_IPV4_ROAMING //TODO(b/183029202): Remove hacks before merging to upstream |
| mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_STATIC); |
| #else |
| if (address_set && gateway_set && dns_set) |
| mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_STATIC); |
| else |
| mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); |
| #endif |
| /* We requested IPv6, but it wasn't reported as activated. If there is no IPv6 address |
| * provided by the modem, we assume the IPv6 bearer wasn't truly activated */ |
| if (!address_set && |
| ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV6 && |
| ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4V6 && |
| ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { |
| mm_obj_dbg (self, "IPv6 requested but no IPv6 activated and no IPv6 address set: ignoring"); |
| g_clear_object (&ipv6_config); |
| } |
| } |
| |
| /* Store result */ |
| ctx->connect_result = mm_bearer_connect_result_new (ctx->link ? ctx->link : ctx->data, |
| ipv4_config, |
| ipv6_config); |
| mm_bearer_connect_result_set_multiplexed (ctx->connect_result, !!ctx->link); |
| |
| if (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN) |
| mm_bearer_connect_result_set_profile_id (ctx->connect_result, ctx->profile_id); |
| |
| /* Propagate speeds from modem object */ |
| mm_broadband_modem_mbim_get_speeds (ctx->modem, &uplink_speed, &downlink_speed); |
| mm_bearer_connect_result_set_uplink_speed (ctx->connect_result, uplink_speed); |
| mm_bearer_connect_result_set_downlink_speed (ctx->connect_result, downlink_speed); |
| } |
| |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Keep on */ |
| ctx->step++; |
| connect_context_step (task); |
| } |
| |
| static void |
| connect_set_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| ConnectContext *ctx; |
| GError *error = NULL; |
| g_autoptr(GError) inner_error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| guint32 session_id; |
| MbimActivationState activation_state; |
| guint32 nw_error; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mbim_device_command_finish (device, res, &error); |
| if (!response) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* always parse, because on failure we also check the NwError */ |
| mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error); |
| |
| if (mbim_device_check_ms_mbimex_version (device, 3, 0)) { |
| if (!mbim_message_ms_basic_connect_v3_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| &ctx->activated_ip_type, |
| NULL, /* context_type */ |
| &nw_error, |
| NULL, /* media_preference */ |
| NULL, /* access_string */ |
| NULL, /* unnamed_ies */ |
| &inner_error)) |
| g_prefix_error (&inner_error, "Failed processing MBIMEx v3.0 connect response: "); |
| else |
| mm_obj_dbg (self, "processed MBIMEx v3.0 connect response"); |
| } else { |
| if (!mbim_message_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| &ctx->activated_ip_type, |
| NULL, /* context_type */ |
| &nw_error, |
| &inner_error)) |
| g_prefix_error (&inner_error, "Failed processing connect response: "); |
| else |
| mm_obj_dbg (self, "processed connect response"); |
| } |
| |
| /* Prefer the error from the result to the parsing error */ |
| if (inner_error) { |
| if (!error) |
| error = g_steal_pointer (&inner_error); |
| } else { |
| /* Report the IP type we asked for and the one returned by the modem */ |
| mm_obj_dbg (self, "session ID '%u': %s (requested IP type: %s, activated IP type: %s, nw error: %s)", |
| session_id, |
| mbim_activation_state_get_string (activation_state), |
| mbim_context_ip_type_get_string (ctx->requested_ip_type), |
| mbim_context_ip_type_get_string (ctx->activated_ip_type), |
| mbim_nw_error_get_string (nw_error)); |
| /* If the response reports an ACTIVATED state, we're good even if |
| * there is a nw_error set (e.g. asking for IPv4v6 may return a |
| * 'pdp-type-ipv4-only-allowed' nw_error). */ |
| if (activation_state != MBIM_ACTIVATION_STATE_ACTIVATED && |
| activation_state != MBIM_ACTIVATION_STATE_ACTIVATING) { |
| g_clear_error (&error); |
| error = mm_mobile_equipment_error_from_mbim_nw_error (nw_error, self); |
| } |
| } |
| |
| if (error) { |
| /* A timeout when attempting to activate the request will require us to |
| * explicitly abort the operation */ |
| if (g_error_matches (error, MBIM_CORE_ERROR, MBIM_CORE_ERROR_TIMEOUT)) |
| ctx->abort_on_failure = build_disconnect_message (self, ctx->mbim, ctx->session_id); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Keep on. From now on, any additional command failure will require an |
| * explicit disconnection */ |
| ctx->abort_on_failure = build_disconnect_message (self, ctx->mbim, ctx->session_id); |
| ctx->step++; |
| connect_context_step (task); |
| } |
| |
| static void |
| ensure_disconnected_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| ConnectContext *ctx; |
| g_autoptr(MbimMessage) response = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Ignore all errors, just go on */ |
| response = mbim_device_command_finish (device, res, NULL); |
| |
| /* Keep on */ |
| ctx->step++; |
| connect_context_step (task); |
| } |
| |
| static void |
| check_disconnected_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| ConnectContext *ctx; |
| g_autoptr(MbimMessage) response = NULL; |
| guint32 session_id; |
| MbimActivationState activation_state = MBIM_ACTIVATION_STATE_UNKNOWN; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mbim_device_command_finish (device, res, NULL); |
| if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, NULL)) { |
| if (mbim_device_check_ms_mbimex_version (device, 3, 0)) |
| mbim_message_ms_basic_connect_v3_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| NULL, /* ip_type */ |
| NULL, /* context_type */ |
| NULL, /* nw_error */ |
| NULL, /* media_preference */ |
| NULL, /* access_string */ |
| NULL, /* unnamed_ies */ |
| NULL); |
| else |
| mbim_message_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| NULL, /* ip_type */ |
| NULL, /* context_type */ |
| NULL, /* nw_error */ |
| NULL); |
| } |
| |
| if (activation_state != MBIM_ACTIVATION_STATE_UNKNOWN) |
| mm_obj_dbg (self, "session ID '%u': %s", session_id, mbim_activation_state_get_string (activation_state)); |
| |
| /* Some modem (e.g. Huawei ME936) reports MBIM_ACTIVATION_STATE_UNKNOWN |
| * when being queried for the activation state before an IP session has |
| * been activated once. Here we expect a modem would at least tell the |
| * truth when the session has been activated, so we proceed to deactivate |
| * the session only the modem indicates the session has been activated or |
| * is being activated. |
| */ |
| if (activation_state == MBIM_ACTIVATION_STATE_ACTIVATED || activation_state == MBIM_ACTIVATION_STATE_ACTIVATING) |
| ctx->step = CONNECT_STEP_ENSURE_DISCONNECTED; |
| else |
| ctx->step = CONNECT_STEP_CONNECT; |
| |
| connect_context_step (task); |
| } |
| |
| static void |
| main_interface_up_ready (MMPortNet *link, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| ConnectContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!mm_port_net_link_setup_finish (link, res, &error)) { |
| g_prefix_error (&error, "Couldn't bring main interface up: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Keep on */ |
| ctx->step++; |
| connect_context_step (task); |
| } |
| |
| 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) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Keep on */ |
| ctx->step++; |
| connect_context_step (task); |
| } |
| |
| static void |
| setup_link_ready (MMPortMbim *mbim, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| ConnectContext *ctx; |
| GError *error = NULL; |
| g_autoptr(MMBaseModem) modem = NULL; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| ctx->link_name = mm_port_mbim_setup_link_finish (mbim, res, &ctx->session_id, &error); |
| if (!ctx->link_name) { |
| g_prefix_error (&error, "failed to create net link for device: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| 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_msg (self, "net link %s created (session id %u)", ctx->link_name, ctx->session_id); |
| |
| /* Wait for the data port with the given interface name, which will be |
| * added asynchronously */ |
| g_object_get (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 gboolean |
| load_settings_from_profile (MMBearerMbim *self, |
| ConnectContext *ctx, |
| MM3gppProfile *profile, |
| MMBearerApnType default_apn_type, |
| GError **error) |
| { |
| MMBearerAllowedAuth bearer_auth; |
| MMBearerApnType apn_type; |
| GError *inner_error = NULL; |
| g_autoptr(MMBaseModem) modem = NULL; |
| |
| g_object_get (self, MM_BASE_BEARER_MODEM, &modem, NULL); |
| g_assert (modem); |
| |
| /* APN settings */ |
| ctx->apn = g_strdup (mm_3gpp_profile_get_apn (profile)); |
| apn_type = mm_3gpp_profile_get_apn_type (profile); |
| if (apn_type == MM_BEARER_APN_TYPE_NONE) { |
| if (default_apn_type == MM_BEARER_APN_TYPE_NONE) { |
| g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "APN type in profile is not initialized"); |
| return FALSE; |
| } |
| apn_type = default_apn_type; |
| } |
| ctx->context_type = mm_bearer_apn_type_to_mbim_context_type ( |
| apn_type, |
| mm_broadband_modem_mbim_is_context_type_ext_supported (MM_BROADBAND_MODEM_MBIM (modem)), |
| self, |
| &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return FALSE; |
| } |
| |
| /* Auth settings */ |
| ctx->user = g_strdup (mm_3gpp_profile_get_user (profile)); |
| ctx->password = g_strdup (mm_3gpp_profile_get_password (profile)); |
| if (!ctx->user && !ctx->password) { |
| ctx->auth = MBIM_AUTH_PROTOCOL_NONE; |
| } else { |
| bearer_auth = mm_3gpp_profile_get_allowed_auth (profile); |
| ctx->auth = mm_bearer_allowed_auth_to_mbim_auth_protocol (bearer_auth, self, &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return FALSE; |
| } |
| } |
| |
| /* This IP type reading is applicable only when the profile comes |
| * from the input bearer properties, as there is no IP type stored |
| * in the device profiles. Therefore, only read it if it hasn't been |
| * read yet */ |
| if (ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_DEFAULT) { |
| MMBearerIpFamily ip_type; |
| |
| ip_type = mm_3gpp_profile_get_ip_type (profile); |
| mm_3gpp_normalize_ip_family (&ip_type); |
| ctx->requested_ip_type = mm_bearer_ip_family_to_mbim_context_ip_type (ip_type, &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| get_profile_ready (MMIfaceModem3gppProfileManager *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| ConnectContext *ctx; |
| GError *error = NULL; |
| g_autoptr(MM3gppProfile) profile = NULL; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| profile = mm_iface_modem_3gpp_profile_manager_get_profile_finish (modem, res, &error); |
| if (!profile) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!load_settings_from_profile (self, ctx, profile, MM_BEARER_APN_TYPE_NONE, &error)) { |
| g_prefix_error (&error, "Couldn't load settings from profile: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Keep on */ |
| ctx->step++; |
| connect_context_step (task); |
| } |
| |
| static void |
| connect_context_step (GTask *task) |
| { |
| MMBearerMbim *self; |
| ConnectContext *ctx; |
| g_autoptr(MbimMessage) message = NULL; |
| |
| /* If cancelled, complete */ |
| if (g_task_return_error_if_cancelled (task)) { |
| g_object_unref (task); |
| return; |
| } |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| switch (ctx->step) { |
| case CONNECT_STEP_FIRST: |
| ctx->step++; |
| /* Fall through */ |
| |
| case CONNECT_STEP_LOAD_PROFILE_SETTINGS: |
| if (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN) { |
| mm_obj_dbg (self, "loading connection settings from profile '%d'...", ctx->profile_id); |
| mm_iface_modem_3gpp_profile_manager_get_profile ( |
| MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (ctx->modem), |
| ctx->profile_id, |
| (GAsyncReadyCallback)get_profile_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case CONNECT_STEP_SETUP_LINK: |
| /* if a link prefix hint is available, it's because we should be doing |
| * multiplexing */ |
| if (ctx->link_prefix_hint) { |
| mm_obj_dbg (self, "setting up new multiplexed link..."); |
| mm_port_mbim_setup_link (ctx->mbim, |
| ctx->data, |
| ctx->link_prefix_hint, |
| (GAsyncReadyCallback) setup_link_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* fall through */ |
| |
| case CONNECT_STEP_SETUP_LINK_MAIN_UP: |
| /* if the connection is done through a new link, we need to ifup the main interface */ |
| if (ctx->link) { |
| mm_obj_dbg (self, "bringing main interface %s up...", mm_port_get_device (ctx->data)); |
| mm_port_net_link_setup (MM_PORT_NET (ctx->data), |
| TRUE, |
| 0, /* ignore */ |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) main_interface_up_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* fall through */ |
| |
| case CONNECT_STEP_CHECK_DISCONNECTED: { |
| MbimDevice *device; |
| |
| mm_obj_dbg (self, "checking if session %u is disconnected...", ctx->session_id); |
| |
| device = mm_port_mbim_peek_device (ctx->mbim); |
| if (mbim_device_check_ms_mbimex_version (device, 3, 0)) |
| message = mbim_message_ms_basic_connect_v3_connect_query_new (ctx->session_id, NULL); |
| else |
| message = mbim_message_connect_query_new ( |
| ctx->session_id, |
| MBIM_ACTIVATION_STATE_UNKNOWN, |
| MBIM_VOICE_CALL_STATE_NONE, |
| MBIM_CONTEXT_IP_TYPE_DEFAULT, |
| mbim_uuid_from_context_type (MBIM_CONTEXT_TYPE_INTERNET), |
| 0, |
| NULL); |
| |
| mbim_device_command (device, |
| message, |
| 10, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback)check_disconnected_ready, |
| task); |
| return; |
| } |
| |
| case CONNECT_STEP_ENSURE_DISCONNECTED: |
| mm_obj_dbg (self, "ensuring session %u is disconnected...", ctx->session_id); |
| message = build_disconnect_message (self, ctx->mbim, ctx->session_id); |
| mbim_device_command (mm_port_mbim_peek_device (ctx->mbim), |
| message, |
| MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback)ensure_disconnected_ready, |
| task); |
| return; |
| |
| case CONNECT_STEP_CONNECT: { |
| MbimDevice *device; |
| |
| mm_obj_dbg (self, "launching %s connection in session %u...", |
| mbim_context_ip_type_get_string (ctx->requested_ip_type), ctx->session_id); |
| |
| device = mm_port_mbim_peek_device (ctx->mbim); |
| if (mbim_device_check_ms_mbimex_version (device, 3, 0)) |
| message = mbim_message_ms_basic_connect_v3_connect_set_new ( |
| ctx->session_id, |
| MBIM_ACTIVATION_COMMAND_ACTIVATE, |
| MBIM_COMPRESSION_NONE, |
| ctx->auth, |
| ctx->requested_ip_type, |
| mbim_uuid_from_context_type (ctx->context_type), |
| MBIM_ACCESS_MEDIA_TYPE_UNKNOWN, |
| ctx->apn ? ctx->apn : "", |
| ctx->user ? ctx->user : "", |
| ctx->password ? ctx->password : "", |
| NULL, /* unnamed ies */ |
| NULL); |
| else |
| message = mbim_message_connect_set_new ( |
| ctx->session_id, |
| MBIM_ACTIVATION_COMMAND_ACTIVATE, |
| ctx->apn ? ctx->apn : "", |
| ctx->user ? ctx->user : "", |
| ctx->password ? ctx->password : "", |
| MBIM_COMPRESSION_NONE, |
| ctx->auth, |
| ctx->requested_ip_type, |
| mbim_uuid_from_context_type (ctx->context_type), |
| NULL); |
| |
| mbim_device_command (mm_port_mbim_peek_device (ctx->mbim), |
| message, |
| MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback)connect_set_ready, |
| task); |
| return; |
| } |
| |
| case CONNECT_STEP_IP_CONFIGURATION: |
| mm_obj_dbg (self, "querying IP configuration..."); |
| message = mbim_message_ip_configuration_query_new ( |
| ctx->session_id, |
| MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_NONE, /* ipv4configurationavailable */ |
| MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_NONE, /* ipv6configurationavailable */ |
| 0, /* ipv4addresscount */ |
| NULL, /* ipv4address */ |
| 0, /* ipv6addresscount */ |
| NULL, /* ipv6address */ |
| NULL, /* ipv4gateway */ |
| NULL, /* ipv6gateway */ |
| 0, /* ipv4dnsservercount */ |
| NULL, /* ipv4dnsserver */ |
| 0, /* ipv6dnsservercount */ |
| NULL, /* ipv6dnsserver */ |
| 0, /* ipv4mtu */ |
| 0, /* ipv6mtu */ |
| NULL); |
| mbim_device_command (mm_port_mbim_peek_device (ctx->mbim), |
| message, |
| 60, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback)ip_configuration_query_ready, |
| task); |
| return; |
| |
| case CONNECT_STEP_LAST: |
| /* Cleanup the abort message so that we don't |
| * run it */ |
| g_clear_pointer (&ctx->abort_on_failure, mbim_message_unref); |
| |
| /* Port is connected; update the state */ |
| mm_port_set_connected (ctx->link ? ctx->link : ctx->data, TRUE); |
| |
| /* Keep connection related data */ |
| g_assert (self->priv->mbim == NULL); |
| self->priv->mbim = g_object_ref (ctx->mbim); |
| g_assert (self->priv->data == NULL); |
| self->priv->data = ctx->data ? g_object_ref (ctx->data) : NULL; |
| g_assert (self->priv->link == NULL); |
| self->priv->link = ctx->link ? g_object_ref (ctx->link) : NULL; |
| g_assert (!self->priv->session_id); |
| self->priv->session_id = ctx->session_id; |
| |
| /* reset the link name to avoid cleaning up the link on context free */ |
| g_clear_pointer (&ctx->link_name, g_free); |
| |
| /* Set operation result */ |
| g_task_return_pointer ( |
| task, |
| mm_bearer_connect_result_ref (ctx->connect_result), |
| (GDestroyNotify)mm_bearer_connect_result_unref); |
| g_object_unref (task); |
| return; |
| |
| default: |
| break; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| static gboolean |
| load_settings_from_bearer (MMBearerMbim *self, |
| ConnectContext *ctx, |
| MMBearerProperties *properties, |
| GError **error) |
| { |
| MMBearerMultiplexSupport multiplex; |
| gboolean multiplex_supported = TRUE; |
| const gchar *data_port_driver; |
| |
| data_port_driver = mm_kernel_device_get_driver (mm_port_peek_kernel_device (ctx->data)); |
| if (!g_strcmp0 (data_port_driver, "mhi_net")) |
| multiplex_supported = FALSE; |
| |
| /* If no multiplex setting given by the user, assume none */ |
| multiplex = mm_bearer_properties_get_multiplex (properties); |
| if (multiplex == MM_BEARER_MULTIPLEX_SUPPORT_UNKNOWN) { |
| if (mm_context_get_test_multiplex_requested ()) |
| multiplex = MM_BEARER_MULTIPLEX_SUPPORT_REQUESTED; |
| else |
| multiplex = MM_BEARER_MULTIPLEX_SUPPORT_NONE; |
| } |
| |
| if (multiplex_supported && |
| (multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUESTED || |
| multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED)) { |
| /* the link prefix hint given must be modem-specific */ |
| ctx->link_prefix_hint = g_strdup_printf ("mbimmux%u.", mm_base_modem_get_dbus_id (MM_BASE_MODEM (ctx->modem))); |
| } |
| |
| if (!multiplex_supported && multiplex == MM_BEARER_MULTIPLEX_SUPPORT_REQUIRED) { |
| g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Multiplexing required but not supported by %s", data_port_driver); |
| return FALSE; |
| } |
| |
| /* If profile id is given, we'll load all settings from the stored profile, |
| * so ignore any other setting received in the bearer properties */ |
| ctx->profile_id = mm_bearer_properties_get_profile_id (properties); |
| if (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN) { |
| MMBearerIpFamily ip_type; |
| GError *inner_error = NULL; |
| |
| /* If we're loading settings from a profile, still read the ip-type |
| * from the user input, as that is not stored in the profile */ |
| ip_type = mm_bearer_properties_get_ip_type (properties); |
| mm_3gpp_normalize_ip_family (&ip_type); |
| ctx->requested_ip_type = mm_bearer_ip_family_to_mbim_context_ip_type (ip_type, &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /* Use the implicit profile settings in the bearer properties. |
| * If not loading from a stored profile, initialize the APN type to 'internet' |
| * (TYPE_DEFAULT) by default, which is what we've done until now. */ |
| if (!load_settings_from_profile (self, |
| ctx, |
| mm_bearer_properties_peek_3gpp_profile (properties), |
| MM_BEARER_APN_TYPE_DEFAULT, |
| error)) |
| return FALSE; |
| |
| /* Is this a 3GPP only modem and no APN or profile id was given? If so, error */ |
| if (mm_iface_modem_is_3gpp_only (MM_IFACE_MODEM (ctx->modem)) && !ctx->apn) { |
| g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, |
| "3GPP connection logic requires APN or profile id setting"); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| _connect (MMBaseBearer *self, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| ConnectContext *ctx; |
| MMPort *data; |
| MMPortMbim *mbim; |
| GTask *task; |
| GError *error = NULL; |
| g_autoptr(MMBaseModem) modem = NULL; |
| g_autoptr(MMBearerProperties) properties = NULL; |
| |
| if (!peek_ports (self, &mbim, &data, callback, user_data)) |
| return; |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| |
| g_object_get (self, |
| MM_BASE_BEARER_MODEM, &modem, |
| MM_BASE_BEARER_CONFIG, &properties, |
| NULL); |
| g_assert (modem); |
| |
| ctx = g_slice_new0 (ConnectContext); |
| ctx->modem = MM_BROADBAND_MODEM_MBIM (g_object_ref (modem)); |
| ctx->mbim = g_object_ref (mbim); |
| ctx->data = g_object_ref (data); |
| ctx->step = CONNECT_STEP_FIRST; |
| ctx->requested_ip_type = MBIM_CONTEXT_IP_TYPE_DEFAULT; |
| ctx->activated_ip_type = MBIM_CONTEXT_IP_TYPE_DEFAULT; |
| g_task_set_task_data (task, ctx, (GDestroyNotify)connect_context_free); |
| |
| if (!load_settings_from_bearer (MM_BEARER_MBIM (self), ctx, properties, &error)) { |
| g_prefix_error (&error, "Invalid bearer properties: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "launching %sconnection with data port (%s/%s)", |
| ctx->link_prefix_hint ? "multiplexed " : "", |
| mm_port_subsys_get_string (mm_port_get_subsys (data)), |
| mm_port_get_device (data)); |
| |
| /* Run! */ |
| connect_context_step (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Disconnect */ |
| |
| typedef enum { |
| DISCONNECT_STEP_FIRST, |
| DISCONNECT_STEP_DISCONNECT, |
| DISCONNECT_STEP_LAST |
| } DisconnectStep; |
| |
| typedef struct { |
| MMPortMbim *mbim; |
| guint session_id; |
| DisconnectStep step; |
| } DisconnectContext; |
| |
| static void |
| disconnect_context_free (DisconnectContext *ctx) |
| { |
| g_object_unref (ctx->mbim); |
| g_slice_free (DisconnectContext, ctx); |
| } |
| |
| static gboolean |
| disconnect_finish (MMBaseBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| reset_bearer_connection (MMBearerMbim *self) |
| { |
| if (self->priv->data) { |
| mm_port_set_connected (self->priv->data, FALSE); |
| g_clear_object (&self->priv->data); |
| } |
| |
| if (self->priv->link) { |
| g_assert (self->priv->mbim); |
| /* Link is disconnected; update the state */ |
| mm_port_set_connected (self->priv->link, FALSE); |
| mm_port_mbim_cleanup_link (self->priv->mbim, |
| mm_port_get_device (self->priv->link), |
| NULL, |
| NULL); |
| g_clear_object (&self->priv->link); |
| } |
| self->priv->session_id = 0; |
| g_clear_object (&self->priv->mbim); |
| } |
| |
| static void disconnect_context_step (GTask *task); |
| |
| static void |
| disconnect_set_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| DisconnectContext *ctx; |
| GError *error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| guint32 session_id; |
| MbimActivationState activation_state; |
| guint32 nw_error; |
| g_autoptr(GError) inner_error = NULL; |
| gboolean result = FALSE; |
| gboolean parsed_result = FALSE; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mbim_device_command_finish (device, res, &error); |
| if (!response) |
| goto out; |
| |
| result = mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error); |
| |
| /* Parse the response only for the cases we need to */ |
| if (result || |
| g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FAILURE) || |
| g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_CONTEXT_NOT_ACTIVATED)) { |
| if (mbim_device_check_ms_mbimex_version (device, 3, 0)) |
| parsed_result = mbim_message_ms_basic_connect_v3_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| NULL, /* ip_type */ |
| NULL, /* context_type */ |
| &nw_error, |
| NULL, /* media_preference */ |
| NULL, /* access_string */ |
| NULL, /* unnamed_ies */ |
| &inner_error); |
| else |
| parsed_result = mbim_message_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| NULL, /* ip_type */ |
| NULL, /* context_type */ |
| &nw_error, |
| &inner_error); |
| } |
| |
| /* Now handle different response / error cases */ |
| |
| if (result && parsed_result) { |
| g_assert (!error); |
| g_assert (!inner_error); |
| mm_obj_dbg (self, "session ID '%u': %s", session_id, mbim_activation_state_get_string (activation_state)); |
| /* success */ |
| goto out; |
| } |
| |
| if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_CONTEXT_NOT_ACTIVATED)) { |
| if (parsed_result) |
| mm_obj_dbg (self, "context not activated: session ID '%u' already disconnected", session_id); |
| else |
| mm_obj_dbg (self, "context not activated: already disconnected"); |
| |
| g_clear_error (&error); |
| g_clear_error (&inner_error); |
| /* success */ |
| goto out; |
| } |
| |
| if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SIM_NOT_INSERTED)) { |
| g_clear_error (&error); |
| g_clear_error (&inner_error); |
| mm_obj_dbg (self, "SIM card not inserted: already disconnected"); |
| /* success */ |
| goto out; |
| } |
| |
| if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FAILURE) && parsed_result && nw_error != 0) { |
| g_assert (!inner_error); |
| g_error_free (error); |
| error = mm_mobile_equipment_error_from_mbim_nw_error (nw_error, self); |
| /* error out with nw_error error */ |
| goto out; |
| } |
| |
| /* Give precedence to original error over parsing error */ |
| if (!error && inner_error) |
| error = g_steal_pointer (&inner_error); |
| |
| out: |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Keep on */ |
| ctx->step++; |
| disconnect_context_step (task); |
| } |
| |
| static void |
| disconnect_context_step (GTask *task) |
| { |
| MMBearerMbim *self; |
| DisconnectContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| switch (ctx->step) { |
| case DISCONNECT_STEP_FIRST: |
| ctx->step++; |
| /* Fall through */ |
| |
| case DISCONNECT_STEP_DISCONNECT: { |
| g_autoptr(MbimMessage) message = NULL; |
| |
| message = build_disconnect_message (self, ctx->mbim, ctx->session_id); |
| mbim_device_command (mm_port_mbim_peek_device (ctx->mbim), |
| message, |
| MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, |
| NULL, |
| (GAsyncReadyCallback)disconnect_set_ready, |
| task); |
| return; |
| } |
| |
| case DISCONNECT_STEP_LAST: |
| /* Port is disconnected; update the state */ |
| reset_bearer_connection (self); |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| |
| default: |
| g_assert_not_reached (); |
| } |
| } |
| |
| static void |
| disconnect (MMBaseBearer *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBearerMbim *self = MM_BEARER_MBIM (_self); |
| DisconnectContext *ctx; |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if ((!self->priv->data && !self->priv->link) || |
| !self->priv->mbim) { |
| mm_obj_dbg (self, "no need to disconnect: MBIM bearer is already disconnected"); |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "launching disconnection on data port (%s/%s)", |
| mm_port_subsys_get_string (mm_port_get_subsys (self->priv->data)), |
| mm_port_get_device (self->priv->data)); |
| |
| ctx = g_slice_new0 (DisconnectContext); |
| ctx->mbim = g_object_ref (self->priv->mbim); |
| ctx->session_id = self->priv->session_id; |
| ctx->step = DISCONNECT_STEP_FIRST; |
| |
| g_task_set_task_data (task, ctx, (GDestroyNotify)disconnect_context_free); |
| |
| /* Run! */ |
| disconnect_context_step (task); |
| } |
| |
| /*****************************************************************************/ |
| |
| guint32 |
| mm_bearer_mbim_get_session_id (MMBearerMbim *self) |
| { |
| g_return_val_if_fail (MM_IS_BEARER_MBIM (self), 0); |
| |
| return self->priv->session_id; |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| report_connection_status (MMBaseBearer *self, |
| MMBearerConnectionStatus status, |
| const GError *connection_error) |
| { |
| if (status == MM_BEARER_CONNECTION_STATUS_DISCONNECTED) |
| /* Cleanup all connection related data */ |
| reset_bearer_connection (MM_BEARER_MBIM (self)); |
| |
| /* Chain up parent's report_connection_status() */ |
| MM_BASE_BEARER_CLASS (mm_bearer_mbim_parent_class)->report_connection_status (self, status, connection_error); |
| } |
| |
| /*****************************************************************************/ |
| |
| #if defined WITH_SUSPEND_RESUME |
| |
| static MMBearerConnectionStatus |
| reload_connection_status_finish (MMBaseBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| gint val; |
| |
| val = g_task_propagate_int (G_TASK (res), error); |
| if (val < 0) |
| return MM_BEARER_CONNECTION_STATUS_UNKNOWN; |
| |
| return (MMBearerConnectionStatus) val; |
| } |
| |
| static void |
| reload_connection_status_ready (MbimDevice *device, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBearerMbim *self; |
| guint32 session_id; |
| MbimActivationState activation_state; |
| MMBearerConnectionStatus bearer_connection_status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; |
| GError *error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| response = mbim_device_command_finish (device, res, &error); |
| if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) { |
| g_prefix_error (&error, "Cannot load session ID '%u' status: ", |
| mm_bearer_mbim_get_session_id (MM_BEARER_MBIM (self))); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (mbim_device_check_ms_mbimex_version (device, 3, 0)) { |
| if (!mbim_message_ms_basic_connect_v3_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| NULL, /* ip_type */ |
| NULL, /* context_type */ |
| NULL, /* nw_error */ |
| NULL, /* media_preference */ |
| NULL, /* access_string */ |
| NULL, /* unnamed_ies */ |
| &error)) |
| g_prefix_error (&error, "Failed processing MBIMEx v3.0 connect response: "); |
| else |
| mm_obj_dbg (self, "processed MBIMEx v3.0 connect response"); |
| } else { |
| if (!mbim_message_connect_response_parse ( |
| response, |
| &session_id, |
| &activation_state, |
| NULL, /* voice_call_state */ |
| NULL, /* ip_type */ |
| NULL, /* context_type */ |
| NULL, /* nw_error */ |
| &error)) |
| g_prefix_error (&error, "Failed processing connect response: "); |
| else |
| mm_obj_dbg (self, "processed connect response"); |
| } |
| |
| if (error) { |
| g_prefix_error (&error, "Cannot load session ID '%u' status: ", |
| mm_bearer_mbim_get_session_id (MM_BEARER_MBIM (self))); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "session ID '%u': %s", session_id, mbim_activation_state_get_string (activation_state)); |
| |
| switch (activation_state) { |
| case MBIM_ACTIVATION_STATE_ACTIVATED: |
| case MBIM_ACTIVATION_STATE_ACTIVATING: |
| /* for the purposes of the sync operation, it's fine to map ACTIVATING |
| * to CONNECTED, as we're really going to ignore that state in the actual |
| * processing of the logic. */ |
| bearer_connection_status = MM_BEARER_CONNECTION_STATUS_CONNECTED; |
| break; |
| case MBIM_ACTIVATION_STATE_DEACTIVATING: |
| bearer_connection_status = MM_BEARER_CONNECTION_STATUS_DISCONNECTING; |
| break; |
| case MBIM_ACTIVATION_STATE_DEACTIVATED: |
| bearer_connection_status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; |
| break; |
| case MBIM_ACTIVATION_STATE_UNKNOWN: |
| default: |
| break; |
| } |
| |
| if (bearer_connection_status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Cannot load session ID '%u' status", |
| mm_bearer_mbim_get_session_id (MM_BEARER_MBIM (self))); |
| else |
| g_task_return_int (task, bearer_connection_status); |
| g_object_unref (task); |
| } |
| |
| static void |
| reload_connection_status (MMBaseBearer *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMPortMbim *mbim; |
| GTask *task = NULL; |
| g_autoptr(MbimMessage) message = NULL; |
| |
| if (!peek_ports (self, &mbim, NULL, callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| message = mbim_message_connect_query_new (mm_bearer_mbim_get_session_id (MM_BEARER_MBIM (self)), |
| MBIM_ACTIVATION_STATE_UNKNOWN, |
| MBIM_VOICE_CALL_STATE_NONE, |
| MBIM_CONTEXT_IP_TYPE_DEFAULT, |
| mbim_uuid_from_context_type (MBIM_CONTEXT_TYPE_INTERNET), |
| 0, |
| NULL); |
| mbim_device_command (mm_port_mbim_peek_device (mbim), |
| message, |
| 10, |
| NULL, |
| (GAsyncReadyCallback)reload_connection_status_ready, |
| task); |
| } |
| |
| #endif /* WITH_SUSPEND_RESUME */ |
| |
| /*****************************************************************************/ |
| |
| static gboolean |
| check_need_removed_filters (MMBroadbandModemMbim *modem) |
| { |
| const gchar **drivers; |
| guint i; |
| |
| drivers = mm_base_modem_get_drivers (MM_BASE_MODEM (modem)); |
| for (i = 0; drivers[i]; i++) { |
| /* Applicable only to the FM350 */ |
| if (g_str_equal (drivers[i], "mtk_t7xx")) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| MMBaseBearer * |
| mm_bearer_mbim_new (MMBroadbandModemMbim *modem, |
| MMBearerProperties *config) |
| { |
| MMBaseBearer *bearer; |
| |
| /* The Mbim bearer inherits from MMBaseBearer (so it's not a MMBroadbandBearer) |
| * and that means that the object is not async-initable, so we just use |
| * g_object_new() here */ |
| bearer = g_object_new (MM_TYPE_BEARER_MBIM, |
| MM_BASE_BEARER_MODEM, modem, |
| MM_BASE_BEARER_CONFIG, config, |
| NULL); |
| |
| MM_BEARER_MBIM (bearer)->priv->remove_filters = check_need_removed_filters (modem); |
| |
| /* Only export valid bearers */ |
| mm_base_bearer_export (bearer); |
| |
| return bearer; |
| } |
| |
| static void |
| mm_bearer_mbim_init (MMBearerMbim *self) |
| { |
| /* Initialize private data */ |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BEARER_MBIM, MMBearerMbimPrivate); |
| } |
| |
| static void |
| dispose (GObject *object) |
| { |
| MMBearerMbim *self = MM_BEARER_MBIM (object); |
| |
| reset_bearer_connection (self); |
| |
| G_OBJECT_CLASS (mm_bearer_mbim_parent_class)->dispose (object); |
| } |
| |
| static void |
| mm_bearer_mbim_class_init (MMBearerMbimClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (MMBearerMbimPrivate)); |
| |
| /* Virtual methods */ |
| object_class->dispose = dispose; |
| |
| base_bearer_class->connect = _connect; |
| base_bearer_class->connect_finish = connect_finish; |
| base_bearer_class->disconnect = disconnect; |
| base_bearer_class->disconnect_finish = disconnect_finish; |
| base_bearer_class->report_connection_status = report_connection_status; |
| base_bearer_class->reload_stats = reload_stats; |
| base_bearer_class->reload_stats_finish = reload_stats_finish; |
| base_bearer_class->load_connection_status = NULL; |
| base_bearer_class->load_connection_status_finish = NULL; |
| #if defined WITH_SUSPEND_RESUME |
| base_bearer_class->reload_connection_status = reload_connection_status; |
| base_bearer_class->reload_connection_status_finish = reload_connection_status_finish; |
| #endif |
| } |