diff options
author | Dan Williams <dcbw@redhat.com> | 2014-06-10 01:18:41 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2014-06-13 11:30:49 -0500 |
commit | 02f9b926cd5776835bcd7c2b77c452e2ff8169d9 (patch) | |
tree | 70661775ab5381c240fb016eedb91fd602374881 | |
parent | ca0a567f74f30a719f11b749165731630528834a (diff) |
broadband-bearer-mbm: support IPv6 DNS and use static IPv4 configuration
-rw-r--r-- | plugins/Makefile.am | 16 | ||||
-rw-r--r-- | plugins/mbm/mm-broadband-bearer-mbm.c | 161 | ||||
-rw-r--r-- | plugins/mbm/mm-modem-helpers-mbm.c | 166 | ||||
-rw-r--r-- | plugins/mbm/mm-modem-helpers-mbm.h | 27 | ||||
-rw-r--r-- | plugins/mbm/tests/test-modem-helpers-mbm.c | 162 |
5 files changed, 532 insertions, 0 deletions
diff --git a/plugins/Makefile.am b/plugins/Makefile.am index bf44ceb6..a45b70d7 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -1,3 +1,4 @@ + include $(top_srcdir)/gtester.make # Common CPPFLAGS and LDFLAGS @@ -212,12 +213,27 @@ libmm_plugin_mbm_la_SOURCES = \ mbm/mm-broadband-modem-mbm.h \ mbm/mm-broadband-bearer-mbm.c \ mbm/mm-broadband-bearer-mbm.h \ + mbm/mm-modem-helpers-mbm.c \ + mbm/mm-modem-helpers-mbm.h \ mbm/mm-sim-mbm.c \ mbm/mm-sim-mbm.h libmm_plugin_mbm_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) libmm_plugin_mbm_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS) udevrules_DATA += mbm/77-mm-ericsson-mbm.rules +noinst_PROGRAMS += test-modem-helpers-mbm +test_modem_helpers_mbm_SOURCES = \ + mbm/mm-modem-helpers-mbm.c \ + mbm/mm-modem-helpers-mbm.h \ + mbm/tests/test-modem-helpers-mbm.c +test_modem_helpers_mbm_CPPFLAGS = \ + -I$(top_srcdir)/plugins/mbm \ + $(PLUGIN_COMMON_COMPILER_FLAGS) +test_modem_helpers_mbm_LDADD = \ + $(top_builddir)/libmm-glib/libmm-glib.la \ + $(top_builddir)/src/libmodem-helpers.la +test_modem_helpers_mbm_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS) + # Option libmm_plugin_option_la_SOURCES = \ option/mm-plugin-option.c \ diff --git a/plugins/mbm/mm-broadband-bearer-mbm.c b/plugins/mbm/mm-broadband-bearer-mbm.c index ed786439..9e5e838e 100644 --- a/plugins/mbm/mm-broadband-bearer-mbm.c +++ b/plugins/mbm/mm-broadband-bearer-mbm.c @@ -39,6 +39,7 @@ #include "mm-broadband-bearer-mbm.h" #include "mm-log.h" #include "mm-modem-helpers.h" +#include "mm-modem-helpers-mbm.h" #include "mm-daemon-enums-types.h" G_DEFINE_TYPE (MMBroadbandBearerMbm, mm_broadband_bearer_mbm, MM_TYPE_BROADBAND_BEARER); @@ -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 (); +} |