| /* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> |
| */ |
| |
| #include <config.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <arpa/inet.h> |
| |
| #include <ModemManager.h> |
| #define _LIBMM_INSIDE_MM |
| #include <libmm-glib.h> |
| |
| #include "mm-broadband-bearer-ublox.h" |
| #include "mm-base-modem-at.h" |
| #include "mm-log-object.h" |
| #include "mm-ublox-enums-types.h" |
| #include "mm-modem-helpers.h" |
| #include "mm-modem-helpers-ublox.h" |
| |
| G_DEFINE_TYPE (MMBroadbandBearerUblox, mm_broadband_bearer_ublox, MM_TYPE_BROADBAND_BEARER) |
| |
| enum { |
| PROP_0, |
| PROP_USB_PROFILE, |
| PROP_NETWORKING_MODE, |
| PROP_LAST |
| }; |
| |
| static GParamSpec *properties[PROP_LAST]; |
| |
| struct _MMBroadbandBearerUbloxPrivate { |
| MMUbloxUsbProfile profile; |
| MMUbloxNetworkingMode mode; |
| MMUbloxBearerAllowedAuth allowed_auths; |
| FeatureSupport statistics; |
| FeatureSupport cedata; |
| }; |
| |
| /*****************************************************************************/ |
| /* Common connection context and task */ |
| |
| typedef struct { |
| MMBroadbandModem *modem; |
| MMPortSerialAt *primary; |
| MMPort *data; |
| guint cid; |
| gboolean auth_required; |
| MMBearerIpConfig *ip_config; /* For IPv4 settings */ |
| } CommonConnectContext; |
| |
| static void |
| common_connect_context_free (CommonConnectContext *ctx) |
| { |
| if (ctx->ip_config) |
| g_object_unref (ctx->ip_config); |
| if (ctx->data) |
| g_object_unref (ctx->data); |
| g_object_unref (ctx->modem); |
| g_object_unref (ctx->primary); |
| g_slice_free (CommonConnectContext, ctx); |
| } |
| |
| static GTask * |
| common_connect_task_new (MMBroadbandBearerUblox *self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| guint cid, |
| MMPort *data, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| CommonConnectContext *ctx; |
| GTask *task; |
| |
| ctx = g_slice_new0 (CommonConnectContext); |
| ctx->modem = g_object_ref (modem); |
| ctx->primary = g_object_ref (primary); |
| ctx->cid = cid; |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) common_connect_context_free); |
| |
| /* We need a net data port */ |
| if (data) |
| ctx->data = g_object_ref (data); |
| else { |
| ctx->data = mm_base_modem_get_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET); |
| if (!ctx->data) { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_NOT_FOUND, |
| "No valid data port found to launch connection"); |
| g_object_unref (task); |
| return NULL; |
| } |
| } |
| |
| return task; |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP IP config (sub-step of the 3GPP Connection sequence) */ |
| |
| static gboolean |
| get_ip_config_3gpp_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| MMBearerIpConfig **ipv4_config, |
| MMBearerIpConfig **ipv6_config, |
| GError **error) |
| { |
| MMBearerConnectResult *configs; |
| MMBearerIpConfig *ipv4; |
| |
| configs = g_task_propagate_pointer (G_TASK (res), error); |
| if (!configs) |
| return FALSE; |
| |
| /* Just IPv4 for now */ |
| ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); |
| g_assert (ipv4); |
| if (ipv4_config) |
| *ipv4_config = g_object_ref (ipv4); |
| if (ipv6_config) |
| *ipv6_config = NULL; |
| mm_bearer_connect_result_unref (configs); |
| return TRUE; |
| } |
| |
| static void |
| complete_get_ip_config_3gpp (GTask *task) |
| { |
| CommonConnectContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| g_assert (mm_bearer_ip_config_get_method (ctx->ip_config) != MM_BEARER_IP_METHOD_UNKNOWN); |
| g_task_return_pointer (task, |
| mm_bearer_connect_result_new (ctx->data, ctx->ip_config, NULL), |
| (GDestroyNotify) mm_bearer_connect_result_unref); |
| g_object_unref (task); |
| } |
| |
| static void |
| cgcontrdp_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| const gchar *response; |
| GError *error = NULL; |
| CommonConnectContext *ctx; |
| gchar *local_address = NULL; |
| gchar *subnet = NULL; |
| gchar *dns_addresses[3] = { NULL, NULL, NULL }; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (!response || !mm_3gpp_parse_cgcontrdp_response (response, |
| NULL, /* cid */ |
| NULL, /* bearer id */ |
| NULL, /* apn */ |
| &local_address, |
| &subnet, |
| NULL, /* gateway_address */ |
| &dns_addresses[0], |
| &dns_addresses[1], |
| &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "IPv4 address retrieved: %s", local_address); |
| mm_bearer_ip_config_set_address (ctx->ip_config, local_address); |
| mm_obj_dbg (self, "IPv4 subnet retrieved: %s", subnet); |
| mm_bearer_ip_config_set_prefix (ctx->ip_config, mm_netmask_to_cidr (subnet)); |
| if (dns_addresses[0]) |
| mm_obj_dbg (self, "primary DNS retrieved: %s", dns_addresses[0]); |
| if (dns_addresses[1]) |
| mm_obj_dbg (self, "secondary DNS retrieved: %s", dns_addresses[1]); |
| mm_bearer_ip_config_set_dns (ctx->ip_config, (const gchar **) dns_addresses); |
| |
| g_free (local_address); |
| g_free (subnet); |
| g_free (dns_addresses[0]); |
| g_free (dns_addresses[1]); |
| |
| mm_obj_dbg (self, "finished IP settings retrieval for PDP context #%u...", ctx->cid); |
| |
| complete_get_ip_config_3gpp (task); |
| } |
| |
| static void |
| uipaddr_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| const gchar *response; |
| gchar *cmd; |
| GError *error = NULL; |
| CommonConnectContext *ctx; |
| gchar *gw_ipv4_address = NULL; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (!response || !mm_ublox_parse_uipaddr_response (response, |
| NULL, /* cid */ |
| NULL, /* if_name */ |
| &gw_ipv4_address, |
| NULL, /* ipv4_subnet */ |
| NULL, /* ipv6_global_address */ |
| NULL, /* ipv6_link_local_address */ |
| &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "IPv4 gateway address retrieved: %s", gw_ipv4_address); |
| mm_bearer_ip_config_set_gateway (ctx->ip_config, gw_ipv4_address); |
| g_free (gw_ipv4_address); |
| |
| cmd = g_strdup_printf ("+CGCONTRDP=%u", ctx->cid); |
| mm_obj_dbg (self, "gathering IP and DNS information for PDP context #%u...", ctx->cid); |
| mm_base_modem_at_command (MM_BASE_MODEM (modem), |
| cmd, |
| 10, |
| FALSE, |
| (GAsyncReadyCallback) cgcontrdp_ready, |
| task); |
| g_free (cmd); |
| } |
| |
| static void |
| get_ip_config_3gpp (MMBroadbandBearer *_self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary, |
| MMPort *data, |
| guint cid, |
| MMBearerIpFamily ip_family, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (_self); |
| GTask *task; |
| CommonConnectContext *ctx; |
| |
| if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| cid, |
| data, |
| NULL, |
| callback, |
| user_data))) |
| return; |
| |
| ctx = g_task_get_task_data (task); |
| ctx->ip_config = mm_bearer_ip_config_new (); |
| |
| /* If we're in BRIDGE mode, we need to ask for static IP addressing details: |
| * - AT+UIPADDR=[CID] will give us the default gateway address. |
| * - +CGCONTRDP?[CID] will give us the IP address, subnet and DNS addresses. |
| */ |
| if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_BRIDGE) { |
| gchar *cmd; |
| |
| mm_bearer_ip_config_set_method (ctx->ip_config, MM_BEARER_IP_METHOD_STATIC); |
| |
| cmd = g_strdup_printf ("+UIPADDR=%u", cid); |
| mm_obj_dbg (self, "gathering gateway information for PDP context #%u...", cid); |
| mm_base_modem_at_command (MM_BASE_MODEM (modem), |
| cmd, |
| 10, |
| FALSE, |
| (GAsyncReadyCallback) uipaddr_ready, |
| task); |
| g_free (cmd); |
| return; |
| } |
| |
| /* If we're in ROUTER networking mode, we just need to request DHCP on the |
| * network interface. Early return with that result. */ |
| if (self->priv->mode == MM_UBLOX_NETWORKING_MODE_ROUTER) { |
| mm_bearer_ip_config_set_method (ctx->ip_config, MM_BEARER_IP_METHOD_DHCP); |
| complete_get_ip_config_3gpp (task); |
| return; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ |
| |
| static MMPort * |
| dial_3gpp_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return MM_PORT (g_task_propagate_pointer (G_TASK (res), error)); |
| } |
| |
| static void |
| cedata_activate_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| MMBroadbandBearerUblox *self) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (!response) { |
| mm_obj_warn (self, "ECM data connection attempt failed: %s", error->message); |
| mm_base_bearer_report_connection_status (MM_BASE_BEARER (self), |
| MM_BEARER_CONNECTION_STATUS_DISCONNECTED); |
| g_error_free (error); |
| } |
| /* we received a full bearer object reference */ |
| g_object_unref (self); |
| } |
| |
| static void |
| cgact_activate_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| CommonConnectContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (!response) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); |
| g_object_unref (task); |
| } |
| |
| static void |
| activate_3gpp (GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| CommonConnectContext *ctx; |
| g_autofree gchar *cmd = NULL; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* SARA-U2xx and LISA-U20x only expose one CDC-ECM interface. Hence, |
| * the fixed 0 as the interface index here. When we see modems with |
| * multiple interfaces, this needs to be revisited. */ |
| if (self->priv->profile == MM_UBLOX_USB_PROFILE_ECM && self->priv->cedata == FEATURE_SUPPORTED) { |
| cmd = g_strdup_printf ("+UCEDATA=%u,0", ctx->cid); |
| mm_obj_dbg (self, "establishing ECM data connection for PDP context #%u...", ctx->cid); |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), |
| cmd, |
| MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, |
| FALSE, |
| (GAsyncReadyCallback) cedata_activate_ready, |
| g_object_ref (self)); |
| |
| /* We'll mark the task done here since the modem expects the DHCP |
| discover packet while +UCEDATA runs. If the command fails, we'll |
| mark the bearer disconnected later in the callback. */ |
| g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref); |
| g_object_unref (task); |
| return; |
| } |
| |
| cmd = g_strdup_printf ("+CGACT=1,%u", ctx->cid); |
| mm_obj_dbg (self, "activating PDP context #%u...", ctx->cid); |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), |
| cmd, |
| MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT, |
| FALSE, |
| (GAsyncReadyCallback) cgact_activate_ready, |
| task); |
| } |
| |
| static void |
| test_cedata_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| const gchar *response; |
| |
| self = g_task_get_source_object (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, NULL); |
| if (response) |
| self->priv->cedata = FEATURE_SUPPORTED; |
| else |
| self->priv->cedata = FEATURE_UNSUPPORTED; |
| mm_obj_dbg (self, "+UCEDATA command%s available", |
| (self->priv->cedata == FEATURE_SUPPORTED) ? "" : " not"); |
| |
| activate_3gpp (task); |
| } |
| |
| static void |
| test_cedata (GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| CommonConnectContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* We don't need to test for +UCEDATA if we're not using CDC-ECM or if we |
| have tested before. Instead, we jump right to the activation. */ |
| if (self->priv->profile != MM_UBLOX_USB_PROFILE_ECM || self->priv->cedata != FEATURE_SUPPORT_UNKNOWN) { |
| activate_3gpp (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "checking availability of +UCEDATA command..."); |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), |
| "+UCEDATA=?", |
| 3, |
| TRUE, /* allow_cached */ |
| (GAsyncReadyCallback) test_cedata_ready, |
| task); |
| } |
| |
| static void |
| uauthreq_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (!response) { |
| CommonConnectContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| /* If authentication required and the +UAUTHREQ failed, abort */ |
| if (ctx->auth_required) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| /* Otherwise, ignore */ |
| g_error_free (error); |
| } |
| |
| test_cedata (task); |
| } |
| |
| static void |
| authenticate_3gpp (GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| CommonConnectContext *ctx; |
| g_autofree gchar *cmd = NULL; |
| MMBearerAllowedAuth allowed_auth; |
| gint ublox_auth = -1; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| |
| if (!ctx->auth_required) { |
| mm_obj_dbg (self, "not using authentication"); |
| ublox_auth = 0; |
| goto out; |
| } |
| |
| if (allowed_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN || allowed_auth == (MM_BEARER_ALLOWED_AUTH_PAP | MM_BEARER_ALLOWED_AUTH_CHAP)) { |
| mm_obj_dbg (self, "using automatic authentication method"); |
| if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO) |
| ublox_auth = 3; |
| else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP) |
| ublox_auth = 2; |
| else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_PAP) |
| ublox_auth = 1; |
| else if (self->priv->allowed_auths & MM_UBLOX_BEARER_ALLOWED_AUTH_NONE) |
| ublox_auth = 0; |
| } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_PAP) { |
| mm_obj_dbg (self, "using PAP authentication method"); |
| ublox_auth = 1; |
| } else if (allowed_auth & MM_BEARER_ALLOWED_AUTH_CHAP) { |
| mm_obj_dbg (self, "using CHAP authentication method"); |
| ublox_auth = 2; |
| } |
| |
| out: |
| |
| if (ublox_auth < 0) { |
| g_autofree gchar *str = NULL; |
| |
| str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth); |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot use any of the specified authentication methods (%s)", str); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (ublox_auth > 0) { |
| const gchar *user; |
| const gchar *password; |
| g_autofree gchar *quoted_user = NULL; |
| g_autofree gchar *quoted_password = NULL; |
| |
| user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| |
| quoted_user = mm_port_serial_at_quote_string (user); |
| quoted_password = mm_port_serial_at_quote_string (password); |
| |
| cmd = g_strdup_printf ("+UAUTHREQ=%u,%u,%s,%s", |
| ctx->cid, |
| ublox_auth, |
| quoted_user, |
| quoted_password); |
| } else |
| cmd = g_strdup_printf ("+UAUTHREQ=%u,0,\"\",\"\"", ctx->cid); |
| |
| mm_obj_dbg (self, "setting up authentication preferences in PDP context #%u...", ctx->cid); |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), |
| cmd, |
| 10, |
| FALSE, |
| (GAsyncReadyCallback) uauthreq_ready, |
| task); |
| } |
| |
| static void |
| uauthreq_test_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| const gchar *response; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (!response) |
| goto out; |
| |
| self->priv->allowed_auths = mm_ublox_parse_uauthreq_test (response, self, &error); |
| out: |
| if (error) { |
| CommonConnectContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| /* If authentication required and the +UAUTHREQ test failed, abort */ |
| if (ctx->auth_required) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| /* Otherwise, ignore and jump to test_cedata directly as no auth setup |
| * is needed */ |
| g_error_free (error); |
| test_cedata (task); |
| return; |
| } |
| |
| authenticate_3gpp (task); |
| } |
| |
| static void |
| check_supported_authentication_methods (GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| CommonConnectContext *ctx; |
| const gchar *user; |
| const gchar *password; |
| MMBearerAllowedAuth allowed_auth; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| password = mm_bearer_properties_get_password (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| allowed_auth = mm_bearer_properties_get_allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (self))); |
| |
| /* Flag whether authentication is required. If it isn't, we won't fail |
| * connection attempt if the +UAUTHREQ command fails */ |
| ctx->auth_required = (user && password && allowed_auth != MM_BEARER_ALLOWED_AUTH_NONE); |
| |
| /* If we already cached the support, not do it again */ |
| if (self->priv->allowed_auths != MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN) { |
| authenticate_3gpp (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "checking supported authentication methods..."); |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->modem), |
| "+UAUTHREQ=?", |
| 10, |
| TRUE, /* allow cached */ |
| (GAsyncReadyCallback) uauthreq_test_ready, |
| task); |
| } |
| |
| static void |
| dial_3gpp (MMBroadbandBearer *self, |
| MMBaseModem *modem, |
| MMPortSerialAt *primary, |
| guint cid, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| cid, |
| NULL, /* data, unused */ |
| cancellable, |
| callback, |
| user_data))) |
| return; |
| |
| check_supported_authentication_methods (task); |
| } |
| |
| /*****************************************************************************/ |
| /* 3GPP disconnection */ |
| |
| static gboolean |
| disconnect_3gpp_finish (MMBroadbandBearer *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| cgact_deactivate_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| const gchar *response; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (!response) { |
| /* TOBY-L4 and TOBY-L2 L2 don't allow to disconnect the last LTE bearer |
| * as that would imply de-registration from the LTE network, so we just |
| * assume that it's disconnected from the user point of view. |
| * |
| * TOBY-L4 reports this as a generic unknown Packet Domain Error, which |
| * is a bit unfortunate: |
| * AT+CGACT=0,1 |
| * +CME ERROR: 148 |
| * |
| * TOBY-L2 reports this as "LAST PDN disconnection not allowed" but using |
| * the legacy numeric value before 3GPP Rel 11 (i.e. 151 instead of 171). |
| * AT+CGACT=0,1 |
| * +CME ERROR: 151 |
| */ |
| if (!g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_GPRS_UNKNOWN) && |
| !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_GPRS_LAST_PDN_DISCONNECTION_NOT_ALLOWED) && |
| !g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_GPRS_LAST_PDN_DISCONNECTION_NOT_ALLOWED_LEGACY)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "ignored error when disconnecting last LTE bearer: %s", error->message); |
| g_clear_error (&error); |
| } |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| disconnect_3gpp (MMBroadbandBearer *self, |
| MMBroadbandModem *modem, |
| MMPortSerialAt *primary, |
| MMPortSerialAt *secondary, |
| MMPort *data, |
| guint cid, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| g_autofree gchar *cmd = NULL; |
| |
| if (!(task = common_connect_task_new (MM_BROADBAND_BEARER_UBLOX (self), |
| MM_BROADBAND_MODEM (modem), |
| primary, |
| cid, |
| data, |
| NULL, |
| callback, |
| user_data))) |
| return; |
| |
| cmd = g_strdup_printf ("+CGACT=0,%u", cid); |
| mm_obj_dbg (self, "deactivating PDP context #%u...", cid); |
| mm_base_modem_at_command (MM_BASE_MODEM (modem), |
| cmd, |
| MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT, |
| FALSE, |
| (GAsyncReadyCallback) cgact_deactivate_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Reload statistics */ |
| |
| typedef struct { |
| guint64 bytes_rx; |
| guint64 bytes_tx; |
| } StatsResult; |
| |
| static gboolean |
| reload_stats_finish (MMBaseBearer *self, |
| guint64 *bytes_rx, |
| guint64 *bytes_tx, |
| GAsyncResult *res, |
| GError **error) |
| { |
| StatsResult *result; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| if (!result) |
| return FALSE; |
| |
| if (bytes_rx) |
| *bytes_rx = result->bytes_rx; |
| if (bytes_tx) |
| *bytes_tx = result->bytes_tx; |
| g_free (result); |
| return TRUE; |
| } |
| |
| static void |
| ugcntrd_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| const gchar *response; |
| GError *error = NULL; |
| guint64 tx_bytes = 0; |
| guint64 rx_bytes = 0; |
| guint cid; |
| |
| self = MM_BROADBAND_BEARER_UBLOX (g_task_get_source_object (task)); |
| |
| cid = mm_broadband_bearer_get_3gpp_cid (MM_BROADBAND_BEARER (self)); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (response) |
| mm_ublox_parse_ugcntrd_response_for_cid (response, |
| cid, |
| &tx_bytes, &rx_bytes, |
| NULL, NULL, |
| &error); |
| |
| if (error) { |
| g_prefix_error (&error, "Couldn't load PDP context %u statistics: ", cid); |
| g_task_return_error (task, error); |
| } else { |
| StatsResult *result; |
| |
| result = g_new (StatsResult, 1); |
| result->bytes_rx = rx_bytes; |
| result->bytes_tx = tx_bytes; |
| g_task_return_pointer (task, result, g_free); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| run_reload_stats (MMBroadbandBearerUblox *self, |
| GTask *task) |
| { |
| /* Unsupported? */ |
| if (self->priv->statistics == FEATURE_UNSUPPORTED) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Loading statistics isn't supported by this device"); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Supported */ |
| if (self->priv->statistics == FEATURE_SUPPORTED) { |
| MMBaseModem *modem = NULL; |
| |
| g_object_get (MM_BASE_BEARER (self), |
| MM_BASE_BEARER_MODEM, &modem, |
| NULL); |
| mm_base_modem_at_command (MM_BASE_MODEM (modem), |
| "+UGCNTRD", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback) ugcntrd_ready, |
| task); |
| g_object_unref (modem); |
| return; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| static void |
| ugcntrd_test_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandBearerUblox *self; |
| |
| self = MM_BROADBAND_BEARER_UBLOX (g_task_get_source_object (task)); |
| |
| if (!mm_base_modem_at_command_finish (modem, res, NULL)) |
| self->priv->statistics = FEATURE_UNSUPPORTED; |
| else |
| self->priv->statistics = FEATURE_SUPPORTED; |
| |
| run_reload_stats (self, task); |
| } |
| |
| static void |
| reload_stats (MMBaseBearer *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (MM_BROADBAND_BEARER_UBLOX (self)->priv->statistics == FEATURE_SUPPORT_UNKNOWN) { |
| MMBaseModem *modem = NULL; |
| |
| g_object_get (MM_BASE_BEARER (self), |
| MM_BASE_BEARER_MODEM, &modem, |
| NULL); |
| |
| mm_base_modem_at_command (MM_BASE_MODEM (modem), |
| "+UGCNTRD=?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback) ugcntrd_test_ready, |
| task); |
| g_object_unref (modem); |
| return; |
| } |
| |
| run_reload_stats (MM_BROADBAND_BEARER_UBLOX (self), task); |
| } |
| |
| /*****************************************************************************/ |
| |
| MMBaseBearer * |
| mm_broadband_bearer_ublox_new_finish (GAsyncResult *res, |
| GError **error) |
| { |
| GObject *source; |
| GObject *bearer; |
| |
| source = g_async_result_get_source_object (res); |
| bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); |
| g_object_unref (source); |
| |
| if (!bearer) |
| return NULL; |
| |
| /* Only export valid bearers */ |
| mm_base_bearer_export (MM_BASE_BEARER (bearer)); |
| |
| return MM_BASE_BEARER (bearer); |
| } |
| |
| void |
| mm_broadband_bearer_ublox_new (MMBroadbandModem *modem, |
| MMUbloxUsbProfile profile, |
| MMUbloxNetworkingMode mode, |
| MMBearerProperties *config, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_assert (mode == MM_UBLOX_NETWORKING_MODE_ROUTER || mode == MM_UBLOX_NETWORKING_MODE_BRIDGE); |
| |
| g_async_initable_new_async ( |
| MM_TYPE_BROADBAND_BEARER_UBLOX, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| MM_BASE_BEARER_MODEM, modem, |
| MM_BASE_BEARER_CONFIG, config, |
| MM_BROADBAND_BEARER_UBLOX_USB_PROFILE, profile, |
| MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE, mode, |
| NULL); |
| } |
| |
| static void |
| set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec) |
| { |
| MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (object); |
| |
| switch (prop_id) { |
| case PROP_USB_PROFILE: |
| self->priv->profile = g_value_get_enum (value); |
| break; |
| case PROP_NETWORKING_MODE: |
| self->priv->mode = g_value_get_enum (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec) |
| { |
| MMBroadbandBearerUblox *self = MM_BROADBAND_BEARER_UBLOX (object); |
| |
| switch (prop_id) { |
| case PROP_USB_PROFILE: |
| g_value_set_enum (value, self->priv->profile); |
| break; |
| case PROP_NETWORKING_MODE: |
| g_value_set_enum (value, self->priv->mode); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| mm_broadband_bearer_ublox_init (MMBroadbandBearerUblox *self) |
| { |
| /* Initialize private data */ |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, |
| MM_TYPE_BROADBAND_BEARER_UBLOX, |
| MMBroadbandBearerUbloxPrivate); |
| |
| /* Defaults */ |
| self->priv->profile = MM_UBLOX_USB_PROFILE_UNKNOWN; |
| self->priv->mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN; |
| self->priv->allowed_auths = MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN; |
| self->priv->statistics = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->cedata = FEATURE_SUPPORT_UNKNOWN; |
| } |
| |
| static void |
| mm_broadband_bearer_ublox_class_init (MMBroadbandBearerUbloxClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass); |
| MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (MMBroadbandBearerUbloxPrivate)); |
| |
| object_class->get_property = get_property; |
| object_class->set_property = set_property; |
| |
| /* Note: the ublox plugin uses the generic AT+CGACT? based check to monitor |
| * the connection status (i.e. default load_connection_status()) */ |
| base_bearer_class->reload_stats = reload_stats; |
| base_bearer_class->reload_stats_finish = reload_stats_finish; |
| |
| broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; |
| broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; |
| broadband_bearer_class->dial_3gpp = dial_3gpp; |
| broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; |
| broadband_bearer_class->get_ip_config_3gpp = get_ip_config_3gpp; |
| broadband_bearer_class->get_ip_config_3gpp_finish = get_ip_config_3gpp_finish; |
| |
| properties[PROP_USB_PROFILE] = |
| g_param_spec_enum (MM_BROADBAND_BEARER_UBLOX_USB_PROFILE, |
| "USB profile", |
| "USB profile in use", |
| MM_TYPE_UBLOX_USB_PROFILE, |
| MM_UBLOX_USB_PROFILE_UNKNOWN, |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); |
| g_object_class_install_property (object_class, PROP_USB_PROFILE, properties[PROP_USB_PROFILE]); |
| |
| properties[PROP_NETWORKING_MODE] = |
| g_param_spec_enum (MM_BROADBAND_BEARER_UBLOX_NETWORKING_MODE, |
| "Networking mode", |
| "Networking mode in use", |
| MM_TYPE_UBLOX_NETWORKING_MODE, |
| MM_UBLOX_NETWORKING_MODE_UNKNOWN, |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); |
| g_object_class_install_property (object_class, PROP_NETWORKING_MODE, properties[PROP_NETWORKING_MODE]); |
| } |