diff options
author | Dan Williams <dcbw@redhat.com> | 2015-08-19 12:44:02 -0500 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2016-03-13 13:25:19 +0100 |
commit | a9b69d210d12acc458d2d8ba4e79554f818e8337 (patch) | |
tree | 0b838e7604d619c9778efc33afc4fdcb2cd4e67f | |
parent | 91d343520fec5c5905a4cc3ce898f75025f3f2a8 (diff) | |
download | ModemManager-a9b69d210d12acc458d2d8ba4e79554f818e8337.tar.gz |
huawei: use AT^DHCP response if available for NDISDUP-capable devices
For non-QMI/non-MBIM Huawei devices that use HiSense chipsets,
the recommended way to create the connection is to use NDISDUP
and either DHCP on the net interface, or the ^DHCP command.
There are some reports of devices that connect successfully, but
don't respond to DHCP requests on the interface. Try to get
IP addressing info from the device via ^DHCP and fall back to
telling clients to use actual DHCP if that fails.
https://bugzilla.redhat.com/show_bug.cgi?id=1254886
-rw-r--r-- | plugins/huawei/mm-broadband-bearer-huawei.c | 112 | ||||
-rw-r--r-- | plugins/huawei/mm-modem-helpers-huawei.c | 112 | ||||
-rw-r--r-- | plugins/huawei/mm-modem-helpers-huawei.h | 10 | ||||
-rw-r--r-- | plugins/huawei/tests/test-modem-helpers-huawei.c | 51 |
4 files changed, 276 insertions, 9 deletions
diff --git a/plugins/huawei/mm-broadband-bearer-huawei.c b/plugins/huawei/mm-broadband-bearer-huawei.c index 6c64d4d0e..60a91e552 100644 --- a/plugins/huawei/mm-broadband-bearer-huawei.c +++ b/plugins/huawei/mm-broadband-bearer-huawei.c @@ -67,6 +67,7 @@ typedef enum { CONNECT_3GPP_CONTEXT_STEP_FIRST = 0, CONNECT_3GPP_CONTEXT_STEP_NDISDUP, CONNECT_3GPP_CONTEXT_STEP_NDISSTATQRY, + CONNECT_3GPP_CONTEXT_STEP_IP_CONFIG, CONNECT_3GPP_CONTEXT_STEP_LAST } Connect3gppContextStep; @@ -80,6 +81,7 @@ typedef struct { Connect3gppContextStep step; guint check_count; guint failed_ndisstatqry_count; + MMBearerIpConfig *ipv4_config; } Connect3gppContext; static void @@ -92,6 +94,7 @@ connect_3gpp_context_complete_and_free (Connect3gppContext *ctx) g_object_unref (ctx->modem); g_object_unref (ctx->self); + g_clear_object (&ctx->ipv4_config); g_clear_object (&ctx->data); g_clear_object (&ctx->primary); @@ -111,6 +114,88 @@ connect_3gpp_finish (MMBroadbandBearer *self, static void connect_3gpp_context_step (Connect3gppContext *ctx); +static void +connect_dhcp_check_ready (MMBaseModem *modem, + GAsyncResult *res, + MMBroadbandBearerHuawei *self) +{ + Connect3gppContext *ctx; + const gchar *response; + GError *error = NULL; + + ctx = self->priv->connect_pending; + g_assert (ctx != NULL); + + /* Balance refcount */ + g_object_unref (self); + + /* Default to automatic/DHCP addressing */ + ctx->ipv4_config = mm_bearer_ip_config_new (); + mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_DHCP); + + /* Cache IPv4 details if available, otherwise clients will have to use DHCP */ + response = mm_base_modem_at_command_full_finish (modem, res, &error); + if (response) { + guint address = 0; + guint prefix = 0; + guint gateway = 0; + guint dns1 = 0; + guint dns2 = 0; + + if (mm_huawei_parse_dhcp_response (response, + &address, + &prefix, + &gateway, + &dns1, + &dns2, + &error)) { + GInetAddress *addr; + gchar *strarr[3] = { NULL, NULL, NULL }; + guint n = 0; + gchar *str; + + mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_STATIC); + + addr = g_inet_address_new_from_bytes ((guint8 *)&address, G_SOCKET_FAMILY_IPV4); + str = g_inet_address_to_string (addr); + mm_bearer_ip_config_set_address (ctx->ipv4_config, str); + g_free (str); + g_object_unref (addr); + + /* Netmask */ + mm_bearer_ip_config_set_prefix (ctx->ipv4_config, prefix); + + /* Gateway */ + addr = g_inet_address_new_from_bytes ((guint8 *)&gateway, G_SOCKET_FAMILY_IPV4); + str = g_inet_address_to_string (addr); + mm_bearer_ip_config_set_gateway (ctx->ipv4_config, str); + g_free (str); + g_object_unref (addr); + + /* DNS */ + if (dns1) { + addr = g_inet_address_new_from_bytes ((guint8 *)&dns1, G_SOCKET_FAMILY_IPV4); + strarr[n++] = g_inet_address_to_string (addr); + g_object_unref (addr); + } + if (dns2) { + addr = g_inet_address_new_from_bytes ((guint8 *)&dns2, G_SOCKET_FAMILY_IPV4); + strarr[n++] = g_inet_address_to_string (addr); + g_object_unref (addr); + } + mm_bearer_ip_config_set_dns (ctx->ipv4_config, (const gchar **)strarr); + g_free (strarr[0]); + g_free (strarr[1]); + } else { + mm_dbg ("Unexpected response to ^DHCP command: %s", error->message); + } + } + + g_clear_error (&error); + ctx->step++; + connect_3gpp_context_step (ctx); +} + static gboolean connect_retry_ndisstatqry_check_cb (MMBroadbandBearerHuawei *self) { @@ -380,21 +465,30 @@ connect_3gpp_context_step (Connect3gppContext *ctx) g_object_ref (ctx->self)); return; + case CONNECT_3GPP_CONTEXT_STEP_IP_CONFIG: + mm_base_modem_at_command_full (ctx->modem, + ctx->primary, + "^DHCP?", + 3, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)connect_dhcp_check_ready, + g_object_ref (ctx->self)); + return; + case CONNECT_3GPP_CONTEXT_STEP_LAST: /* Clear context */ ctx->self->priv->connect_pending = NULL; /* Setup result */ { - MMBearerIpConfig *ipv4_config; - - ipv4_config = mm_bearer_ip_config_new (); - mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); - g_simple_async_result_set_op_res_gpointer ( - ctx->result, - mm_bearer_connect_result_new (ctx->data, ipv4_config, NULL), - (GDestroyNotify)mm_bearer_connect_result_unref); - g_object_unref (ipv4_config); + if (ctx->ipv4_config) { + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + mm_bearer_connect_result_new (ctx->data, ctx->ipv4_config, NULL), + (GDestroyNotify)mm_bearer_connect_result_unref); + } } connect_3gpp_context_complete_and_free (ctx); diff --git a/plugins/huawei/mm-modem-helpers-huawei.c b/plugins/huawei/mm-modem-helpers-huawei.c index 5362c55a4..87a54e429 100644 --- a/plugins/huawei/mm-modem-helpers-huawei.c +++ b/plugins/huawei/mm-modem-helpers-huawei.c @@ -152,6 +152,118 @@ mm_huawei_parse_ndisstatqry_response (const gchar *response, } /*****************************************************************************/ +/* ^DHCP response parser */ + +static gboolean +match_info_to_ip4_addr (GMatchInfo *match_info, + guint match_index, + guint *out_addr) +{ + gchar *s, *bin = NULL; + gchar buf[9]; + gsize len, bin_len; + gboolean success = FALSE; + + s = g_match_info_fetch (match_info, match_index); + g_return_val_if_fail (s != NULL, FALSE); + + len = strlen (s); + if (len == 1 && s[0] == '0') { + *out_addr = 0; + success = TRUE; + goto done; + } + if (len < 7 || len > 8) + goto done; + + /* Handle possibly missing leading zero */ + memset (buf, 0, sizeof (buf)); + if (len == 7) { + strcpy (&buf[1], s); + buf[0] = '0'; + } else if (len == 8) + strcpy (buf, s); + else + g_assert_not_reached (); + + bin = mm_utils_hexstr2bin (buf, &bin_len); + if (!bin || bin_len != 4) + goto done; + + *out_addr = GUINT32_TO_BE (*((guint32 *) bin)); + success = TRUE; + +done: + g_free (s); + g_free (bin); + return success; +} + +gboolean +mm_huawei_parse_dhcp_response (const char *reply, + guint *out_address, + guint *out_prefix, + guint *out_gateway, + guint *out_dns1, + guint *out_dns2, + GError **error) +{ + gboolean matched; + GRegex *r; + GMatchInfo *match_info = NULL; + GError *match_error = NULL; + + g_assert (reply != NULL); + g_assert (out_address != NULL); + g_assert (out_prefix != NULL); + g_assert (out_gateway != NULL); + g_assert (out_dns1 != NULL); + g_assert (out_dns2 != NULL); + + /* Format: + * + * ^DHCP: <address>,<netmask>,<gateway>,<?>,<dns1>,<dns2>,<uplink>,<downlink> + * + * All numbers are hexadecimal representations of IPv4 addresses, with + * least-significant byte first. eg, 192.168.50.32 is expressed as + * "2032A8C0". Sometimes leading zeros are stripped, so "1010A0A" is + * actually 10.10.1.1. + */ + + r = g_regex_new ("\\^DHCP:\\s*([0-9a-fA-F]+),([0-9a-fA-F]+),([0-9a-fA-F]+),([0-9a-fA-F]+),([0-9a-fA-F]+),([0-9a-fA-F]+),.*$", 0, 0, NULL); + g_assert (r != NULL); + + matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error); + if (!matched) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^DHCP results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match ^DHCP reply"); + } + } else { + guint netmask; + + if (match_info_to_ip4_addr (match_info, 1, out_address) && + match_info_to_ip4_addr (match_info, 2, &netmask) && + match_info_to_ip4_addr (match_info, 3, out_gateway) && + match_info_to_ip4_addr (match_info, 5, out_dns1) && + match_info_to_ip4_addr (match_info, 6, out_dns2)) { + *out_prefix = mm_count_bits_set (netmask); + matched = TRUE; + } + } + + if (match_info) + g_match_info_free (match_info); + g_regex_unref (r); + return matched; +} + +/*****************************************************************************/ /* ^SYSINFO response parser */ gboolean diff --git a/plugins/huawei/mm-modem-helpers-huawei.h b/plugins/huawei/mm-modem-helpers-huawei.h index e225e9087..62019e8cc 100644 --- a/plugins/huawei/mm-modem-helpers-huawei.h +++ b/plugins/huawei/mm-modem-helpers-huawei.h @@ -29,6 +29,16 @@ gboolean mm_huawei_parse_ndisstatqry_response (const gchar *response, GError **error); /*****************************************************************************/ +/* ^DHCP response parser */ +gboolean mm_huawei_parse_dhcp_response (const char *reply, + guint *out_address, + guint *out_prefix, + guint *out_gateway, + guint *out_dns1, + guint *out_dns2, + GError **error); + +/*****************************************************************************/ /* ^SYSINFO response parser */ gboolean mm_huawei_parse_sysinfo_response (const char *reply, guint *out_srv_status, diff --git a/plugins/huawei/tests/test-modem-helpers-huawei.c b/plugins/huawei/tests/test-modem-helpers-huawei.c index 19e6decc4..bd0a8a6dd 100644 --- a/plugins/huawei/tests/test-modem-helpers-huawei.c +++ b/plugins/huawei/tests/test-modem-helpers-huawei.c @@ -16,6 +16,7 @@ #include <glib.h> #include <glib-object.h> #include <locale.h> +#include <arpa/inet.h> #include <ModemManager.h> #define _LIBMM_INSIDE_MM @@ -139,6 +140,55 @@ test_ndisstatqry (void) } /*****************************************************************************/ +/* Test ^DHCP responses */ + +typedef struct { + const gchar *str; + const gchar *expected_addr; + guint expected_prefix; + const gchar *expected_gateway; + const gchar *expected_dns1; + const gchar *expected_dns2; +} DhcpTest; + +static const DhcpTest dhcp_tests[] = { + { "^DHCP:a3ec5c64,f8ffffff,a1ec5c64,a1ec5c64,2200b10a,74bba80a,236800,236800\r\n", + "100.92.236.163", 29, "100.92.236.161", "10.177.0.34", "10.168.187.116" }, + { "^DHCP: 1010A0A,FCFFFFFF,2010A0A,2010A0A,0,0,150000000,150000000\r\n", + "10.10.1.1", 30, "10.10.1.2", "0.0.0.0", "0.0.0.0" }, + { "^DHCP: CCDB080A,F8FFFFFF,C9DB080A,C9DB080A,E67B59C0,E77B59C0,85600,85600\r\n", + "10.8.219.204", 29, "10.8.219.201", "192.89.123.230", "192.89.123.231" }, + { NULL } +}; + +static void +test_dhcp (void) +{ + guint i; + + for (i = 0; dhcp_tests[i].str; i++) { + GError *error = NULL; + guint addr, prefix, gateway, dns1, dns2; + + g_assert (mm_huawei_parse_dhcp_response ( + dhcp_tests[i].str, + &addr, + &prefix, + &gateway, + &dns1, + &dns2, + &error) == TRUE); + g_assert_no_error (error); + + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &addr)), ==, dhcp_tests[i].expected_addr); + g_assert_cmpint (prefix, ==, dhcp_tests[i].expected_prefix); + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &gateway)), ==, dhcp_tests[i].expected_gateway); + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &dns1)), ==, dhcp_tests[i].expected_dns1); + g_assert_cmpstr (inet_ntoa (*((struct in_addr *) &dns2)), ==, dhcp_tests[i].expected_dns2); + } +} + +/*****************************************************************************/ /* Test ^SYSINFO responses */ typedef struct { @@ -1171,6 +1221,7 @@ int main (int argc, char **argv) g_test_init (&argc, &argv, NULL); g_test_add_func ("/MM/huawei/ndisstatqry", test_ndisstatqry); + g_test_add_func ("/MM/huawei/dhcp", test_dhcp); g_test_add_func ("/MM/huawei/sysinfo", test_sysinfo); g_test_add_func ("/MM/huawei/sysinfoex", test_sysinfoex); g_test_add_func ("/MM/huawei/prefmode", test_prefmode); |