From 1e30bad1741a7322142e7cf129f92cdfff4f9216 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 10 Apr 2014 18:31:39 -0500 Subject: [PATCH 01/11] libmm-glib: always return all bearer properties IPv6 may have addresses and other information even if the bearer's method is DHCP. --- libmm-glib/mm-bearer-ip-config.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libmm-glib/mm-bearer-ip-config.c b/libmm-glib/mm-bearer-ip-config.c index 7ffd7bab..8be59ac6 100644 --- a/libmm-glib/mm-bearer-ip-config.c +++ b/libmm-glib/mm-bearer-ip-config.c @@ -234,10 +234,7 @@ mm_bearer_ip_config_get_dictionary (MMBearerIpConfig *self) g_variant_new_uint32 (self ? self->priv->method : MM_BEARER_IP_METHOD_UNKNOWN)); - - /* If static IP method, report remaining configuration */ - if (self && - self->priv->method == MM_BEARER_IP_METHOD_STATIC) { + if (self) { if (self->priv->address) g_variant_builder_add (&builder, "{sv}", From 502a5dbaf5555515cf6a81d69403aaf6aa6ea656 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 18 Feb 2014 12:19:25 -0600 Subject: [PATCH 02/11] broadband-bearer-sierra: use correct modem type when creating bearer MMBroadbandModemSierraIcera is not a subclass of MMBroadbandModemSierra, so we cannot cast it to that type when passing it to bearer creation. Luckily the bearer doesn't care, so just downgrade the type to MMBroadbandModem. --- plugins/sierra/mm-broadband-bearer-sierra.c | 2 +- plugins/sierra/mm-broadband-bearer-sierra.h | 2 +- plugins/sierra/mm-broadband-modem-sierra-icera.c | 2 +- plugins/sierra/mm-broadband-modem-sierra.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/sierra/mm-broadband-bearer-sierra.c b/plugins/sierra/mm-broadband-bearer-sierra.c index 67cb3f27..dbe8a448 100644 --- a/plugins/sierra/mm-broadband-bearer-sierra.c +++ b/plugins/sierra/mm-broadband-bearer-sierra.c @@ -442,7 +442,7 @@ mm_broadband_bearer_sierra_new_finish (GAsyncResult *res, } void -mm_broadband_bearer_sierra_new (MMBroadbandModemSierra *modem, +mm_broadband_bearer_sierra_new (MMBroadbandModem *modem, MMBearerProperties *config, GCancellable *cancellable, GAsyncReadyCallback callback, diff --git a/plugins/sierra/mm-broadband-bearer-sierra.h b/plugins/sierra/mm-broadband-bearer-sierra.h index 92a5acc8..98b0735c 100644 --- a/plugins/sierra/mm-broadband-bearer-sierra.h +++ b/plugins/sierra/mm-broadband-bearer-sierra.h @@ -48,7 +48,7 @@ struct _MMBroadbandBearerSierraClass { GType mm_broadband_bearer_sierra_get_type (void); /* Default 3GPP bearer creation implementation */ -void mm_broadband_bearer_sierra_new (MMBroadbandModemSierra *modem, +void mm_broadband_bearer_sierra_new (MMBroadbandModem *modem, MMBearerProperties *config, GCancellable *cancellable, GAsyncReadyCallback callback, diff --git a/plugins/sierra/mm-broadband-modem-sierra-icera.c b/plugins/sierra/mm-broadband-modem-sierra-icera.c index d9a4e400..e01ea0a0 100644 --- a/plugins/sierra/mm-broadband-modem-sierra-icera.c +++ b/plugins/sierra/mm-broadband-modem-sierra-icera.c @@ -85,7 +85,7 @@ modem_create_bearer (MMIfaceModem *self, modem_create_bearer); mm_dbg ("Creating Sierra bearer..."); - mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM_SIERRA (self), + mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self), properties, NULL, /* cancellable */ (GAsyncReadyCallback)broadband_bearer_sierra_new_ready, diff --git a/plugins/sierra/mm-broadband-modem-sierra.c b/plugins/sierra/mm-broadband-modem-sierra.c index 8d06b017..e49c84cb 100644 --- a/plugins/sierra/mm-broadband-modem-sierra.c +++ b/plugins/sierra/mm-broadband-modem-sierra.c @@ -1134,7 +1134,7 @@ modem_create_bearer (MMIfaceModem *self, modem_create_bearer); mm_dbg ("Creating Sierra bearer..."); - mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM_SIERRA (self), + mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self), properties, NULL, /* cancellable */ (GAsyncReadyCallback)broadband_bearer_sierra_new_ready, From e9395d83e388aba8b46f2fbe371c247c42bd6452 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 20 Feb 2014 12:29:27 -0600 Subject: [PATCH 03/11] broadband-bearer: pass the IP family to subclasses when getting IP config Subclasses may need to know which IP families were used for the setup so they can return the correct IP configuration. We can't just use the MMBearer default_ip_family becuase that isn't the family that was actually used during the connection. --- plugins/icera/mm-broadband-bearer-icera.c | 1 + plugins/option/mm-broadband-bearer-hso.c | 1 + src/mm-broadband-bearer.c | 1 + src/mm-broadband-bearer.h | 1 + 4 files changed, 4 insertions(+) diff --git a/plugins/icera/mm-broadband-bearer-icera.c b/plugins/icera/mm-broadband-bearer-icera.c index 4507a611..128b20e8 100644 --- a/plugins/icera/mm-broadband-bearer-icera.c +++ b/plugins/icera/mm-broadband-bearer-icera.c @@ -269,6 +269,7 @@ get_ip_config_3gpp (MMBroadbandBearer *self, MMPortSerialAt *secondary, MMPort *data, guint cid, + MMBearerIpFamily ip_family, GAsyncReadyCallback callback, gpointer user_data) { diff --git a/plugins/option/mm-broadband-bearer-hso.c b/plugins/option/mm-broadband-bearer-hso.c index 15d9551b..c7c0e372 100644 --- a/plugins/option/mm-broadband-bearer-hso.c +++ b/plugins/option/mm-broadband-bearer-hso.c @@ -187,6 +187,7 @@ get_ip_config_3gpp (MMBroadbandBearer *self, MMPortSerialAt *secondary, MMPort *data, guint cid, + MMBearerIpFamily ip_family, GAsyncReadyCallback callback, gpointer user_data) { diff --git a/src/mm-broadband-bearer.c b/src/mm-broadband-bearer.c index de762d76..dce13f44 100644 --- a/src/mm-broadband-bearer.c +++ b/src/mm-broadband-bearer.c @@ -701,6 +701,7 @@ dial_3gpp_ready (MMBroadbandModem *modem, ctx->secondary, ctx->data, ctx->cid, + ctx->ip_family, (GAsyncReadyCallback)get_ip_config_3gpp_ready, ctx); return; diff --git a/src/mm-broadband-bearer.h b/src/mm-broadband-bearer.h index 4a008cd1..f3869120 100644 --- a/src/mm-broadband-bearer.h +++ b/src/mm-broadband-bearer.h @@ -79,6 +79,7 @@ struct _MMBroadbandBearerClass { MMPortSerialAt *secondary, MMPort *data, guint cid, + MMBearerIpFamily ip_family, GAsyncReadyCallback callback, gpointer user_data); gboolean (* get_ip_config_3gpp_finish) (MMBroadbandBearer *self, From e68d51f439190eb5434581c8db072687028f0b55 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 19 Feb 2014 12:06:55 -0600 Subject: [PATCH 04/11] broadband-bearer: also return IPv6 config if IPv6 context is used If the subclass doesn't override IP config retrieval, and IPv6 was used when connecting the bearer, return IPv6 config too. --- src/mm-broadband-bearer.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/mm-broadband-bearer.c b/src/mm-broadband-bearer.c index dce13f44..bed9ec2d 100644 --- a/src/mm-broadband-bearer.c +++ b/src/mm-broadband-bearer.c @@ -674,7 +674,9 @@ dial_3gpp_ready (MMBroadbandModem *modem, GAsyncResult *res, DetailedConnectContext *ctx) { - MMBearerIpConfig *config; + MMBearerIpMethod ip_method = MM_BEARER_IP_METHOD_UNKNOWN; + MMBearerIpConfig *ipv4_config = NULL; + MMBearerIpConfig *ipv6_config = NULL; GError *error = NULL; ctx->data = MM_BROADBAND_BEARER_GET_CLASS (ctx->self)->dial_3gpp_finish (ctx->self, res, &error); @@ -715,19 +717,30 @@ dial_3gpp_ready (MMBroadbandModem *modem, /* If no specific IP retrieval requested, set the default implementation * (PPP if data port is AT, DHCP otherwise) */ - config = mm_bearer_ip_config_new (); - mm_bearer_ip_config_set_method (config, - (MM_IS_PORT_SERIAL_AT (ctx->data) ? - MM_BEARER_IP_METHOD_PPP : - MM_BEARER_IP_METHOD_DHCP)); + ip_method = MM_IS_PORT_SERIAL_AT (ctx->data) ? + MM_BEARER_IP_METHOD_PPP : + MM_BEARER_IP_METHOD_DHCP; + + if (ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4 || + ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv4_config, ip_method); + } + if (ctx->ip_family & MM_BEARER_IP_FAMILY_IPV6 || + ctx->ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + ipv6_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv6_config, ip_method); + } + g_assert (ipv4_config || ipv6_config); g_simple_async_result_set_op_res_gpointer ( ctx->result, - mm_bearer_connect_result_new (ctx->data, config, NULL), + mm_bearer_connect_result_new (ctx->data, ipv4_config, ipv6_config), (GDestroyNotify)mm_bearer_connect_result_unref); detailed_connect_context_complete_and_free (ctx); - g_object_unref (config); + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); } static void From a416d8056c9f9bbc8b926ffbe8f066859054a990 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 17 Feb 2014 12:32:02 -0600 Subject: [PATCH 05/11] broadband-bearer-icera: add IPv6 support Split out the %IPDPADDR parsing into a helper and add testcases for it, and add support for IPv6 handling. If the returned IPv6 is link-local, the address should be assigned to the interface and SLAAC performed to retrieve the actual IPv6 prefix and RDNSS/DNSSD information. --- plugins/Makefile.am | 17 +- plugins/icera/mm-broadband-bearer-icera.c | 187 ++++-------- plugins/icera/mm-modem-helpers-icera.c | 277 ++++++++++++++++++ plugins/icera/mm-modem-helpers-icera.h | 28 ++ .../icera/tests/test-modem-helpers-icera.c | 191 ++++++++++++ 5 files changed, 571 insertions(+), 129 deletions(-) create mode 100644 plugins/icera/mm-modem-helpers-icera.c create mode 100644 plugins/icera/mm-modem-helpers-icera.h create mode 100644 plugins/icera/tests/test-modem-helpers-icera.c diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 31cc50ed..bf44ceb6 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -90,13 +90,28 @@ libmm_utils_icera_la_SOURCES = \ icera/mm-broadband-modem-icera.h \ icera/mm-broadband-modem-icera.c \ icera/mm-broadband-bearer-icera.h \ - icera/mm-broadband-bearer-icera.c + icera/mm-broadband-bearer-icera.c \ + icera/mm-modem-helpers-icera.c \ + icera/mm-modem-helpers-icera.h libmm_utils_icera_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) libmm_utils_icera_la_LIBADD = $(GUDEV_LIBS) $(MM_LIBS) ICERA_COMMON_COMPILER_FLAGS = -I$(top_srcdir)/plugins/icera ICERA_COMMON_LIBADD_FLAGS = $(builddir)/libmm-utils-icera.la +noinst_PROGRAMS += test-modem-helpers-icera +test_modem_helpers_icera_SOURCES = \ + icera/mm-modem-helpers-icera.c \ + icera/mm-modem-helpers-icera.h \ + icera/tests/test-modem-helpers-icera.c +test_modem_helpers_icera_CPPFLAGS = \ + -I$(top_srcdir)/plugins/icera \ + $(PLUGIN_COMMON_COMPILER_FLAGS) +test_modem_helpers_icera_LDADD = \ + $(top_builddir)/libmm-glib/libmm-glib.la \ + $(top_builddir)/src/libmodem-helpers.la +test_modem_helpers_icera_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS) + ######################################## pkglib_LTLIBRARIES = \ diff --git a/plugins/icera/mm-broadband-bearer-icera.c b/plugins/icera/mm-broadband-bearer-icera.c index 128b20e8..347f373f 100644 --- a/plugins/icera/mm-broadband-bearer-icera.c +++ b/plugins/icera/mm-broadband-bearer-icera.c @@ -33,6 +33,7 @@ #include "mm-modem-helpers.h" #include "mm-error-helpers.h" #include "mm-daemon-enums-types.h" +#include "mm-modem-helpers-icera.h" G_DEFINE_TYPE (MMBroadbandBearerIcera, mm_broadband_bearer_icera, MM_TYPE_BROADBAND_BEARER); @@ -109,157 +110,72 @@ get_ip_config_3gpp_finish (MMBroadbandBearer *self, MMBearerIpConfig **ipv6_config, GError **error) { - MMBearerIpConfig *ip_config; + MMBearerConnectResult *configs; + MMBearerIpConfig *ipv4, *ipv6; if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) return FALSE; - /* No IPv6 for now */ - ip_config = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); - *ipv4_config = g_object_ref (ip_config); - *ipv6_config = NULL; + configs = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); + g_assert (configs); + + ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); + ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs); + g_assert (ipv4 || ipv6); + if (ipv4_config && ipv4) + *ipv4_config = g_object_ref (ipv4); + if (ipv6_config && ipv6) + *ipv6_config = g_object_ref (ipv6); + return TRUE; } -#define IPDPADDR_TAG "%IPDPADDR: " - static void ip_config_ready (MMBaseModem *modem, GAsyncResult *res, GetIpConfig3gppContext *ctx) { - MMBearerIpConfig *ip_config = NULL; + MMBearerIpConfig *ipv4_config = NULL; + MMBearerIpConfig *ipv6_config = NULL; const gchar *response; GError *error = NULL; - gchar **items; - gchar *dns[3] = { 0 }; - guint i; - guint dns_i; + MMBearerConnectResult *connect_result; response = mm_base_modem_at_command_full_finish (modem, res, &error); if (error) { g_simple_async_result_take_error (ctx->result, error); - get_ip_config_context_complete_and_free (ctx); - return; + goto out; } - /* TODO: use a regex to parse this */ + if (!mm_icera_parse_ipdpaddr_response (response, + ctx->cid, + &ipv4_config, + &ipv6_config, + &error)) { + g_simple_async_result_take_error (ctx->result, error); + goto out; + } - /* Check result */ - if (!g_str_has_prefix (response, IPDPADDR_TAG)) { + if (!ipv4_config && !ipv6_config) { g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, - "Couldn't get IP config: invalid response '%s'", + "Couldn't get IP config: couldn't parse response '%s'", response); - get_ip_config_context_complete_and_free (ctx); - return; + goto out; } - /* %IPDPADDR: ,,,,[,,[,,,]] - * - * Sierra USB305: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0 - * K3805-Z: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10, - */ - response = mm_strip_tag (response, IPDPADDR_TAG); - items = g_strsplit (response, ", ", 0); - - ip_config = mm_bearer_ip_config_new (); - - for (i = 0, dns_i = 0; items[i]; i++) { - if (i == 0) { /* CID */ - gint num; - - if (!mm_get_int_from_str (items[i], &num) || - num != ctx->cid) { - error = g_error_new (MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "Unknown CID in IPDPADDR response (" - "got %d, expected %d)", - (guint) num, - ctx->cid); - break; - } - } else if (i == 1) { /* IP address */ - guint32 tmp = 0; - - if (!inet_pton (AF_INET, items[i], &tmp)) { - mm_warn ("Couldn't parse IP address '%s'", items[i]); - g_clear_object (&ip_config); - break; - } - - mm_bearer_ip_config_set_method (ip_config, MM_BEARER_IP_METHOD_STATIC); - mm_bearer_ip_config_set_address (ip_config, items[i]); - } else if (i == 2) { /* Gateway */ - guint32 tmp = 0; - - if (!inet_pton (AF_INET, items[i], &tmp)) { - mm_warn ("Couldn't parse gateway address '%s'", items[i]); - g_clear_object (&ip_config); - break; - } - - if (tmp) - mm_bearer_ip_config_set_gateway (ip_config, items[i]); - } else if (i == 3 || i == 4) { /* DNS entries */ - guint32 tmp = 0; - - if (!inet_pton (AF_INET, items[i], &tmp)) { - mm_warn ("Couldn't parse DNS address '%s'", items[i]); - g_clear_object (&ip_config); - break; - } - - if (tmp) - dns[dns_i++] = items[i]; - } else if (i == 8) { /* Netmask */ - guint32 tmp = 0; - - if (!inet_pton (AF_INET, items[i], &tmp)) { - mm_warn ("Couldn't parse netmask '%s'", items[i]); - g_clear_object (&ip_config); - break; - } - - mm_bearer_ip_config_set_prefix (ip_config, mm_netmask_to_cidr (items[i])); - } else if (i == 9) { /* Duplicate Gateway */ - if (!mm_bearer_ip_config_get_gateway (ip_config)) { - guint32 tmp = 0; - - if (!inet_pton (AF_INET, items[i], &tmp)) { - mm_warn ("Couldn't parse (duplicate) gateway address '%s'", items[i]); - g_clear_object (&ip_config); - break; - } - - if (tmp) - mm_bearer_ip_config_set_gateway (ip_config, items[i]); - } - } - } - - if (!ip_config) { - if (error) - g_simple_async_result_take_error (ctx->result, error); - else - g_simple_async_result_set_error (ctx->result, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "Couldn't get IP config: couldn't parse response '%s'", - response); - } else { - /* If we got DNS entries, set them in the IP config */ - if (dns[0]) - mm_bearer_ip_config_set_dns (ip_config, (const gchar **)dns); - - g_simple_async_result_set_op_res_gpointer (ctx->result, - ip_config, - (GDestroyNotify)g_object_unref); - } + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary), + ipv4_config, + ipv6_config); + g_simple_async_result_set_op_res_gpointer (ctx->result, + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); +out: + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); get_ip_config_context_complete_and_free (ctx); - g_strfreev (items); } static void @@ -285,7 +201,7 @@ get_ip_config_3gpp (MMBroadbandBearer *self, if (ctx->self->priv->default_ip_method == MM_BEARER_IP_METHOD_STATIC) { gchar *command; - command = g_strdup_printf ("%%IPDPADDR=%d", cid); + command = g_strdup_printf ("%%IPDPADDR=%u", cid); mm_base_modem_at_command_full (MM_BASE_MODEM (modem), primary, command, @@ -301,13 +217,28 @@ get_ip_config_3gpp (MMBroadbandBearer *self, /* Otherwise, DHCP */ if (ctx->self->priv->default_ip_method == MM_BEARER_IP_METHOD_DHCP) { - MMBearerIpConfig *ip_config; + MMBearerConnectResult *connect_result; + MMBearerIpConfig *ipv4_config, *ipv6_config; + + if (ip_family & MM_BEARER_IP_FAMILY_IPV4 || ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + } + if (ip_family & MM_BEARER_IP_FAMILY_IPV6 || ip_family & MM_BEARER_IP_FAMILY_IPV4V6) { + ipv6_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + } + g_assert (ipv4_config || ipv6_config); + + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary), + ipv4_config, + ipv6_config); + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); - ip_config = mm_bearer_ip_config_new (); - mm_bearer_ip_config_set_method (ip_config, MM_BEARER_IP_METHOD_DHCP); g_simple_async_result_set_op_res_gpointer (ctx->result, - ip_config, - (GDestroyNotify)g_object_unref); + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); get_ip_config_context_complete_and_free (ctx); return; } diff --git a/plugins/icera/mm-modem-helpers-icera.c b/plugins/icera/mm-modem-helpers-icera.c new file mode 100644 index 00000000..fd3e4fc7 --- /dev/null +++ b/plugins/icera/mm-modem-helpers-icera.c @@ -0,0 +1,277 @@ +/* -*- 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) 2012 Google, Inc. + * Copyright (C) 2012 - 2013 Aleksander Morgado + * Copyright (C) 2014 Dan Williams + */ + +#include +#include +#include +#include +#include + +#include +#define _LIBMM_INSIDE_MM +#include + +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-icera.h" + +/*****************************************************************************/ +/* %IPDPADDR response parser */ + +static MMBearerIpConfig * +parse_ipdpaddr_v4 (const gchar **items, guint num_items, GError **error) +{ + MMBearerIpConfig *config; + const gchar *dns[3] = { 0 }; + guint dns_i = 0, tmp; + const gchar *netmask = NULL; + + /* IP address and prefix */ + tmp = 0; + if (!inet_pton (AF_INET, items[1], &tmp)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse IPv4 address '%s'", items[1]); + return NULL; + } + + if (!tmp) { + /* No IPv4 config */ + return NULL; + } + + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC); + mm_bearer_ip_config_set_address (config, items[1]); + mm_bearer_ip_config_set_prefix (config, 32); /* default prefix */ + + /* Gateway */ + tmp = 0; + if (inet_pton (AF_INET, items[2], &tmp)) { + if (tmp) + mm_bearer_ip_config_set_gateway (config, items[2]); + } else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse gateway address '%s'", items[2]); + goto error; + } + + /* DNS */ + tmp = 0; + if (inet_pton (AF_INET, items[3], &tmp) && tmp) + dns[dns_i++] = items[3]; + else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse DNS address '%s'", items[3]); + goto error; + } + + /* DNS2 - sometimes missing and set to 0.0.0.0 */ + tmp = 0; + if (inet_pton (AF_INET, items[4], &tmp) && tmp) + dns[dns_i++] = items[4]; + if (dns_i > 0) + mm_bearer_ip_config_set_dns (config, (const gchar **) dns); + + /* Short form (eg, Sierra USB305) */ + if (num_items < 9) + return config; + + /* Devices return netmask and secondary gateway in one of two + * positions. The netmask may be either at index 7 or 8, while + * the secondary gateway may be at position 8 or 9. + */ + + if (items[7] && strstr (items[7], "255.") && !strstr (items[7], "255.0.0.0")) + netmask = items[7]; + if (items[8] && strstr (items[8], "255.") && !strstr (items[8], "255.0.0.0")) + netmask = items[8]; + if (netmask) { + if (!inet_pton (AF_INET, netmask, &tmp)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse netmask '%s'", + netmask); + goto error; + } + mm_bearer_ip_config_set_prefix (config, mm_netmask_to_cidr (netmask)); + } + + /* Secondary gateway */ + if (!mm_bearer_ip_config_get_gateway (config)) { + const char *gw2 = NULL; + + if (num_items >= 10 && items[9] && !strstr (items[9], "255.") && !strstr (items[9], "::")) + gw2 = items[9]; + /* Prefer position 8 */ + if (items[8] && !strstr (items[8], "255.")) + gw2 = items[8]; + + if (gw2 && inet_pton (AF_INET, gw2, &tmp) && tmp) + mm_bearer_ip_config_set_gateway (config, gw2); + else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse secondary gateway address '%s'", + gw2 ? gw2 : "(unknown)"); + goto error; + } + } + + return config; + +error: + g_object_unref (config); + return NULL; +} + +static MMBearerIpConfig * +parse_ipdpaddr_v6 (const gchar **items, guint num_items, GError **error) +{ + MMBearerIpConfig *config; + const gchar *dns[2] = { 0 }; + struct in6_addr tmp6 = IN6ADDR_ANY_INIT; + + if (num_items < 12) + return NULL; + + /* It appears that for IPv6 %IPDPADDR returns only the expected + * link-local address and a DNS address, and that to retrieve the + * default router, extra DNS, and search domains, the host must listen + * for IPv6 Router Advertisements on the net port. + */ + + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC); + + /* IP address and prefix */ + if (inet_pton (AF_INET6, items[9], &tmp6) != 1 || + IN6_IS_ADDR_UNSPECIFIED (&tmp6)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse IPv6 address '%s'", items[9]); + goto error; + } + mm_bearer_ip_config_set_address (config, items[9]); + mm_bearer_ip_config_set_prefix (config, 64); + + /* If the address is a link-local one, then SLAAC or DHCP must be used + * to get the real prefix and address. Change the method to DHCP to + * indicate this to clients. + */ + if (IN6_IS_ADDR_LINKLOCAL (&tmp6)) + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP); + + /* DNS server */ + memset (&tmp6, 0, sizeof (tmp6)); + if (inet_pton (AF_INET6, items[11], &tmp6) == 1 && + !IN6_IS_ADDR_UNSPECIFIED (&tmp6)) { + dns[0] = items[11]; + dns[1] = NULL; + mm_bearer_ip_config_set_dns (config, (const gchar **) dns); + } else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse DNS address '%s'", items[11]); + goto error; + } + + return config; + +error: + g_object_unref (config); + return NULL; +} + +#define IPDPADDR_TAG "%IPDPADDR: " + +gboolean +mm_icera_parse_ipdpaddr_response (const gchar *response, + guint expected_cid, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error) +{ + MMBearerIpConfig *ip4_config = NULL; + MMBearerIpConfig *ip6_config = NULL; + GError *local = NULL; + gboolean success; + char **items; + guint num_items, i, first_free; + gint num; + + g_return_val_if_fail (out_ip4_config, FALSE); + g_return_val_if_fail (out_ip6_config, FALSE); + + if (!response || !g_str_has_prefix (response, IPDPADDR_TAG)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing %%IPDPADDR prefix"); + return FALSE; + } + + /* %IPDPADDR: ,,,,[,,[,,,]] + * %IPDPADDR: ,,,,,,,, + * %IPDPADDR: ,,,,,,,,,,::,,::,::,::,::,:: + * + * Sierra USB305: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0 + * K3805-Z: %IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10, + * Nokia 21M: %IPDPADDR: 2, 33.196.7.127, 33.196.7.128, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 33.196.7.128, fe80::f:9135:5901, ::, fd00:976a::9, ::, ::, ::, ::, :: + * Nokia 21M: %IPDPADDR: 3, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::2e:437b:7901, ::, fd00:976a::9, ::, ::, ::, ::, :: + */ + response = mm_strip_tag (response, IPDPADDR_TAG); + items = g_strsplit_set (response, ",", 0); + + /* Strip any spaces on elements; inet_pton() doesn't like them */ + num_items = g_strv_length (items); + for (i = 0, first_free = 0; i < num_items; i++) + items[i] = g_strstrip (items[i]); + + if (num_items < 7) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Malformed IPDPADDR response (not enough items)"); + goto out; + } + + /* Validate context ID */ + if (!mm_get_int_from_str (items[0], &num) || + num != expected_cid) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unknown CID in IPDPADDR response (got %d, expected %d)", + (guint) num, + expected_cid); + goto out; + } + + ip4_config = parse_ipdpaddr_v4 ((const gchar **) items, num_items, &local); + if (local) { + g_propagate_error (error, local); + goto out; + } + + ip6_config = parse_ipdpaddr_v6 ((const gchar **) items, num_items, &local); + if (local) { + g_propagate_error (error, local); + goto out; + } + + success = TRUE; + +out: + g_strfreev (items); + + *out_ip4_config = ip4_config; + *out_ip6_config = ip6_config; + return success; +} + diff --git a/plugins/icera/mm-modem-helpers-icera.h b/plugins/icera/mm-modem-helpers-icera.h new file mode 100644 index 00000000..8a7a87a2 --- /dev/null +++ b/plugins/icera/mm-modem-helpers-icera.h @@ -0,0 +1,28 @@ +/* -*- 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) 2014 Dan Williams + */ + +#ifndef MM_MODEM_HELPERS_ICERA_H +#define MM_MODEM_HELPERS_ICERA_H + +#include "glib.h" + +/* %IPDPADDR response parser */ +gboolean mm_icera_parse_ipdpaddr_response (const gchar *response, + guint expected_cid, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error); + +#endif /* MM_MODEM_HELPERS_HUAWEI_H */ diff --git a/plugins/icera/tests/test-modem-helpers-icera.c b/plugins/icera/tests/test-modem-helpers-icera.c new file mode 100644 index 00000000..8eaff4cb --- /dev/null +++ b/plugins/icera/tests/test-modem-helpers-icera.c @@ -0,0 +1,191 @@ +/* -*- 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 + * Copyright (C) 2014 Dan Williams + */ + +#include +#include +#include +#include +#include + +#include +#define _LIBMM_INSIDE_MM +#include + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-icera.h" + +/*****************************************************************************/ +/* Test %IPDPADDR responses */ + +typedef struct { + const gchar *str; + const guint expected_cid; + + /* IPv4 */ + const gchar *ipv4_addr; + const guint ipv4_prefix; + const gchar *ipv4_gw; + const gchar *ipv4_dns1; + const gchar *ipv4_dns2; + + /* IPv6 */ + const gchar *ipv6_addr; + const gchar *ipv6_dns1; +} IpdpaddrTest; + +static const IpdpaddrTest ipdpaddr_tests[] = { + /* Sierra USB305 */ + { "%IPDPADDR: 2, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0\r\n", + 2, "21.93.217.11", 32, "21.93.217.10", "10.177.0.34", "10.161.171.220", + NULL, NULL }, + + /* ZTE/Vodafone K3805-Z */ + { "%IPDPADDR: 5, 21.93.217.11, 21.93.217.10, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10,\r\n", + 5, "21.93.217.11", 24, "21.93.217.10", "10.177.0.34", "10.161.171.220", + NULL, NULL }, + + /* Secondary gateway check */ + { "%IPDPADDR: 5, 21.93.217.11, 0.0.0.0, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 255.255.255.0, 21.93.217.10,\r\n", + 5, "21.93.217.11", 24, "21.93.217.10", "10.177.0.34", "10.161.171.220", + NULL, NULL }, + + /* Secondary gateway check #2 */ + { "%IPDPADDR: 5, 27.107.96.189, 0.0.0.0, 121.242.190.210, 121.242.190.181, 0.0.0.0, 0.0.0.0, 255.255.255.254, 27.107.96.188\r\n", + 5, "27.107.96.189", 31, "27.107.96.188", "121.242.190.210", "121.242.190.181", + NULL, NULL }, + + /* Nokia 21M */ + { "%IPDPADDR: 1, 33.196.7.127, 33.196.7.128, 10.177.0.34, 10.161.171.220, 0.0.0.0, 0.0.0.0, 255.0.0.0, 33.196.7.128, fe80::f:9135:5901, ::, fd00:976a::9, ::, ::, ::, ::, ::\r\n", + 1, "33.196.7.127", 32, "33.196.7.128", "10.177.0.34", "10.161.171.220", + "fe80::f:9135:5901", "fd00:976a::9" }, + + { "%IPDPADDR: 3, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, 0.0.0.0, fe80::2e:437b:7901, ::, fd00:976a::9, ::, ::, ::, ::, ::\r\n", + 3, NULL, 0, NULL, NULL, NULL, + "fe80::2e:437b:7901", "fd00:976a::9" }, + + /* Some development chip (cnsbg.p1001.rev2, CL477342) */ + { "%IPDPADDR: 5, 27.107.96.189, 27.107.96.188, 121.242.190.210, 121.242.190.181, 0.0.0.0, 0.0.0.0, 255.255.255.254, 27.107.96.188\r\n", + 5, "27.107.96.189", 31, "27.107.96.188", "121.242.190.210", "121.242.190.181", + NULL, NULL }, + + /* 21M with newer firmware */ + { "%IPDPADDR: 2, 188.150.116.13, 188.150.116.14, 188.149.250.16, 0.0.0.0, 0.0.0.0, 0.0.0.0, 255.255.0.0, 188.150.116.14, fe80::1:e414:eb01, ::, 2a00:e18:0:3::6, ::, ::, ::, ::, ::\r\n", + 2, "188.150.116.13", 16, "188.150.116.14", "188.149.250.16", NULL, + "fe80::1:e414:eb01", "2a00:e18:0:3::6" }, + + { NULL } +}; + +static void +test_ipdpaddr (void) +{ + guint i; + + for (i = 0; ipdpaddr_tests[i].str; i++) { + gboolean success; + GError *error = NULL; + MMBearerIpConfig *ipv4 = NULL; + MMBearerIpConfig *ipv6 = NULL; + const gchar **dns; + guint dnslen; + + success = mm_icera_parse_ipdpaddr_response ( + ipdpaddr_tests[i].str, + ipdpaddr_tests[i].expected_cid, + &ipv4, + &ipv6, + &error); + g_assert_no_error (error); + g_assert (success); + + /* IPv4 */ + if (ipdpaddr_tests[i].ipv4_addr) { + g_assert (ipv4); + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC); + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, ipdpaddr_tests[i].ipv4_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, ipdpaddr_tests[i].ipv4_prefix); + g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, ipdpaddr_tests[i].ipv4_gw); + + dns = mm_bearer_ip_config_get_dns (ipv4); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (ipdpaddr_tests[i].ipv4_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv4_dns1); + g_assert_cmpstr (dns[1], ==, ipdpaddr_tests[i].ipv4_dns2); + } else + g_assert (ipv4 == NULL); + + /* IPv6 */ + if (ipdpaddr_tests[i].ipv6_addr) { + struct in6_addr a6; + g_assert (ipv6); + + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, ipdpaddr_tests[i].ipv6_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64); + + g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6)); + if (IN6_IS_ADDR_LINKLOCAL (&a6)) + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP); + else + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC); + + dns = mm_bearer_ip_config_get_dns (ipv6); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv6_dns1); + } else + g_assert (ipv6 == NULL); + } +} + +/*****************************************************************************/ + +void +_mm_log (const char *loc, + const char *func, + guint32 level, + const char *fmt, + ...) +{ +#if defined ENABLE_TEST_MESSAGE_TRACES + /* Dummy log function */ + va_list args; + gchar *msg; + + va_start (args, fmt); + msg = g_strdup_vprintf (fmt, args); + va_end (args); + g_print ("%s\n", msg); + g_free (msg); +#endif +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/icera/ipdpaddr", test_ipdpaddr); + + return g_test_run (); +} From 6e5d013eb12d6ee134a59d4131128374af139fb3 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 18 Feb 2014 12:16:54 -0600 Subject: [PATCH 06/11] broadband-bearer-sierra: use Icera authentication for Icera devices Devices with Icera chipsets (USB305) don't support the Qualcomm proprietary $QCPDPP command, and we must use the Icera command instead. Otherwise authenticated bearer creation will fail. --- plugins/sierra/mm-broadband-bearer-sierra.c | 90 +++++++++++++++++-- plugins/sierra/mm-broadband-bearer-sierra.h | 3 + .../sierra/mm-broadband-modem-sierra-icera.c | 1 + plugins/sierra/mm-broadband-modem-sierra.c | 1 + 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/plugins/sierra/mm-broadband-bearer-sierra.c b/plugins/sierra/mm-broadband-bearer-sierra.c index dbe8a448..b4bfe109 100644 --- a/plugins/sierra/mm-broadband-bearer-sierra.c +++ b/plugins/sierra/mm-broadband-bearer-sierra.c @@ -34,6 +34,16 @@ G_DEFINE_TYPE (MMBroadbandBearerSierra, mm_broadband_bearer_sierra, MM_TYPE_BROADBAND_BEARER); +struct _MMBroadbandBearerSierraPrivate { + gboolean is_icera; +}; + +enum { + PROP_0, + PROP_IS_ICERA, + PROP_LAST +}; + /*****************************************************************************/ /* 3GPP Dialing (sub-step of the 3GPP Connection sequence) */ @@ -198,7 +208,10 @@ dial_3gpp_context_step (Dial3gppContext *ctx) if (!user || !password || allowed_auth == MM_BEARER_ALLOWED_AUTH_NONE) { mm_dbg ("Not using authentication"); - command = g_strdup_printf ("$QCPDPP=%d,0", ctx->cid); + if (ctx->self->priv->is_icera) + command = g_strdup_printf ("%%IPDPCFG=%d,0,0,\"\",\"\"", ctx->cid); + else + command = g_strdup_printf ("$QCPDPP=%d,0", ctx->cid); } else { gchar *quoted_user; gchar *quoted_password; @@ -230,11 +243,20 @@ dial_3gpp_context_step (Dial3gppContext *ctx) quoted_user = mm_port_serial_at_quote_string (user); quoted_password = mm_port_serial_at_quote_string (password); - command = g_strdup_printf ("$QCPDPP=%d,%u,%s,%s", - ctx->cid, - sierra_auth, - quoted_password, - quoted_user); + if (ctx->self->priv->is_icera) { + command = g_strdup_printf ("%%IPDPCFG=%d,0,%u,%s,%s", + ctx->cid, + sierra_auth, + quoted_user, + quoted_password); + } else { + /* Yes, password comes first... */ + command = g_strdup_printf ("$QCPDPP=%d,%u,%s,%s", + ctx->cid, + sierra_auth, + quoted_password, + quoted_user); + } g_free (quoted_user); g_free (quoted_password); } @@ -421,6 +443,8 @@ disconnect_3gpp (MMBroadbandBearer *self, /*****************************************************************************/ +#define MM_BROADBAND_BEARER_SIERRA_IS_ICERA "is-icera" + MMBearer * mm_broadband_bearer_sierra_new_finish (GAsyncResult *res, GError **error) @@ -444,6 +468,7 @@ mm_broadband_bearer_sierra_new_finish (GAsyncResult *res, void mm_broadband_bearer_sierra_new (MMBroadbandModem *modem, MMBearerProperties *config, + gboolean is_icera, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) @@ -456,21 +481,74 @@ mm_broadband_bearer_sierra_new (MMBroadbandModem *modem, user_data, MM_BEARER_MODEM, modem, MM_BEARER_CONFIG, config, + MM_BROADBAND_BEARER_SIERRA_IS_ICERA, is_icera, NULL); } +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBroadbandBearerSierra *self = MM_BROADBAND_BEARER_SIERRA (object); + + switch (prop_id) { + case PROP_IS_ICERA: + self->priv->is_icera = g_value_get_boolean (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) +{ + MMBroadbandBearerSierra *self = MM_BROADBAND_BEARER_SIERRA (object); + + switch (prop_id) { + case PROP_IS_ICERA: + g_value_set_boolean (value, self->priv->is_icera); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + static void mm_broadband_bearer_sierra_init (MMBroadbandBearerSierra *self) { + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_BEARER_SIERRA, + MMBroadbandBearerSierraPrivate); } static void mm_broadband_bearer_sierra_class_init (MMBroadbandBearerSierraClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass); + g_type_class_add_private (object_class, sizeof (MMBroadbandBearerSierraPrivate)); + + object_class->set_property = set_property; + object_class->get_property = get_property; broadband_bearer_class->dial_3gpp = dial_3gpp; broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish; broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; + + g_object_class_install_property (object_class, PROP_IS_ICERA, + g_param_spec_boolean (MM_BROADBAND_BEARER_SIERRA_IS_ICERA, + "IsIcera", + "Whether the modem uses Icera commands or not.", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } diff --git a/plugins/sierra/mm-broadband-bearer-sierra.h b/plugins/sierra/mm-broadband-bearer-sierra.h index 98b0735c..fea7b94f 100644 --- a/plugins/sierra/mm-broadband-bearer-sierra.h +++ b/plugins/sierra/mm-broadband-bearer-sierra.h @@ -36,9 +36,11 @@ typedef struct _MMBroadbandBearerSierra MMBroadbandBearerSierra; typedef struct _MMBroadbandBearerSierraClass MMBroadbandBearerSierraClass; +typedef struct _MMBroadbandBearerSierraPrivate MMBroadbandBearerSierraPrivate; struct _MMBroadbandBearerSierra { MMBroadbandBearer parent; + MMBroadbandBearerSierraPrivate *priv; }; struct _MMBroadbandBearerSierraClass { @@ -50,6 +52,7 @@ GType mm_broadband_bearer_sierra_get_type (void); /* Default 3GPP bearer creation implementation */ void mm_broadband_bearer_sierra_new (MMBroadbandModem *modem, MMBearerProperties *config, + gboolean is_icera, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); diff --git a/plugins/sierra/mm-broadband-modem-sierra-icera.c b/plugins/sierra/mm-broadband-modem-sierra-icera.c index e01ea0a0..3bfad984 100644 --- a/plugins/sierra/mm-broadband-modem-sierra-icera.c +++ b/plugins/sierra/mm-broadband-modem-sierra-icera.c @@ -87,6 +87,7 @@ modem_create_bearer (MMIfaceModem *self, mm_dbg ("Creating Sierra bearer..."); mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self), properties, + TRUE, /* is_icera */ NULL, /* cancellable */ (GAsyncReadyCallback)broadband_bearer_sierra_new_ready, result); diff --git a/plugins/sierra/mm-broadband-modem-sierra.c b/plugins/sierra/mm-broadband-modem-sierra.c index e49c84cb..d6dd965b 100644 --- a/plugins/sierra/mm-broadband-modem-sierra.c +++ b/plugins/sierra/mm-broadband-modem-sierra.c @@ -1136,6 +1136,7 @@ modem_create_bearer (MMIfaceModem *self, mm_dbg ("Creating Sierra bearer..."); mm_broadband_bearer_sierra_new (MM_BROADBAND_MODEM (self), properties, + FALSE, /* is_icera */ NULL, /* cancellable */ (GAsyncReadyCallback)broadband_bearer_sierra_new_ready, result); From 33eba0393d259b6dc97e991be6c1dc4585e15f5d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 27 Feb 2014 13:01:38 -0600 Subject: [PATCH 07/11] api: update documentation about IPv6 bearer methods Even if the IPv6 bearer method is DHCP, the client should use SLAAC (eg, IPv6 Router Solicitations, Router Adverisements, and possibly DHCPv6) to determine the correct prefix and DNS information. In some cases, even with the DHCP method, and IPv6 "address" will be given which should be assigned to the data port before beginning an IP addressing as the mobile network may expect this address to be used. --- include/ModemManager-enums.h | 8 ++++++-- .../org.freedesktop.ModemManager1.Bearer.xml | 11 ++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/ModemManager-enums.h b/include/ModemManager-enums.h index ee09ddc0..5ff799f0 100644 --- a/include/ModemManager-enums.h +++ b/include/ModemManager-enums.h @@ -849,8 +849,12 @@ typedef enum { /*< underscore_name=mm_modem_contacts_storage >*/ * MMBearerIpMethod: * @MM_BEARER_IP_METHOD_UNKNOWN: Unknown method. * @MM_BEARER_IP_METHOD_PPP: Use PPP to get the address. - * @MM_BEARER_IP_METHOD_STATIC: Use the provided static IP configuration given by the modem to configure the IP data interface. - * @MM_BEARER_IP_METHOD_DHCP: Begin DHCP on the data interface to obtain necessary IP configuration details. + * @MM_BEARER_IP_METHOD_STATIC: Use the provided static IP configuration given + * by the modem to configure the IP data interface. + * @MM_BEARER_IP_METHOD_DHCP: Begin DHCP or IPv6 SLAAC on the data interface to + * obtain necessary IP configuration details. For IPv4 bearers DHCP should + * be used. For IPv6 bearers SLAAC should be used to determine the prefix and + * any additional details. * * Type of IP method configuration to be used in a given Bearer. */ diff --git a/introspection/org.freedesktop.ModemManager1.Bearer.xml b/introspection/org.freedesktop.ModemManager1.Bearer.xml index 1d2f287b..e1463a56 100644 --- a/introspection/org.freedesktop.ModemManager1.Bearer.xml +++ b/introspection/org.freedesktop.ModemManager1.Bearer.xml @@ -186,10 +186,15 @@ - If the bearer specifies configuration via PPP or DHCP, only the - "method" item will be present. + If the bearer specifies configuration via PPP or DHCP, often only the + "method" item will be present. IPv6 SLAAC should + be used to retrieve correct addressing and DNS information via Router + Advertisements and DHCPv6. In some cases an IPv6 Link-Local + "address" item will be present, which should be + assigned to the data port before performing SLAAC, as the mobile network + may expect SLAAC setup to use this address. - Additional items which are only applicable when using the + Additional items which are usually only applicable when using the MM_BEARER_IP_METHOD_STATIC method are: From 7673a1d3a35c3e0a26301b5c605b2f68d4b58de1 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 28 Feb 2014 10:22:28 -0600 Subject: [PATCH 08/11] bearer-mbim: use DHCP method when only an IPv6 link-local address is found When only given a link-local address, then SLAAC must be used to retrieve the real prefix. --- src/mm-bearer-mbim.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mm-bearer-mbim.c b/src/mm-bearer-mbim.c index b1e3176c..c25b2811 100644 --- a/src/mm-bearer-mbim.c +++ b/src/mm-bearer-mbim.c @@ -370,6 +370,14 @@ ip_configuration_query_ready (MbimDevice *device, str = g_inet_address_to_string (addr); mm_bearer_ip_config_set_address (ipv6_config, str); g_free (str); + + /* If the address is a link-local one, then SLAAC or DHCP must be used + * to get the real prefix and address. Change the method to DHCP to + * indicate this to clients. + */ + if (g_inet_address_get_is_link_local (addr)) + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + g_object_unref (addr); /* Netmask */ From 801762835309bdaeb6c6a9758b41c58e03cbdf38 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 4 Mar 2014 09:26:03 -0600 Subject: [PATCH 09/11] cli: print IP details for all valid bearer types Even DHCP or PPP may have some IP details, like the link-local address for IPv6 bearers. --- cli/mmcli-bearer.c | 64 ++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/cli/mmcli-bearer.c b/cli/mmcli-bearer.c index c54ac54c..5829e60a 100644 --- a/cli/mmcli-bearer.c +++ b/cli/mmcli-bearer.c @@ -189,27 +189,29 @@ print_bearer_info (MMBearer *bearer) (ipv4_config ? mm_bearer_ip_method_get_string (mm_bearer_ip_config_get_method (ipv4_config)) : "none")); - if (ipv4_config) { - guint mtu; + if (ipv4_config && + mm_bearer_ip_config_get_method (ipv4_config) != MM_BEARER_IP_METHOD_UNKNOWN) { + const gchar **dns = mm_bearer_ip_config_get_dns (ipv4_config); + guint i, mtu; - if (mm_bearer_ip_config_get_method (ipv4_config) == MM_BEARER_IP_METHOD_STATIC) { - const gchar **dns; - guint i; + g_print (" | address: '%s'\n" + " | prefix: '%u'\n" + " | gateway: '%s'\n", + VALIDATE_UNKNOWN (mm_bearer_ip_config_get_address (ipv4_config)), + mm_bearer_ip_config_get_prefix (ipv4_config), + VALIDATE_UNKNOWN (mm_bearer_ip_config_get_gateway (ipv4_config))); - dns = mm_bearer_ip_config_get_dns (ipv4_config); - g_print (" | address: '%s'\n" - " | prefix: '%u'\n" - " | gateway: '%s'\n" - " | DNS: '%s'", - VALIDATE_UNKNOWN (mm_bearer_ip_config_get_address (ipv4_config)), - mm_bearer_ip_config_get_prefix (ipv4_config), - VALIDATE_UNKNOWN (mm_bearer_ip_config_get_gateway (ipv4_config)), - VALIDATE_UNKNOWN (dns[0])); + if (dns && dns[0]) { + g_print ( + " | DNS: '%s'", dns[0]); /* Additional DNS addresses */ for (i = 1; dns[i]; i++) g_print (", '%s'", dns[i]); - g_print ("\n"); + } else { + g_print ( + " | DNS: none"); } + g_print ("\n"); mtu = mm_bearer_ip_config_get_mtu (ipv4_config); if (mtu) @@ -222,27 +224,29 @@ print_bearer_info (MMBearer *bearer) (ipv6_config ? mm_bearer_ip_method_get_string (mm_bearer_ip_config_get_method (ipv6_config)) : "none")); - if (ipv6_config) { - guint mtu; + if (ipv6_config && + mm_bearer_ip_config_get_method (ipv6_config) != MM_BEARER_IP_METHOD_UNKNOWN) { + const gchar **dns = mm_bearer_ip_config_get_dns (ipv6_config); + guint i, mtu; - if (mm_bearer_ip_config_get_method (ipv6_config) == MM_BEARER_IP_METHOD_STATIC) { - const gchar **dns; - guint i; + g_print (" | address: '%s'\n" + " | prefix: '%u'\n" + " | gateway: '%s'\n", + VALIDATE_UNKNOWN(mm_bearer_ip_config_get_address (ipv6_config)), + mm_bearer_ip_config_get_prefix (ipv6_config), + VALIDATE_UNKNOWN(mm_bearer_ip_config_get_gateway (ipv6_config))); - dns = mm_bearer_ip_config_get_dns (ipv6_config); - g_print (" | address: '%s'\n" - " | prefix: '%u'\n" - " | gateway: '%s'\n" - " | DNS: '%s'", - VALIDATE_UNKNOWN(mm_bearer_ip_config_get_address (ipv6_config)), - mm_bearer_ip_config_get_prefix (ipv6_config), - VALIDATE_UNKNOWN(mm_bearer_ip_config_get_gateway (ipv6_config)), - VALIDATE_UNKNOWN(dns[0])); + if (dns && dns[0]) { + g_print ( + " | DNS: '%s'", dns[0]); /* Additional DNS addresses */ for (i = 1; dns[i]; i++) g_print (", '%s'", dns[i]); - g_print ("\n"); + } else { + g_print ( + " | DNS: none"); } + g_print ("\n"); mtu = mm_bearer_ip_config_get_mtu (ipv6_config); if (mtu) From ca0a567f74f30a719f11b749165731630528834a Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 15 Apr 2014 15:22:11 -0500 Subject: [PATCH 10/11] broadband-bearer-qmi: support IPv6 configuration and use static for IPv4 --- src/mm-bearer-qmi.c | 306 ++++++++++++++++++++++++++++---------------- 1 file changed, 195 insertions(+), 111 deletions(-) diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c index 4c693d24..41fdc6eb 100644 --- a/src/mm-bearer-qmi.c +++ b/src/mm-bearer-qmi.c @@ -31,6 +31,7 @@ #include "mm-modem-helpers-qmi.h" #include "mm-port-enums-types.h" #include "mm-log.h" +#include "mm-modem-helpers.h" G_DEFINE_TYPE (MMBearerQmi, mm_bearer_qmi, MM_TYPE_BEARER); @@ -82,12 +83,14 @@ typedef struct { gboolean running_ipv4; QmiClientWds *client_ipv4; guint32 packet_data_handle_ipv4; + MMBearerIpConfig *ipv4_config; GError *error_ipv4; gboolean ipv6; gboolean running_ipv6; QmiClientWds *client_ipv6; guint32 packet_data_handle_ipv6; + MMBearerIpConfig *ipv6_config; GError *error_ipv6; } ConnectContext; @@ -99,14 +102,12 @@ connect_context_complete_and_free (ConnectContext *ctx) g_free (ctx->apn); g_free (ctx->user); g_free (ctx->password); - if (ctx->error_ipv4) - g_error_free (ctx->error_ipv4); - if (ctx->error_ipv6) - g_error_free (ctx->error_ipv6); - if (ctx->client_ipv4) - g_object_unref (ctx->client_ipv4); - if (ctx->client_ipv6) - g_object_unref (ctx->client_ipv6); + g_clear_error (&ctx->error_ipv4); + g_clear_error (&ctx->error_ipv6); + g_clear_object (&ctx->client_ipv4); + g_clear_object (&ctx->client_ipv6); + g_clear_object (&ctx->ipv4_config); + g_clear_object (&ctx->ipv6_config); g_object_unref (ctx->data); g_object_unref (ctx->qmi); g_object_unref (ctx->cancellable); @@ -247,51 +248,191 @@ build_start_network_input (ConnectContext *ctx) } static void -print_address4 (gboolean success, const char *tag, guint32 address, GError *error) +qmi_inet4_ntop (guint32 address, char *buf, const gsize buflen) { struct in_addr a = { .s_addr = GUINT32_TO_BE (address) }; - char buf[INET_ADDRSTRLEN + 1]; - if (success) { - memset (buf, 0, sizeof (buf)); - if (inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)) - mm_dbg (" %s: %s", tag, buf); - else - mm_dbg (" %s: failed (address conversion error)", tag); - return; + g_assert (buflen >= INET_ADDRSTRLEN); + + /* We can ignore inet_ntop() return value if 'buf' is + * at least INET_ADDRSTRLEN in size. */ + memset (buf, 0, buflen); + g_assert (inet_ntop (AF_INET, &a, buf, buflen)); +} + +static MMBearerIpConfig * +get_ipv4_config (QmiMessageWdsGetCurrentSettingsOutput *output, guint32 mtu) +{ + MMBearerIpConfig *config; + char buf[INET_ADDRSTRLEN]; + char buf2[INET_ADDRSTRLEN]; + const gchar *dns[3] = { 0 }; + guint dns_idx = 0; + guint32 addr = 0; + GError *error = NULL; + guint32 prefix = 0; + + /* IPv4 subnet mask */ + if (!qmi_message_wds_get_current_settings_output_get_ipv4_gateway_subnet_mask (output, &addr, &error)) { + mm_dbg ("Failed to read IPv4 netmask (%s)", error->message); + g_clear_error (&error); + return NULL; } - mm_dbg (" %s: failed (%s)", tag, error ? error->message : "unknown"); + qmi_inet4_ntop (addr, buf, sizeof (buf)); + prefix = mm_netmask_to_cidr (buf); + + /* IPv4 address */ + if (!qmi_message_wds_get_current_settings_output_get_ipv4_address (output, &addr, &error)) { + mm_dbg ("IPv4 family but no IPv4 address (%s)", error->message); + g_clear_error (&error); + return NULL; + } + + mm_dbg ("QMI IPv4 Settings:"); + + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC); + + /* IPv4 address */ + qmi_inet4_ntop (addr, buf, sizeof (buf)); + mm_bearer_ip_config_set_address (config, buf); + mm_bearer_ip_config_set_prefix (config, prefix); + mm_dbg (" Address: %s/%d", buf, prefix); + + /* IPv4 gateway address */ + if (qmi_message_wds_get_current_settings_output_get_ipv4_gateway_address (output, &addr, &error)) { + qmi_inet4_ntop (addr, buf, sizeof (buf)); + mm_bearer_ip_config_set_gateway (config, buf); + mm_dbg (" Gateway: %s", buf); + } else { + mm_dbg (" Gateway: failed (%s)", error->message); + g_clear_error (&error); + } + + /* IPv4 DNS #1 */ + if (qmi_message_wds_get_current_settings_output_get_primary_ipv4_dns_address (output, &addr, &error)) { + qmi_inet4_ntop (addr, buf, sizeof (buf)); + dns[dns_idx++] = buf; + mm_dbg (" DNS #1: %s", buf); + } else { + mm_dbg (" DNS #1: failed (%s)", error->message); + g_clear_error (&error); + } + + /* IPv6 DNS #2 */ + if (qmi_message_wds_get_current_settings_output_get_secondary_ipv4_dns_address (output, &addr, &error)) { + qmi_inet4_ntop (addr, buf2, sizeof (buf2)); + dns[dns_idx++] = buf2; + mm_dbg (" DNS #2: %s", buf2); + } else { + mm_dbg (" DNS #2: failed (%s)", error->message); + g_clear_error (&error); + } + + if (dns_idx > 0) + mm_bearer_ip_config_set_dns (config, (const gchar **) &dns); + + if (mtu) { + mm_bearer_ip_config_set_mtu (config, mtu); + mm_dbg (" MTU: %d", mtu); + } + + return config; } static void -print_address6 (gboolean success, - const char *tag, - GArray *array, - guint32 prefix, - GError *error) +qmi_inet6_ntop (GArray *array, char *buf, const gsize buflen) { struct in6_addr a; - char buf[INET6_ADDRSTRLEN + 1]; guint32 i; - if (success) { - g_assert (array); - g_assert (array->len == 8); + g_assert (array); + g_assert (array->len == 8); + g_assert (buflen >= INET6_ADDRSTRLEN); - memset (buf, 0, sizeof (buf)); - for (i = 0; i < array->len; i++) - a.s6_addr16[i] = GUINT16_TO_BE (g_array_index (array, guint16, i)); + for (i = 0; i < array->len; i++) + a.s6_addr16[i] = GUINT16_TO_BE (g_array_index (array, guint16, i)); - if (inet_ntop (AF_INET6, &a, buf, sizeof (buf) - 1)) - mm_dbg (" %s: %s/%d", tag, buf, prefix); - else - mm_dbg (" %s: failed (address conversion error)", tag); + /* We can ignore inet_ntop() return value if 'buf' is + * at least INET6_ADDRSTRLEN in size. */ + memset (buf, 0, buflen); + g_assert (inet_ntop (AF_INET6, &a, buf, buflen)); +} - g_array_free (array, TRUE); - return; +static MMBearerIpConfig * +get_ipv6_config (QmiMessageWdsGetCurrentSettingsOutput *output, guint32 mtu) +{ + MMBearerIpConfig *config; + char buf[INET6_ADDRSTRLEN]; + char buf2[INET6_ADDRSTRLEN]; + const gchar *dns[3] = { 0 }; + guint dns_idx = 0; + GArray *array; + GError *error = NULL; + guint8 prefix = 0; + + /* If the message has an IPv6 address, create an IPv6 bearer config */ + if (!qmi_message_wds_get_current_settings_output_get_ipv6_address (output, &array, &prefix, &error)) { + mm_dbg ("IPv6 family but no IPv6 address (%s)", error->message); + g_clear_error (&error); + return NULL; } - mm_dbg (" %s: failed (%s)", tag, error ? error->message : "unknown"); + mm_dbg ("QMI IPv6 Settings:"); + + config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_STATIC); + + /* IPv6 address */ + qmi_inet6_ntop (array, buf, sizeof (buf)); + g_array_unref (array); + + mm_bearer_ip_config_set_address (config, buf); + mm_bearer_ip_config_set_prefix (config, prefix); + mm_dbg (" Address: %s/%d", buf, prefix); + + /* IPv6 gateway address */ + if (qmi_message_wds_get_current_settings_output_get_ipv6_gateway_address (output, &array, &prefix, &error)) { + qmi_inet6_ntop (array, buf, sizeof (buf)); + mm_bearer_ip_config_set_gateway (config, buf); + mm_dbg (" Gateway: %s", buf); + g_array_unref (array); + } else { + mm_dbg (" Gateway: failed (%s)", error->message); + g_clear_error (&error); + } + + /* IPv6 DNS #1 */ + if (qmi_message_wds_get_current_settings_output_get_ipv6_primary_dns_address (output, &array, &error)) { + qmi_inet6_ntop (array, buf, sizeof (buf)); + dns[dns_idx++] = buf; + mm_dbg (" DNS #1: %s", buf); + g_array_unref (array); + } else { + mm_dbg (" DNS #1: failed (%s)", error->message); + g_clear_error (&error); + } + + /* IPv6 DNS #2 */ + if (qmi_message_wds_get_current_settings_output_get_ipv6_secondary_dns_address (output, &array, &error)) { + qmi_inet6_ntop (array, buf2, sizeof (buf2)); + dns[dns_idx++] = buf2; + mm_dbg (" DNS #2: %s", buf2); + g_array_unref (array); + } else { + mm_dbg (" DNS #2: failed (%s)", error->message); + g_clear_error (&error); + } + + if (dns_idx > 0) + mm_bearer_ip_config_set_dns (config, (const gchar **) &dns); + + if (mtu) { + mm_bearer_ip_config_set_mtu (config, mtu); + mm_dbg (" MTU: %d", mtu); + } + + return config; } static void @@ -312,73 +453,33 @@ get_current_settings_ready (QmiClientWds *client, mm_info ("error: couldn't get current settings: %s", error->message); g_error_free (error); } else { - gboolean success; - guint32 addr, mtu, i; + QmiWdsIpFamily ip_family = QMI_WDS_IP_FAMILY_UNSPECIFIED; + guint32 mtu = 0; GArray *array; - guint8 prefix; - /* If the message has an IPv4 address, print IPv4 settings */ - if (qmi_message_wds_get_current_settings_output_get_ipv4_address (output, &addr, &error)) { - mm_dbg ("QMI IPv4 Settings:"); - - /* IPv4 address */ - print_address4 (TRUE, "Address", addr, error); + if (!qmi_message_wds_get_current_settings_output_get_ip_family (output, &ip_family, &error)) { + mm_dbg (" IP Family: failed (%s); assuming IPv4", error->message); g_clear_error (&error); + ip_family = QMI_WDS_IP_FAMILY_IPV4; + } + mm_dbg (" IP Family: %s", + (ip_family == QMI_WDS_IP_FAMILY_IPV4) ? "IPv4" : + (ip_family == QMI_WDS_IP_FAMILY_IPV6) ? "IPv6" : "unknown"); - /* IPv4 gateway address */ - success = qmi_message_wds_get_current_settings_output_get_ipv4_gateway_address (output, &addr, &error); - print_address4 (success, "Gateway", addr, error); - g_clear_error (&error); - - /* IPv4 subnet mask */ - success = qmi_message_wds_get_current_settings_output_get_ipv4_gateway_subnet_mask (output, &addr, &error); - print_address4 (success, "Netmask", addr, error); - g_clear_error (&error); - - /* IPv4 DNS #1 */ - success = qmi_message_wds_get_current_settings_output_get_primary_ipv4_dns_address (output, &addr, &error); - print_address4 (success, " DNS #1", addr, error); - g_clear_error (&error); - - /* IPv4 DNS #2 */ - success = qmi_message_wds_get_current_settings_output_get_secondary_ipv4_dns_address (output, &addr, &error); - print_address4 (success, " DNS #2", addr, error); - g_clear_error (&error); - } else { - /* no IPv4 configuration */ + if (!qmi_message_wds_get_current_settings_output_get_mtu (output, &mtu, &error)) { + mm_dbg (" MTU: failed (%s)", error->message); g_clear_error (&error); } - /* If the message has an IPv6 address, print IPv6 settings */ - if (qmi_message_wds_get_current_settings_output_get_ipv6_address (output, &array, &prefix, &error)) { - mm_dbg ("QMI IPv6 Settings:"); - - /* IPv6 address */ - print_address6 (TRUE, "Address", array, prefix, error); - g_clear_error (&error); - - /* IPv6 gateway address */ - success = qmi_message_wds_get_current_settings_output_get_ipv6_gateway_address (output, &array, &prefix, &error); - print_address6 (success, "Gateway", array, prefix, error); - g_clear_error (&error); - - /* IPv6 DNS #1 */ - success = qmi_message_wds_get_current_settings_output_get_ipv6_primary_dns_address (output, &array, &error); - print_address6 (success, " DNS #1", array, 0, error); - g_clear_error (&error); - - /* IPv6 DNS #2 */ - success = qmi_message_wds_get_current_settings_output_get_ipv6_secondary_dns_address (output, &array, &error); - print_address6 (success, " DNS #2", array, 0, error); - g_clear_error (&error); - } else { - /* no IPv6 configuration */ - g_clear_error (&error); - } + if (ip_family == QMI_WDS_IP_FAMILY_IPV4) + ctx->ipv4_config = get_ipv4_config (output, mtu); + else if (ip_family == QMI_WDS_IP_FAMILY_IPV6) + ctx->ipv6_config = get_ipv6_config (output, mtu); /* Domain names */ if (qmi_message_wds_get_current_settings_output_get_domain_name_list (output, &array, &error)) { GString *s = g_string_sized_new (array ? (array->len * 20) : 1); + guint i; for (i = 0; array && (i < array->len); i++) { if (s->len) @@ -391,13 +492,6 @@ get_current_settings_ready (QmiClientWds *client, mm_dbg (" Domains: failed (%s)", error ? error->message : "unknown"); g_clear_error (&error); } - - if (qmi_message_wds_get_current_settings_output_get_mtu (output, &mtu, &error)) - mm_dbg (" MTU: %d", mtu); - else { - mm_dbg (" MTU: failed (%s)", error ? error->message : "unknown"); - g_clear_error (&error); - } } if (output) @@ -728,8 +822,6 @@ connect_context_step (ConnectContext *ctx) case CONNECT_STEP_LAST: /* If one of IPv4 or IPv6 succeeds, we're connected */ if (ctx->packet_data_handle_ipv4 || ctx->packet_data_handle_ipv6) { - MMBearerIpConfig *config; - /* Port is connected; update the state */ mm_port_set_connected (MM_PORT (ctx->data), TRUE); @@ -751,19 +843,11 @@ connect_context_step (ConnectContext *ctx) ctx->self->priv->client_ipv6 = g_object_ref (ctx->client_ipv6); } - /* Build IP config; always DHCP based */ - config = mm_bearer_ip_config_new (); - mm_bearer_ip_config_set_method (config, MM_BEARER_IP_METHOD_DHCP); - /* Set operation result */ g_simple_async_result_set_op_res_gpointer ( ctx->result, - mm_bearer_connect_result_new ( - ctx->data, - ctx->packet_data_handle_ipv4 ? config : NULL, - ctx->packet_data_handle_ipv6 ? config : NULL), + mm_bearer_connect_result_new (ctx->data, ctx->ipv4_config, ctx->ipv6_config), (GDestroyNotify)mm_bearer_connect_result_unref); - g_object_unref (config); } else { GError *error; From 02f9b926cd5776835bcd7c2b77c452e2ff8169d9 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 10 Jun 2014 01:18:41 -0500 Subject: [PATCH 11/11] broadband-bearer-mbm: support IPv6 DNS and use static IPv4 configuration --- plugins/Makefile.am | 16 ++ plugins/mbm/mm-broadband-bearer-mbm.c | 161 ++++++++++++++++++++ plugins/mbm/mm-modem-helpers-mbm.c | 166 +++++++++++++++++++++ plugins/mbm/mm-modem-helpers-mbm.h | 27 ++++ plugins/mbm/tests/test-modem-helpers-mbm.c | 162 ++++++++++++++++++++ 5 files changed, 532 insertions(+) create mode 100644 plugins/mbm/mm-modem-helpers-mbm.c create mode 100644 plugins/mbm/mm-modem-helpers-mbm.h create mode 100644 plugins/mbm/tests/test-modem-helpers-mbm.c diff --git a/plugins/Makefile.am b/plugins/Makefile.am index bf44ceb6..a45b70d7 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -1,3 +1,4 @@ + include $(top_srcdir)/gtester.make # Common CPPFLAGS and LDFLAGS @@ -212,12 +213,27 @@ libmm_plugin_mbm_la_SOURCES = \ mbm/mm-broadband-modem-mbm.h \ mbm/mm-broadband-bearer-mbm.c \ mbm/mm-broadband-bearer-mbm.h \ + mbm/mm-modem-helpers-mbm.c \ + mbm/mm-modem-helpers-mbm.h \ mbm/mm-sim-mbm.c \ mbm/mm-sim-mbm.h libmm_plugin_mbm_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) libmm_plugin_mbm_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS) udevrules_DATA += mbm/77-mm-ericsson-mbm.rules +noinst_PROGRAMS += test-modem-helpers-mbm +test_modem_helpers_mbm_SOURCES = \ + mbm/mm-modem-helpers-mbm.c \ + mbm/mm-modem-helpers-mbm.h \ + mbm/tests/test-modem-helpers-mbm.c +test_modem_helpers_mbm_CPPFLAGS = \ + -I$(top_srcdir)/plugins/mbm \ + $(PLUGIN_COMMON_COMPILER_FLAGS) +test_modem_helpers_mbm_LDADD = \ + $(top_builddir)/libmm-glib/libmm-glib.la \ + $(top_builddir)/src/libmodem-helpers.la +test_modem_helpers_mbm_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS) + # Option libmm_plugin_option_la_SOURCES = \ option/mm-plugin-option.c \ diff --git a/plugins/mbm/mm-broadband-bearer-mbm.c b/plugins/mbm/mm-broadband-bearer-mbm.c index ed786439..9e5e838e 100644 --- a/plugins/mbm/mm-broadband-bearer-mbm.c +++ b/plugins/mbm/mm-broadband-bearer-mbm.c @@ -39,6 +39,7 @@ #include "mm-broadband-bearer-mbm.h" #include "mm-log.h" #include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" #include "mm-daemon-enums-types.h" G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_BEARER); @@ -465,6 +466,164 @@ dial_3gpp (MMBroadbandBearer *self, authenticate (ctx); } +/*****************************************************************************/ +/* 3GPP IP config retrieval (sub-step of the 3GPP Connection sequence) */ + +typedef struct { + MMBroadbandBearerMbm *self; + MMBaseModem *modem; + MMPortSerialAt *primary; + MMBearerIpFamily family; + GSimpleAsyncResult *result; +} GetIpConfig3gppContext; + +static GetIpConfig3gppContext * +get_ip_config_3gpp_context_new (MMBroadbandBearerMbm *self, + MMBaseModem *modem, + MMPortSerialAt *primary, + MMBearerIpFamily family, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GetIpConfig3gppContext *ctx; + + ctx = g_new0 (GetIpConfig3gppContext, 1); + ctx->self = g_object_ref (self); + ctx->modem = g_object_ref (modem); + ctx->primary = g_object_ref (primary); + ctx->family = family; + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + get_ip_config_3gpp_context_new); + return ctx; +} + +static void +get_ip_config_context_complete_and_free (GetIpConfig3gppContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->primary); + g_object_unref (ctx->modem); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +get_ip_config_3gpp_finish (MMBroadbandBearer *self, + GAsyncResult *res, + MMBearerIpConfig **ipv4_config, + MMBearerIpConfig **ipv6_config, + GError **error) +{ + MMBearerConnectResult *configs; + MMBearerIpConfig *ipv4, *ipv6; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return FALSE; + + configs = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); + g_assert (configs); + + ipv4 = mm_bearer_connect_result_peek_ipv4_config (configs); + ipv6 = mm_bearer_connect_result_peek_ipv6_config (configs); + g_assert (ipv4 || ipv6); + if (ipv4_config && ipv4) + *ipv4_config = g_object_ref (ipv4); + if (ipv6_config && ipv6) + *ipv6_config = g_object_ref (ipv6); + + return TRUE; +} + +static void +ip_config_ready (MMBaseModem *modem, + GAsyncResult *res, + GetIpConfig3gppContext *ctx) +{ + MMBearerIpConfig *ipv4_config = NULL; + MMBearerIpConfig *ipv6_config = NULL; + const gchar *response; + GError *error = NULL; + MMBearerConnectResult *connect_result; + + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (error) { + g_error_free (error); + + /* Fall back to DHCP configuration; early devices don't support *E2IPCFG */ + if (ctx->family == MM_BEARER_IP_FAMILY_IPV4 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) { + ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + } + if (ctx->family == MM_BEARER_IP_FAMILY_IPV6 || ctx->family == MM_BEARER_IP_FAMILY_IPV4V6) { + ipv6_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + } + } else { + if (!mm_mbm_parse_e2ipcfg_response (response, + &ipv4_config, + &ipv6_config, + &error)) { + g_simple_async_result_take_error (ctx->result, error); + goto out; + } + + if (!ipv4_config && !ipv6_config) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't get IP config: couldn't parse response '%s'", + response); + goto out; + } + } + + connect_result = mm_bearer_connect_result_new (MM_PORT (ctx->primary), + ipv4_config, + ipv6_config); + g_simple_async_result_set_op_res_gpointer (ctx->result, + connect_result, + (GDestroyNotify)mm_bearer_connect_result_unref); + +out: + g_clear_object (&ipv4_config); + g_clear_object (&ipv6_config); + get_ip_config_context_complete_and_free (ctx); +} + +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) +{ + GetIpConfig3gppContext *ctx; + + ctx = get_ip_config_3gpp_context_new (MM_BROADBAND_BEARER_MBM (self), + MM_BASE_MODEM (modem), + primary, + ip_family, + callback, + user_data); + + mm_base_modem_at_command_full (MM_BASE_MODEM (modem), + primary, + "*E2IPCFG?", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)ip_config_ready, + ctx); +} + /*****************************************************************************/ /* 3GPP disconnect */ @@ -608,6 +767,8 @@ mm_broadband_bearer_mbm_class_init (MMBroadbandBearerMbmClass *klass) bearer_class->report_connection_status = report_connection_status; 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; broadband_bearer_class->disconnect_3gpp = disconnect_3gpp; broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish; } diff --git a/plugins/mbm/mm-modem-helpers-mbm.c b/plugins/mbm/mm-modem-helpers-mbm.c new file mode 100644 index 00000000..42653d88 --- /dev/null +++ b/plugins/mbm/mm-modem-helpers-mbm.c @@ -0,0 +1,166 @@ +/* -*- 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) 2012 Google, Inc. + * Copyright (C) 2012 - 2013 Aleksander Morgado + * Copyright (C) 2014 Dan Williams + */ + +#include +#include +#include +#include +#include + +#include +#define _LIBMM_INSIDE_MM +#include + +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" + +/*****************************************************************************/ +/* *E2IPCFG response parser */ + +static gboolean +validate_address (int family, const char *addr) +{ + struct in6_addr tmp6 = IN6ADDR_ANY_INIT; + + if (inet_pton (family, addr, (void *) &tmp6) != 1) +{ +g_message ("%s: famil '%s'", __func__, addr); + return FALSE; +} + if ((family == AF_INET6) && IN6_IS_ADDR_UNSPECIFIED (&tmp6)) + return FALSE; + return TRUE; +} + +#define E2IPCFG_TAG "*E2IPCFG" + +gboolean +mm_mbm_parse_e2ipcfg_response (const gchar *response, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error) +{ + MMBearerIpConfig **ip_config = NULL; + gboolean got_address = FALSE, got_gw = FALSE, got_dns = FALSE; + GRegex *r; + GMatchInfo *match_info = NULL; + GError *match_error = NULL; + gchar *dns[3] = { 0 }; + guint dns_idx = 0; + int family = AF_INET; + MMBearerIpMethod method = MM_BEARER_IP_METHOD_STATIC; + + g_return_val_if_fail (out_ip4_config, FALSE); + g_return_val_if_fail (out_ip6_config, FALSE); + + if (!response || !g_str_has_prefix (response, E2IPCFG_TAG)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing " E2IPCFG_TAG " prefix"); + return FALSE; + } + + response = mm_strip_tag (response, "*E2IPCFG: "); + + if (strchr (response, ':')) { + family = AF_INET6; + ip_config = out_ip6_config; + method = MM_BEARER_IP_METHOD_DHCP; + } else if (strchr (response, '.')) { + family = AF_INET; + ip_config = out_ip4_config; + method = MM_BEARER_IP_METHOD_STATIC; + } else { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Failed to detect " E2IPCFG_TAG " address family"); + return FALSE; + } + + /* *E2IPCFG: (1,)(2,)(3,)(3,) + * + * *E2IPCFG: (1,"46.157.32.246")(2,"46.157.32.243")(3,"193.213.112.4")(3,"130.67.15.198") + * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0000:e537:1801")(3,"2001:4600:0004:0fff:0000:0000:0000:0054")(3,"2001:4600:0004:1fff:0000:0000:0000:0054") + * *E2IPCFG: (1,"fe80:0000:0000:0000:0000:0027:b7fe:9401")(3,"fd00:976a:0000:0000:0000:0000:0000:0009") + */ + r = g_regex_new ("\\((\\d),\"([0-9a-fA-F.:]+)\"\\)", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse " E2IPCFG_TAG " results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match " E2IPCFG_TAG " reply"); + } + goto done; + } + + *ip_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (*ip_config, method); + while (g_match_info_matches (match_info)) { + char *id = g_match_info_fetch (match_info, 1); + char *str = g_match_info_fetch (match_info, 2); + + switch (atoi (id)) { + case 1: + if (validate_address (family, str)) { + mm_bearer_ip_config_set_address (*ip_config, str); + mm_bearer_ip_config_set_prefix (*ip_config, (family == AF_INET6) ? 64 : 28); + got_address = TRUE; + } + break; + case 2: + if ((family == AF_INET) && validate_address (family, str)) { + mm_bearer_ip_config_set_gateway (*ip_config, str); + got_gw = TRUE; + } + break; + case 3: + if (validate_address (family, str)) { + dns[dns_idx++] = g_strdup (str); + got_dns = TRUE; + } + break; + default: + break; + } + g_free (id); + g_free (str); + g_match_info_next (match_info, NULL); + } + + if (got_dns) { + mm_bearer_ip_config_set_dns (*ip_config, (const gchar **) dns); + g_free (dns[0]); + g_free (dns[1]); + } + + if (!got_address || (family == AF_INET && !got_gw)) { + g_object_unref (*ip_config); + *ip_config = NULL; + g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Got incomplete IP configuration from " E2IPCFG_TAG); + } + +done: + if (match_info) + g_match_info_free (match_info); + g_regex_unref (r); + return !!*ip_config; +} + diff --git a/plugins/mbm/mm-modem-helpers-mbm.h b/plugins/mbm/mm-modem-helpers-mbm.h new file mode 100644 index 00000000..ef15845d --- /dev/null +++ b/plugins/mbm/mm-modem-helpers-mbm.h @@ -0,0 +1,27 @@ +/* -*- 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) 2014 Dan Williams + */ + +#ifndef MM_MODEM_HELPERS_MBM_H +#define MM_MODEM_HELPERS_MBM_H + +#include "glib.h" + +/* *E2IPCFG response parser */ +gboolean mm_mbm_parse_e2ipcfg_response (const gchar *response, + MMBearerIpConfig **out_ip4_config, + MMBearerIpConfig **out_ip6_config, + GError **error); + +#endif /* MM_MODEM_HELPERS_MBM_H */ diff --git a/plugins/mbm/tests/test-modem-helpers-mbm.c b/plugins/mbm/tests/test-modem-helpers-mbm.c new file mode 100644 index 00000000..2e6dd1a3 --- /dev/null +++ b/plugins/mbm/tests/test-modem-helpers-mbm.c @@ -0,0 +1,162 @@ +/* -*- 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 + * Copyright (C) 2014 Dan Williams + */ + +#include +#include +#include +#include +#include + +#include +#define _LIBMM_INSIDE_MM +#include + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" + +/*****************************************************************************/ +/* Test *E2IPCFG responses */ + +typedef struct { + const gchar *str; + + /* IPv4 */ + const gchar *ipv4_addr; + const gchar *ipv4_gw; + const gchar *ipv4_dns1; + const gchar *ipv4_dns2; + + /* IPv6 */ + const gchar *ipv6_addr; + const gchar *ipv6_dns1; + const gchar *ipv6_dns2; +} E2ipcfgTest; + +static const E2ipcfgTest tests[] = { + { "*E2IPCFG: (1,\"46.157.32.246\")(2,\"46.157.32.243\")(3,\"193.213.112.4\")(3,\"130.67.15.198\")\r\n", + "46.157.32.246", "46.157.32.243", "193.213.112.4", "130.67.15.198", + NULL, NULL }, + + { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0000:e537:1801\")(3,\"2001:4600:0004:0fff:0000:0000:0000:0054\")(3,\"2001:4600:0004:1fff:0000:0000:0000:0054\")\r\n", + NULL, NULL, NULL, NULL, + "fe80:0000:0000:0000:0000:0000:e537:1801", "2001:4600:0004:0fff:0000:0000:0000:0054", "2001:4600:0004:1fff:0000:0000:0000:0054" }, + + { "*E2IPCFG: (1,\"fe80:0000:0000:0000:0000:0027:b7fe:9401\")(3,\"fd00:976a:0000:0000:0000:0000:0000:0009\")\r\n", + NULL, NULL, NULL, NULL, + "fe80:0000:0000:0000:0000:0027:b7fe:9401", "fd00:976a:0000:0000:0000:0000:0000:0009", NULL }, + + { NULL } +}; + +static void +test_e2ipcfg (void) +{ + guint i; + + for (i = 0; tests[i].str; i++) { + gboolean success; + GError *error = NULL; + MMBearerIpConfig *ipv4 = NULL; + MMBearerIpConfig *ipv6 = NULL; + const gchar **dns; + guint dnslen; + + success = mm_mbm_parse_e2ipcfg_response (tests[i].str, &ipv4, &ipv6, &error); + g_assert_no_error (error); + g_assert (success); + + /* IPv4 */ + if (tests[i].ipv4_addr) { + g_assert (ipv4); + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv4), ==, MM_BEARER_IP_METHOD_STATIC); + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv4), ==, tests[i].ipv4_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv4), ==, 28); + g_assert_cmpstr (mm_bearer_ip_config_get_gateway (ipv4), ==, tests[i].ipv4_gw); + + dns = mm_bearer_ip_config_get_dns (ipv4); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (tests[i].ipv4_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, tests[i].ipv4_dns1); + g_assert_cmpstr (dns[1], ==, tests[i].ipv4_dns2); + } else + g_assert (ipv4 == NULL); + + /* IPv6 */ + if (tests[i].ipv6_addr) { + struct in6_addr a6; + g_assert (ipv6); + + g_assert_cmpstr (mm_bearer_ip_config_get_address (ipv6), ==, tests[i].ipv6_addr); + g_assert_cmpint (mm_bearer_ip_config_get_prefix (ipv6), ==, 64); + + g_assert (inet_pton (AF_INET6, mm_bearer_ip_config_get_address (ipv6), &a6)); + if (IN6_IS_ADDR_LINKLOCAL (&a6)) + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_DHCP); + else + g_assert_cmpint (mm_bearer_ip_config_get_method (ipv6), ==, MM_BEARER_IP_METHOD_STATIC); + + dns = mm_bearer_ip_config_get_dns (ipv6); + g_assert (dns); + dnslen = g_strv_length ((gchar **) dns); + if (tests[i].ipv6_dns2 != NULL) + g_assert_cmpint (dnslen, ==, 2); + else + g_assert_cmpint (dnslen, ==, 1); + g_assert_cmpstr (dns[0], ==, tests[i].ipv6_dns1); + g_assert_cmpstr (dns[1], ==, tests[i].ipv6_dns2); + } else + g_assert (ipv6 == NULL); + } +} + +/*****************************************************************************/ + +void +_mm_log (const char *loc, + const char *func, + guint32 level, + const char *fmt, + ...) +{ +#if defined ENABLE_TEST_MESSAGE_TRACES + /* Dummy log function */ + va_list args; + gchar *msg; + + va_start (args, fmt); + msg = g_strdup_vprintf (fmt, args); + va_end (args); + g_print ("%s\n", msg); + g_free (msg); +#endif +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/MM/mbm/e2ipcfg", test_e2ipcfg); + + return g_test_run (); +}