diff options
22 files changed, 1478 insertions, 302 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 (mm_bearer_ip_config_get_method (ipv4_config) == MM_BEARER_IP_METHOD_STATIC) { - const gchar **dns; - guint i; - - 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 (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; + + 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))); + + 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 (mm_bearer_ip_config_get_method (ipv6_config) == MM_BEARER_IP_METHOD_STATIC) { - const gchar **dns; - guint i; - - 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 (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; + + 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))); + + 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) 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 @@ </varlistentry> </variablelist> - If the bearer specifies configuration via PPP or DHCP, only the - <literal>"method"</literal> item will be present. - - Additional items which are only applicable when using the + If the bearer specifies configuration via PPP or DHCP, often only the + <literal>"method"</literal> 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 + <literal>"address"</literal> 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 usually only applicable when using the <link linkend="MM-BEARER-IP-METHOD-STATIC:CAPS">MM_BEARER_IP_METHOD_STATIC</link> method are: <variablelist> 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}", diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 31cc50ed..a45b70d7 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -1,3 +1,4 @@ + include $(top_srcdir)/gtester.make # Common CPPFLAGS and LDFLAGS @@ -90,13 +91,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 = \ @@ -197,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/icera/mm-broadband-bearer-icera.c b/plugins/icera/mm-broadband-bearer-icera.c index 4507a611..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: <cid>,<ip>,<gw>,<dns1>,<dns2>[,<nbns1>,<nbns2>[,<??>,<netmask>,<gw>]] - * - * 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 @@ -269,6 +185,7 @@ get_ip_config_3gpp (MMBroadbandBearer *self, MMPortSerialAt *secondary, MMPort *data, guint cid, + MMBearerIpFamily ip_family, GAsyncReadyCallback callback, gpointer user_data) { @@ -284,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, @@ -300,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 <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#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: <cid>,<ip>,<gw>,<dns1>,<dns2>[,<nbns1>,<nbns2>[,<??>,<netmask>,<gw>]] + * %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>,<nbns1>,<nbns2>,<netmask>,<gw> + * %IPDPADDR: <cid>,<ip>,<gw>,<dns1>,<dns2>,<nbns1>,<nbns2>,<??>,<gw>,<ip6>,::,<ip6_dns1>,::,::,::,::,:: + * + * 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 <dcbw@redhat.com> + */ + +#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 <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#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 (); +} 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); @@ -466,6 +467,164 @@ dial_3gpp (MMBroadbandBearer *self, } /*****************************************************************************/ +/* 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 */ typedef struct { @@ -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 <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#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,<IP>)(2,<gateway>)(3,<DNS>)(3,<DNS>) + * + * *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 <dcbw@redhat.com> + */ + +#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 <aleksander@gnu.org> + * Copyright (C) 2014 Dan Williams <dcbw@redhat.com> + */ + +#include <glib.h> +#include <glib-object.h> +#include <locale.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#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 (); +} 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/plugins/sierra/mm-broadband-bearer-sierra.c b/plugins/sierra/mm-broadband-bearer-sierra.c index 67cb3f27..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) @@ -442,8 +466,9 @@ 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, + gboolean is_icera, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) @@ -456,21 +481,74 @@ mm_broadband_bearer_sierra_new (MMBroadbandModemSierra *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 92a5acc8..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 { @@ -48,8 +50,9 @@ 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, + 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 d9a4e400..3bfad984 100644 --- a/plugins/sierra/mm-broadband-modem-sierra-icera.c +++ b/plugins/sierra/mm-broadband-modem-sierra-icera.c @@ -85,8 +85,9 @@ 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, + 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 8d06b017..d6dd965b 100644 --- a/plugins/sierra/mm-broadband-modem-sierra.c +++ b/plugins/sierra/mm-broadband-modem-sierra.c @@ -1134,8 +1134,9 @@ 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, + FALSE, /* is_icera */ NULL, /* cancellable */ (GAsyncReadyCallback)broadband_bearer_sierra_new_ready, result); 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 */ 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); - g_clear_error (&error); - - /* 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_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"); - /* 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 */ + 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 (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; diff --git a/src/mm-broadband-bearer.c b/src/mm-broadband-bearer.c index de762d76..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); @@ -701,6 +703,7 @@ dial_3gpp_ready (MMBroadbandModem *modem, ctx->secondary, ctx->data, ctx->cid, + ctx->ip_family, (GAsyncReadyCallback)get_ip_config_3gpp_ready, ctx); return; @@ -714,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 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, |