We want to expose in the Modem interface the list of supported IP families, and the easiest way to do so is to have the IP family as flags, and provide in the interface a single enum. Also, a value of 0 for a MMBearerIpFamily specifies that no flags are set, so just rename it to 'NONE'. And add a new 'ANY' value which sets all flags to 1.
1135 lines
38 KiB
C
1135 lines
38 KiB
C
/* -*- 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-modem-helpers-mbim.h"
|
|
#include "mm-serial-enums-types.h"
|
|
#include "mm-bearer-mbim.h"
|
|
#include "mm-log.h"
|
|
|
|
G_DEFINE_TYPE (MMBearerMbim, mm_bearer_mbim, MM_TYPE_BEARER)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_SESSION_ID,
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST];
|
|
|
|
struct _MMBearerMbimPrivate {
|
|
/* The session ID for this bearer */
|
|
guint32 session_id;
|
|
|
|
MMPort *data;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
peek_ports (gpointer self,
|
|
MbimDevice **o_device,
|
|
MMPort **o_data,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBaseModem *modem = NULL;
|
|
|
|
g_object_get (G_OBJECT (self),
|
|
MM_BEARER_MODEM, &modem,
|
|
NULL);
|
|
g_assert (MM_IS_BASE_MODEM (modem));
|
|
|
|
if (o_device) {
|
|
MMMbimPort *port;
|
|
|
|
port = mm_base_modem_peek_port_mbim (modem);
|
|
if (!port) {
|
|
g_simple_async_report_error_in_idle (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't peek MBIM port");
|
|
g_object_unref (modem);
|
|
return FALSE;
|
|
}
|
|
|
|
*o_device = mm_mbim_port_peek_device (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_simple_async_report_error_in_idle (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_NOT_FOUND,
|
|
"No valid data port found to launch connection");
|
|
g_object_unref (modem);
|
|
return FALSE;
|
|
}
|
|
|
|
*o_data = port;
|
|
}
|
|
|
|
g_object_unref (modem);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Connect */
|
|
|
|
typedef enum {
|
|
CONNECT_STEP_FIRST,
|
|
CONNECT_STEP_PACKET_SERVICE,
|
|
CONNECT_STEP_PROVISIONED_CONTEXTS,
|
|
CONNECT_STEP_CONNECT,
|
|
CONNECT_STEP_IP_CONFIGURATION,
|
|
CONNECT_STEP_LAST
|
|
} ConnectStep;
|
|
|
|
typedef struct {
|
|
MMBearerMbim *self;
|
|
MbimDevice *device;
|
|
GSimpleAsyncResult *result;
|
|
GCancellable *cancellable;
|
|
MMBearerProperties *properties;
|
|
ConnectStep step;
|
|
MMPort *data;
|
|
MbimContextIpType ip_type;
|
|
MMBearerConnectResult *connect_result;
|
|
} ConnectContext;
|
|
|
|
static void
|
|
connect_context_complete_and_free (ConnectContext *ctx)
|
|
{
|
|
g_simple_async_result_complete_in_idle (ctx->result);
|
|
g_object_unref (ctx->result);
|
|
if (ctx->connect_result)
|
|
mm_bearer_connect_result_unref (ctx->connect_result);
|
|
g_object_unref (ctx->data);
|
|
g_object_unref (ctx->cancellable);
|
|
g_object_unref (ctx->device);
|
|
g_object_unref (ctx->self);
|
|
g_slice_free (ConnectContext, ctx);
|
|
}
|
|
|
|
static MMBearerConnectResult *
|
|
connect_finish (MMBearer *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
|
|
return mm_bearer_connect_result_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
|
|
}
|
|
|
|
static void connect_context_step (ConnectContext *ctx);
|
|
|
|
static void
|
|
ip_configuration_query_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
ConnectContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
MbimIPConfigurationAvailableFlag ipv4configurationavailable;
|
|
MbimIPConfigurationAvailableFlag ipv6configurationavailable;
|
|
guint32 ipv4addresscount;
|
|
MbimIPv4Element **ipv4address;
|
|
guint32 ipv6addresscount;
|
|
MbimIPv6Element **ipv6address;
|
|
const MbimIPv4 *ipv4gateway;
|
|
const MbimIPv6 *ipv6gateway;
|
|
guint32 ipv4dnsservercount;
|
|
MbimIPv4 *ipv4dnsserver;
|
|
guint32 ipv6dnsservercount;
|
|
MbimIPv6 *ipv6dnsserver;
|
|
guint32 ipv4mtu;
|
|
guint32 ipv6mtu;
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response &&
|
|
mbim_message_command_done_get_result (response, &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)) {
|
|
gchar *str;
|
|
GInetAddress *addr;
|
|
MMBearerIpConfig *ipv4_config;
|
|
MMBearerIpConfig *ipv6_config;
|
|
|
|
/* IPv4 info */
|
|
|
|
str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv4configurationavailable);
|
|
mm_dbg ("IPv4 configuration available: '%s'", str);
|
|
g_free (str);
|
|
|
|
if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) {
|
|
guint i;
|
|
|
|
mm_dbg (" IP addresses (%u)", ipv4addresscount);
|
|
for (i = 0; i < ipv4addresscount; i++) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4address[i]->ipv4_address, G_SOCKET_FAMILY_IPV4);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_dbg (" IP [%u]: '%s/%u'", i, str, ipv4address[i]->on_link_prefix_length);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
}
|
|
|
|
if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_dbg (" Gateway: '%s'", str);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
|
|
if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) {
|
|
guint i;
|
|
|
|
mm_dbg (" DNS addresses (%u)", ipv4dnsservercount);
|
|
for (i = 0; i < ipv4dnsservercount; i++) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_dbg (" DNS [%u]: '%s'", i, str);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
}
|
|
|
|
if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) {
|
|
mm_dbg (" MTU: '%u'", ipv4mtu);
|
|
}
|
|
|
|
/* IPv6 info */
|
|
|
|
str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv6configurationavailable);
|
|
mm_dbg ("IPv6 configuration available: '%s'", str);
|
|
g_free (str);
|
|
|
|
if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) {
|
|
guint i;
|
|
|
|
mm_dbg (" IP addresses (%u)", ipv6addresscount);
|
|
for (i = 0; i < ipv6addresscount; i++) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6address[i]->ipv6_address, G_SOCKET_FAMILY_IPV6);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_dbg (" IP [%u]: '%s/%u'", i, str, ipv6address[i]->on_link_prefix_length);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
}
|
|
|
|
if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_dbg (" Gateway: '%s'", str);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
|
|
if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) {
|
|
guint i;
|
|
|
|
mm_dbg (" DNS addresses (%u)", ipv6dnsservercount);
|
|
for (i = 0; i < ipv6dnsservercount; i++) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6dnsserver[i], G_SOCKET_FAMILY_IPV6);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_dbg (" DNS [%u]: '%s'", i, str);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
}
|
|
|
|
if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) {
|
|
mm_dbg (" MTU: '%u'", ipv6mtu);
|
|
}
|
|
|
|
/* Build connection results */
|
|
|
|
/* Build IPv4 config */
|
|
if (ctx->ip_type == MBIM_CONTEXT_IP_TYPE_IPV4 ||
|
|
ctx->ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 ||
|
|
ctx->ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) {
|
|
ipv4_config = mm_bearer_ip_config_new ();
|
|
|
|
/* We assume that if we have IP and DNS, we can setup static */
|
|
if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS &&
|
|
ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS &&
|
|
ipv4addresscount > 0 &&
|
|
ipv4dnsservercount > 0) {
|
|
gchar **strarr;
|
|
guint i;
|
|
|
|
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);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
|
|
/* Netmask */
|
|
mm_bearer_ip_config_set_prefix (ipv4_config, ipv4address[0]->on_link_prefix_length);
|
|
|
|
/* Gateway */
|
|
if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_bearer_ip_config_set_gateway (ipv4_config, str);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
|
|
/* DNS */
|
|
strarr = g_new0 (gchar *, ipv4dnsservercount + 1);
|
|
for (i = 0; i < ipv4dnsservercount; i++) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4);
|
|
strarr[i] = g_inet_address_to_string (addr);
|
|
g_object_unref (addr);
|
|
}
|
|
mm_bearer_ip_config_set_dns (ipv4_config, (const gchar **)strarr);
|
|
g_strfreev (strarr);
|
|
} else
|
|
mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP);
|
|
} else
|
|
ipv4_config = NULL;
|
|
|
|
/* Build IPv6 config; always DHCP based */
|
|
if (ctx->ip_type == MBIM_CONTEXT_IP_TYPE_IPV6 ||
|
|
ctx->ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 ||
|
|
ctx->ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) {
|
|
ipv6_config = mm_bearer_ip_config_new ();
|
|
|
|
/* We assume that if we have IP and DNS, we can setup static */
|
|
if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS &&
|
|
ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS &&
|
|
ipv6addresscount > 0 &&
|
|
ipv6dnsservercount > 0) {
|
|
gchar **strarr;
|
|
guint i;
|
|
|
|
mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_STATIC);
|
|
|
|
/* 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);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
|
|
/* Netmask */
|
|
mm_bearer_ip_config_set_prefix (ipv6_config, ipv6address[0]->on_link_prefix_length);
|
|
|
|
/* Gateway */
|
|
if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6);
|
|
str = g_inet_address_to_string (addr);
|
|
mm_bearer_ip_config_set_gateway (ipv6_config, str);
|
|
g_free (str);
|
|
g_object_unref (addr);
|
|
}
|
|
|
|
/* DNS */
|
|
strarr = g_new0 (gchar *, ipv6dnsservercount + 1);
|
|
for (i = 0; i < ipv6dnsservercount; i++) {
|
|
addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6dnsserver[i], G_SOCKET_FAMILY_IPV6);
|
|
strarr[i] = g_inet_address_to_string (addr);
|
|
g_object_unref (addr);
|
|
}
|
|
mm_bearer_ip_config_set_dns (ipv6_config, (const gchar **)strarr);
|
|
g_strfreev (strarr);
|
|
} else
|
|
mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP);
|
|
} else
|
|
ipv6_config = NULL;
|
|
|
|
/* Store result */
|
|
ctx->connect_result = mm_bearer_connect_result_new (ctx->data,
|
|
ipv4_config,
|
|
ipv6_config);
|
|
|
|
mbim_ipv4_element_array_free (ipv4address);
|
|
mbim_ipv6_element_array_free (ipv6address);
|
|
g_free (ipv4dnsserver);
|
|
g_free (ipv6dnsserver);
|
|
}
|
|
|
|
if (error) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Keep on */
|
|
ctx->step++;
|
|
connect_context_step (ctx);
|
|
}
|
|
|
|
static void
|
|
connect_set_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
ConnectContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
guint32 session_id;
|
|
MbimActivationState activation_state;
|
|
MbimContextIpType ip_type;
|
|
guint32 nw_error;
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response &&
|
|
mbim_message_command_done_get_result (response, &error) &&
|
|
mbim_message_connect_response_parse (
|
|
response,
|
|
&session_id,
|
|
&activation_state,
|
|
NULL, /* voice_call_state */
|
|
&ip_type,
|
|
NULL, /* context_type */
|
|
&nw_error,
|
|
&error)) {
|
|
if (nw_error)
|
|
error = mm_mobile_equipment_error_from_mbim_nw_error (nw_error);
|
|
else {
|
|
ctx->ip_type = ip_type;
|
|
mm_dbg ("Session ID '%u': %s (IP type: %s)",
|
|
session_id,
|
|
mbim_activation_state_get_string (activation_state),
|
|
mbim_context_ip_type_get_string (ip_type));
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Keep on */
|
|
ctx->step++;
|
|
connect_context_step (ctx);
|
|
}
|
|
|
|
static void
|
|
provisioned_contexts_query_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
ConnectContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
guint32 provisioned_contexts_count;
|
|
MbimProvisionedContextElement **provisioned_contexts;
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response &&
|
|
mbim_message_command_done_get_result (response, &error) &&
|
|
mbim_message_provisioned_contexts_response_parse (
|
|
response,
|
|
&provisioned_contexts_count,
|
|
&provisioned_contexts,
|
|
&error)) {
|
|
guint32 i;
|
|
|
|
mm_dbg ("Provisioned contexts found (%u):", provisioned_contexts_count);
|
|
for (i = 0; i < provisioned_contexts_count; i++) {
|
|
MbimProvisionedContextElement *el = provisioned_contexts[i];
|
|
gchar *uuid_str;
|
|
|
|
uuid_str = mbim_uuid_get_printable (&el->context_type);
|
|
mm_dbg ("[%u] context type: %s", el->context_id, mbim_context_type_get_string (mbim_uuid_to_context_type (&el->context_type)));
|
|
mm_dbg (" uuid: %s", uuid_str);
|
|
mm_dbg (" access string: %s", el->access_string ? el->access_string : "");
|
|
mm_dbg (" username: %s", el->user_name ? el->user_name : "");
|
|
mm_dbg (" password: %s", el->password ? el->password : "");
|
|
mm_dbg (" compression: %s", mbim_compression_get_string (el->compression));
|
|
mm_dbg (" auth: %s", mbim_auth_protocol_get_string (el->auth_protocol));
|
|
g_free (uuid_str);
|
|
}
|
|
|
|
mbim_provisioned_context_element_array_free (provisioned_contexts);
|
|
} else {
|
|
mm_dbg ("Error listing provisioned contexts: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
/* Keep on */
|
|
ctx->step++;
|
|
connect_context_step (ctx);
|
|
}
|
|
|
|
static void
|
|
packet_service_set_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
ConnectContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
guint32 nw_error;
|
|
MbimPacketServiceState packet_service_state;
|
|
MbimDataClass highest_available_data_class;
|
|
guint64 uplink_speed;
|
|
guint64 downlink_speed;
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response &&
|
|
mbim_message_command_done_get_result (response, &error) &&
|
|
mbim_message_packet_service_response_parse (
|
|
response,
|
|
&nw_error,
|
|
&packet_service_state,
|
|
&highest_available_data_class,
|
|
&uplink_speed,
|
|
&downlink_speed,
|
|
&error)) {
|
|
if (nw_error)
|
|
error = mm_mobile_equipment_error_from_mbim_nw_error (nw_error);
|
|
else {
|
|
gchar *str;
|
|
|
|
str = mbim_data_class_build_string_from_mask (highest_available_data_class);
|
|
mm_dbg ("Packet service update:");
|
|
mm_dbg (" state: '%s'", mbim_packet_service_state_get_string (packet_service_state));
|
|
mm_dbg (" data class: '%s'", str);
|
|
mm_dbg (" uplink: '%" G_GUINT64_FORMAT "' bps", uplink_speed);
|
|
mm_dbg (" downlink: '%" G_GUINT64_FORMAT "' bps", downlink_speed);
|
|
g_free (str);
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Keep on */
|
|
ctx->step++;
|
|
connect_context_step (ctx);
|
|
}
|
|
|
|
static void
|
|
connect_context_step (ConnectContext *ctx)
|
|
{
|
|
MbimMessage *message;
|
|
|
|
/* If cancelled, complete */
|
|
if (g_cancellable_is_cancelled (ctx->cancellable)) {
|
|
g_simple_async_result_set_error (ctx->result,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_CANCELLED,
|
|
"Connection setup operation has been cancelled");
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
switch (ctx->step) {
|
|
case CONNECT_STEP_FIRST:
|
|
/* Fall down */
|
|
ctx->step++;
|
|
|
|
case CONNECT_STEP_PACKET_SERVICE: {
|
|
GError *error = NULL;
|
|
|
|
mm_dbg ("Activating packet service...");
|
|
message = (mbim_message_packet_service_set_new (
|
|
MBIM_PACKET_SERVICE_ACTION_ATTACH,
|
|
&error));
|
|
if (!message) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (ctx->device,
|
|
message,
|
|
30,
|
|
NULL,
|
|
(GAsyncReadyCallback)packet_service_set_ready,
|
|
ctx);
|
|
mbim_message_unref (message);
|
|
return;
|
|
}
|
|
|
|
case CONNECT_STEP_PROVISIONED_CONTEXTS:
|
|
mm_dbg ("Listing provisioned contexts...");
|
|
message = mbim_message_provisioned_contexts_query_new (NULL);
|
|
mbim_device_command (ctx->device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)provisioned_contexts_query_ready,
|
|
ctx);
|
|
mbim_message_unref (message);
|
|
return;
|
|
|
|
case CONNECT_STEP_CONNECT: {
|
|
const gchar *apn;
|
|
const gchar *user;
|
|
const gchar *password;
|
|
MbimAuthProtocol auth;
|
|
MbimContextIpType ip_type;
|
|
MMBearerIpFamily ip_family;
|
|
GError *error = NULL;
|
|
|
|
/* Setup parameters to use */
|
|
|
|
apn = mm_bearer_properties_get_apn (ctx->properties);
|
|
user = mm_bearer_properties_get_user (ctx->properties);
|
|
password = mm_bearer_properties_get_password (ctx->properties);
|
|
|
|
if (!user && !password) {
|
|
auth = MBIM_AUTH_PROTOCOL_NONE;
|
|
} else {
|
|
MMBearerAllowedAuth bearer_auth;
|
|
bearer_auth = mm_bearer_properties_get_allowed_auth (ctx->properties);
|
|
if (bearer_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) {
|
|
mm_dbg ("Using default (PAP) authentication method");
|
|
auth = MBIM_AUTH_PROTOCOL_PAP;
|
|
} else if (bearer_auth & MM_BEARER_ALLOWED_AUTH_PAP) {
|
|
auth = MBIM_AUTH_PROTOCOL_PAP;
|
|
} else if (bearer_auth & MM_BEARER_ALLOWED_AUTH_CHAP) {
|
|
auth = MBIM_AUTH_PROTOCOL_CHAP;
|
|
} else if (bearer_auth & MM_BEARER_ALLOWED_AUTH_MSCHAPV2) {
|
|
auth = MBIM_AUTH_PROTOCOL_MSCHAPV2;
|
|
} else if (bearer_auth & MM_BEARER_ALLOWED_AUTH_NONE) {
|
|
auth = MBIM_AUTH_PROTOCOL_NONE;
|
|
} else {
|
|
gchar *str;
|
|
|
|
str = mm_bearer_allowed_auth_build_string_from_mask (bearer_auth);
|
|
g_simple_async_result_set_error (
|
|
ctx->result,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot use any of the specified authentication methods (%s)",
|
|
str);
|
|
g_free (str);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ip_family = mm_bearer_properties_get_ip_type (ctx->properties);
|
|
if (ip_family == MM_BEARER_IP_FAMILY_NONE ||
|
|
ip_family == MM_BEARER_IP_FAMILY_ANY) {
|
|
gchar * str;
|
|
|
|
ip_family = mm_bearer_get_default_ip_family (MM_BEARER (ctx->self));
|
|
str = mm_bearer_ip_family_build_string_from_mask (ip_family);
|
|
mm_dbg ("No specific IP family requested, defaulting to %s", str);
|
|
g_free (str);
|
|
}
|
|
|
|
if (ip_family == MM_BEARER_IP_FAMILY_IPV4)
|
|
ip_type = MBIM_CONTEXT_IP_TYPE_IPV4;
|
|
else if (ip_family == MM_BEARER_IP_FAMILY_IPV6)
|
|
ip_type = MBIM_CONTEXT_IP_TYPE_IPV6;
|
|
else if (ip_family == MM_BEARER_IP_FAMILY_IPV4V6)
|
|
ip_type = MBIM_CONTEXT_IP_TYPE_IPV4V6;
|
|
else if (ip_family == (MM_BEARER_IP_FAMILY_IPV4 | MM_BEARER_IP_FAMILY_IPV6))
|
|
ip_type = MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6;
|
|
else if (ip_family == MM_BEARER_IP_FAMILY_NONE ||
|
|
ip_family == MM_BEARER_IP_FAMILY_ANY)
|
|
/* A valid default IP family should have been specified */
|
|
g_assert_not_reached ();
|
|
else {
|
|
gchar * str;
|
|
|
|
str = mm_bearer_ip_family_build_string_from_mask (ip_family);
|
|
g_simple_async_result_set_error (
|
|
ctx->result,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Unsupported IP type configuration: '%s'",
|
|
str);
|
|
g_free (str);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_dbg ("Launching connection with APN '%s'...", apn);
|
|
message = (mbim_message_connect_set_new (
|
|
ctx->self->priv->session_id,
|
|
MBIM_ACTIVATION_COMMAND_ACTIVATE,
|
|
apn ? apn : "",
|
|
user ? user : "",
|
|
password ? password : "",
|
|
MBIM_COMPRESSION_NONE,
|
|
auth,
|
|
ip_type,
|
|
mbim_uuid_from_context_type (MBIM_CONTEXT_TYPE_INTERNET),
|
|
&error));
|
|
if (!message) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (ctx->device,
|
|
message,
|
|
60,
|
|
NULL,
|
|
(GAsyncReadyCallback)connect_set_ready,
|
|
ctx);
|
|
mbim_message_unref (message);
|
|
return;
|
|
}
|
|
|
|
case CONNECT_STEP_IP_CONFIGURATION: {
|
|
GError *error = NULL;
|
|
|
|
mm_dbg ("Querying IP configuration...");
|
|
message = (mbim_message_ip_configuration_query_new (
|
|
ctx->self->priv->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 */
|
|
&error));
|
|
if (!message) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (ctx->device,
|
|
message,
|
|
60,
|
|
NULL,
|
|
(GAsyncReadyCallback)ip_configuration_query_ready,
|
|
ctx);
|
|
mbim_message_unref (message);
|
|
return;
|
|
}
|
|
|
|
case CONNECT_STEP_LAST:
|
|
/* Port is connected; update the state */
|
|
mm_port_set_connected (MM_PORT (ctx->data), TRUE);
|
|
|
|
/* Keep the data port */
|
|
g_assert (ctx->self->priv->data == NULL);
|
|
ctx->self->priv->data = g_object_ref (ctx->data);
|
|
|
|
/* Set operation result */
|
|
g_simple_async_result_set_op_res_gpointer (
|
|
ctx->result,
|
|
mm_bearer_connect_result_ref (ctx->connect_result),
|
|
(GDestroyNotify)mm_bearer_connect_result_unref);
|
|
connect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
_connect (MMBearer *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
ConnectContext *ctx;
|
|
MMPort *data;
|
|
MbimDevice *device;
|
|
MMBaseModem *modem = NULL;
|
|
const gchar *apn;
|
|
|
|
if (!peek_ports (self, &device, &data, callback, user_data))
|
|
return;
|
|
|
|
g_object_get (self,
|
|
MM_BEARER_MODEM, &modem,
|
|
NULL);
|
|
g_assert (modem);
|
|
|
|
/* Check whether we have an APN */
|
|
apn = mm_bearer_properties_get_apn (mm_bearer_peek_config (MM_BEARER (self)));
|
|
|
|
/* Is this a 3GPP only modem and no APN was given? If so, error */
|
|
if (mm_iface_modem_is_3gpp_only (MM_IFACE_MODEM (modem)) && !apn) {
|
|
g_simple_async_report_error_in_idle (
|
|
G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_INVALID_ARGS,
|
|
"3GPP connection logic requires APN setting");
|
|
g_object_unref (modem);
|
|
return;
|
|
}
|
|
|
|
g_object_unref (modem);
|
|
|
|
mm_dbg ("Launching connection with data port (%s/%s)",
|
|
mm_port_subsys_get_string (mm_port_get_subsys (data)),
|
|
mm_port_get_device (data));
|
|
|
|
ctx = g_slice_new0 (ConnectContext);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->device = g_object_ref (device);;
|
|
ctx->data = g_object_ref (data);
|
|
ctx->cancellable = g_object_ref (cancellable);
|
|
ctx->step = CONNECT_STEP_FIRST;
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
_connect);
|
|
|
|
g_object_get (self,
|
|
MM_BEARER_CONFIG, &ctx->properties,
|
|
NULL);
|
|
|
|
/* Run! */
|
|
connect_context_step (ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disconnect */
|
|
|
|
typedef enum {
|
|
DISCONNECT_STEP_FIRST,
|
|
DISCONNECT_STEP_DISCONNECT,
|
|
DISCONNECT_STEP_LAST
|
|
} DisconnectStep;
|
|
|
|
typedef struct {
|
|
MMBearerMbim *self;
|
|
MbimDevice *device;
|
|
GSimpleAsyncResult *result;
|
|
MMPort *data;
|
|
DisconnectStep step;
|
|
} DisconnectContext;
|
|
|
|
static void
|
|
disconnect_context_complete_and_free (DisconnectContext *ctx)
|
|
{
|
|
g_simple_async_result_complete_in_idle (ctx->result);
|
|
g_object_unref (ctx->result);
|
|
g_object_unref (ctx->data);
|
|
g_object_unref (ctx->self);
|
|
g_slice_free (DisconnectContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
disconnect_finish (MMBearer *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void disconnect_context_step (DisconnectContext *ctx);
|
|
|
|
static void
|
|
disconnect_set_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
DisconnectContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
guint32 session_id;
|
|
MbimActivationState activation_state;
|
|
guint32 nw_error;
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response &&
|
|
mbim_message_command_done_get_result (response, &error) &&
|
|
mbim_message_connect_response_parse (
|
|
response,
|
|
&session_id,
|
|
&activation_state,
|
|
NULL, /* voice_call_state */
|
|
NULL, /* ip_type */
|
|
NULL, /* context_type */
|
|
&nw_error,
|
|
&error)) {
|
|
if (nw_error)
|
|
error = mm_mobile_equipment_error_from_mbim_nw_error (nw_error);
|
|
else
|
|
mm_dbg ("Session ID '%u': %s",
|
|
session_id,
|
|
mbim_activation_state_get_string (activation_state));
|
|
}
|
|
|
|
if (error) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
disconnect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Keep on */
|
|
ctx->step++;
|
|
disconnect_context_step (ctx);
|
|
}
|
|
|
|
static void
|
|
disconnect_context_step (DisconnectContext *ctx)
|
|
{
|
|
switch (ctx->step) {
|
|
case DISCONNECT_STEP_FIRST:
|
|
/* Fall down */
|
|
ctx->step++;
|
|
|
|
case DISCONNECT_STEP_DISCONNECT: {
|
|
MbimMessage *message;
|
|
GError *error = NULL;
|
|
|
|
message = (mbim_message_connect_set_new (
|
|
ctx->self->priv->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),
|
|
&error));
|
|
if (!message) {
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
disconnect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (ctx->device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)disconnect_set_ready,
|
|
ctx);
|
|
mbim_message_unref (message);
|
|
return;
|
|
}
|
|
|
|
case DISCONNECT_STEP_LAST:
|
|
/* Port is disconnected; update the state */
|
|
mm_port_set_connected (ctx->self->priv->data, FALSE);
|
|
g_clear_object (&ctx->self->priv->data);
|
|
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
disconnect_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
disconnect (MMBearer *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBearerMbim *self = MM_BEARER_MBIM (_self);
|
|
MbimDevice *device;
|
|
DisconnectContext *ctx;
|
|
|
|
if (!self->priv->data) {
|
|
g_simple_async_report_error_in_idle (
|
|
G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't disconnect MBIM bearer: this bearer is not connected");
|
|
return;
|
|
}
|
|
|
|
if (!peek_ports (self, &device, NULL, callback, user_data))
|
|
return;
|
|
|
|
mm_dbg ("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->self = g_object_ref (self);
|
|
ctx->device = g_object_ref (device);
|
|
ctx->data = g_object_ref (self->priv->data);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
disconnect);
|
|
ctx->step = DISCONNECT_STEP_FIRST;
|
|
|
|
/* Run! */
|
|
disconnect_context_step (ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMBearer *
|
|
mm_bearer_mbim_new (MMBroadbandModemMbim *modem,
|
|
MMBearerProperties *config,
|
|
guint32 session_id)
|
|
{
|
|
MMBearer *bearer;
|
|
|
|
/* The Mbim bearer inherits from MMBearer (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_BEARER_MODEM, modem,
|
|
MM_BEARER_CONFIG, config,
|
|
MM_BEARER_MBIM_SESSION_ID, (guint)session_id,
|
|
NULL);
|
|
|
|
/* Only export valid bearers */
|
|
mm_bearer_export (bearer);
|
|
|
|
return bearer;
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMBearerMbim *self = MM_BEARER_MBIM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_SESSION_ID:
|
|
self->priv->session_id = g_value_get_uint (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)
|
|
{
|
|
MMBearerMbim *self = MM_BEARER_MBIM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_SESSION_ID:
|
|
g_value_set_uint (value, self->priv->session_id);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
g_clear_object (&self->priv->data);
|
|
|
|
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);
|
|
MMBearerClass *bearer_class = MM_BEARER_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMBearerMbimPrivate));
|
|
|
|
/* Virtual methods */
|
|
object_class->dispose = dispose;
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
|
|
bearer_class->connect = _connect;
|
|
bearer_class->connect_finish = connect_finish;
|
|
bearer_class->disconnect = disconnect;
|
|
bearer_class->disconnect_finish = disconnect_finish;
|
|
|
|
properties[PROP_SESSION_ID] =
|
|
g_param_spec_uint (MM_BEARER_MBIM_SESSION_ID,
|
|
"Session ID",
|
|
"Session ID to use with this bearer",
|
|
0,
|
|
255,
|
|
0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_SESSION_ID, properties[PROP_SESSION_ID]);
|
|
}
|