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
This commit is contained in:
Dan Williams
2016-03-13 13:25:19 +01:00
committed by Aleksander Morgado
parent 91d343520f
commit a9b69d210d
4 changed files with 276 additions and 9 deletions
+103 -9
View File
@@ -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);
+112
View File
@@ -151,6 +151,118 @@ mm_huawei_parse_ndisstatqry_response (const gchar *response,
return TRUE;
}
/*****************************************************************************/
/* ^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 */
+10
View File
@@ -28,6 +28,16 @@ gboolean mm_huawei_parse_ndisstatqry_response (const gchar *response,
gboolean *ipv6_connected,
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,
@@ -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
@@ -138,6 +139,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 */
@@ -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);