diff options
author | Dan Williams <dcbw@redhat.com> | 2014-06-13 14:57:08 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2014-06-13 14:57:08 -0500 |
commit | 6e490944d07061ca15837963552d023807958feb (patch) | |
tree | 70661775ab5381c240fb016eedb91fd602374881 | |
parent | ff4e45791ddac7086c6b45b9b732c67d54dc6c1a (diff) | |
parent | 02f9b926cd5776835bcd7c2b77c452e2ff8169d9 (diff) |
merge: fix IPv6 support and operation
Notable changes include reporting all available IP configruation
information in the bearer IP details, even if the method is DHCP,
in which case the client should apply the given configuration and
then proceed with DHCP/PPP/SLAAC/etc addressing mechanisms.
QMI, MBIM, and some other plugins have been switched to the "STATIC"
configuration method from DHCP when the firmware supplies all the
necessary details.
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, |