Merge request !1266 from 'v6-cgcontrdp'
modem-helpers: add IPv6 support to +CGCONTRDP parsing https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1266
This commit is contained in:
+186
-35
@@ -14,6 +14,7 @@
|
|||||||
* Copyright (C) 2009 - 2012 Red Hat, Inc.
|
* Copyright (C) 2009 - 2012 Red Hat, Inc.
|
||||||
* Copyright (C) 2012 Google, Inc.
|
* Copyright (C) 2012 Google, Inc.
|
||||||
* Copyright (c) 2021 Qualcomm Innovation Center, Inc.
|
* Copyright (c) 2021 Qualcomm Innovation Center, Inc.
|
||||||
|
* Copyright (C) 2024 JUCR GmbH
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
@@ -2339,13 +2340,89 @@ mm_3gpp_parse_crsm_response (const gchar *reply,
|
|||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* CGCONTRDP=N response parser */
|
/* CGCONTRDP=N response parser */
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
dots_to_v6_address (const gchar *str,
|
||||||
|
gsize len,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(GRegex) r = NULL;
|
||||||
|
g_autoptr(GMatchInfo) match_info = NULL;
|
||||||
|
guint i;
|
||||||
|
g_autoptr(GString) addr = NULL;
|
||||||
|
g_autoptr(GInetAddress) normalized = NULL;
|
||||||
|
|
||||||
|
r = g_regex_new ("(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)", 0, 0, NULL);
|
||||||
|
g_assert (r != NULL);
|
||||||
|
|
||||||
|
if (!g_regex_match_full (r, str, len, 0, 0, &match_info, error))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Remember that g_match_info_get_match_count() includes match #0 */
|
||||||
|
if (g_match_info_get_match_count (match_info) < 17) {
|
||||||
|
g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
||||||
|
"Failed to match IPv6 address");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = g_string_sized_new (40);
|
||||||
|
|
||||||
|
for (i = 1; i <= 16; i += 2) {
|
||||||
|
guint num1 = 0, num2 = 0;
|
||||||
|
|
||||||
|
if (!mm_get_uint_from_match_info (match_info, i, &num1) ||
|
||||||
|
!mm_get_uint_from_match_info (match_info, i + 1, &num2)) {
|
||||||
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
||||||
|
"Failed to parse uint from IPv6 regex match %d or %d",
|
||||||
|
i,
|
||||||
|
i + 1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_string_append_printf (addr, "%s%02x%02x", i > 1 ? ":" : "", num1, num2);
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized = g_inet_address_new_from_string (addr->str);
|
||||||
|
if (!normalized) {
|
||||||
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
||||||
|
"Failed to parse IP address '%s'",
|
||||||
|
addr->str);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_inet_address_to_string (normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
static guint
|
||||||
|
count_separators (const gchar *str,
|
||||||
|
const gchar separator,
|
||||||
|
const gchar **middle)
|
||||||
|
{
|
||||||
|
const gchar *found;
|
||||||
|
guint count = 0;
|
||||||
|
|
||||||
|
found = str;
|
||||||
|
while ((found = strchr (found, separator))) {
|
||||||
|
count++;
|
||||||
|
if (count == 4) { /* middle of v4 address+mask */
|
||||||
|
if (middle)
|
||||||
|
*middle = found;
|
||||||
|
} else if (count == 16) /* middle of v6 address+mask */
|
||||||
|
if (middle)
|
||||||
|
*middle = found;
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
split_local_address_and_subnet (const gchar *str,
|
split_local_address_and_subnet (const gchar *str,
|
||||||
gchar **local_address,
|
gchar **local_address,
|
||||||
gchar **subnet)
|
gchar **subnet,
|
||||||
|
gboolean *is_ipv6,
|
||||||
|
GError **error)
|
||||||
{
|
{
|
||||||
const gchar *separator;
|
const gchar *middle = NULL;
|
||||||
guint count = 0;
|
guint num_dots = 0;
|
||||||
|
|
||||||
/* E.g. split: "2.43.2.44.255.255.255.255"
|
/* E.g. split: "2.43.2.44.255.255.255.255"
|
||||||
* into:
|
* into:
|
||||||
@@ -2355,35 +2432,93 @@ split_local_address_and_subnet (const gchar *str,
|
|||||||
g_assert (str);
|
g_assert (str);
|
||||||
g_assert (local_address);
|
g_assert (local_address);
|
||||||
g_assert (subnet);
|
g_assert (subnet);
|
||||||
|
g_assert (is_ipv6);
|
||||||
|
|
||||||
separator = str;
|
/* Count number of dots to determine IPv4 or IPv6. Cases are:
|
||||||
while (1) {
|
* 0: might be IPv6 address formatted with AT+CGPIAF
|
||||||
separator = strchr (separator, '.');
|
* 3: IPv4 address
|
||||||
if (separator) {
|
* 7: IPv4 address and subnet mask
|
||||||
count++;
|
* 15: IPv6 address
|
||||||
if (count == 4) {
|
* 31: IPv6 address and mask/prefix
|
||||||
if (local_address)
|
*/
|
||||||
*local_address = g_strndup (str, (separator - str));
|
num_dots = count_separators (str, '.', &middle);
|
||||||
if (subnet)
|
if (num_dots == 0) {
|
||||||
*subnet = g_strdup (++separator);
|
if (strchr (str, ':')) {
|
||||||
|
g_autoptr(GInetAddress) a = NULL;
|
||||||
|
|
||||||
|
/* May already be v6-formatted due to AT+CGPIAF */
|
||||||
|
if ((a = g_inet_address_new_from_string (str))) {
|
||||||
|
*local_address = g_inet_address_to_string (a);
|
||||||
|
*is_ipv6 = TRUE;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
separator++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Not even the full IP? report error parsing */
|
|
||||||
if (count < 3)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
if (count == 3) {
|
|
||||||
if (local_address)
|
|
||||||
*local_address = g_strdup (str);
|
|
||||||
if (subnet)
|
|
||||||
*subnet = NULL;
|
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
||||||
|
"Error parsing IP address '%s'", str);
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (num_dots) {
|
||||||
|
case 3:
|
||||||
|
*local_address = g_strdup (str);
|
||||||
|
*subnet = NULL;
|
||||||
|
return TRUE;
|
||||||
|
case 7:
|
||||||
|
*local_address = g_strndup (str, (middle - str));
|
||||||
|
*subnet = g_strdup (++middle);
|
||||||
|
return TRUE;
|
||||||
|
case 15:
|
||||||
|
*local_address = dots_to_v6_address (str, -1, error);
|
||||||
|
*subnet = NULL;
|
||||||
|
*is_ipv6 = TRUE;
|
||||||
|
return (!!*local_address);
|
||||||
|
case 31:
|
||||||
|
*local_address = dots_to_v6_address (str, (middle - str), error);
|
||||||
|
if (!*local_address)
|
||||||
|
return FALSE;
|
||||||
|
*subnet = dots_to_v6_address (++middle, -1, error);
|
||||||
|
*is_ipv6 = TRUE;
|
||||||
|
return (!!*subnet);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
||||||
|
"Error parsing IP address '%s'", str);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
get_normalized_address_from_match_info (GMatchInfo *match_info,
|
||||||
|
guint field_index,
|
||||||
|
gchar **out_address,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_autoptr(GInetAddress) a = NULL;
|
||||||
|
g_autofree gchar *address = NULL;
|
||||||
|
|
||||||
|
g_assert (out_address && !*out_address);
|
||||||
|
|
||||||
|
/* Missing match is not fatal */
|
||||||
|
address = mm_get_string_unquoted_from_match_info (match_info, field_index);
|
||||||
|
if (!address)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
if (count_separators (address, '.', NULL) == 15) {
|
||||||
|
*out_address = dots_to_v6_address (address, -1, error);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
a = g_inet_address_new_from_string (address);
|
||||||
|
if (!a) {
|
||||||
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
||||||
|
"Error normalizing IP address '%s'",
|
||||||
|
address);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_address = g_inet_address_to_string (a);
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
@@ -2411,6 +2546,7 @@ mm_3gpp_parse_cgcontrdp_response (const gchar *response,
|
|||||||
g_autofree gchar *dns_primary_address = NULL;
|
g_autofree gchar *dns_primary_address = NULL;
|
||||||
g_autofree gchar *dns_secondary_address = NULL;
|
g_autofree gchar *dns_secondary_address = NULL;
|
||||||
guint field_format_extra_index = 0;
|
guint field_format_extra_index = 0;
|
||||||
|
gboolean is_ipv6 = FALSE;
|
||||||
|
|
||||||
/* Response may be e.g.:
|
/* Response may be e.g.:
|
||||||
* +CGCONTRDP: 4,5,"ibox.tim.it.mnc001.mcc222.gprs","2.197.17.49.255.255.255.255","2.197.17.49","10.207.43.46","10.206.56.132","0.0.0.0","0.0.0.0",0
|
* +CGCONTRDP: 4,5,"ibox.tim.it.mnc001.mcc222.gprs","2.197.17.49.255.255.255.255","2.197.17.49","10.207.43.46","10.206.56.132","0.0.0.0","0.0.0.0",0
|
||||||
@@ -2467,20 +2603,35 @@ mm_3gpp_parse_cgcontrdp_response (const gchar *response,
|
|||||||
* the subnet in its own comma-separated field. Try to detect that.
|
* the subnet in its own comma-separated field. Try to detect that.
|
||||||
*/
|
*/
|
||||||
local_address_and_subnet = mm_get_string_unquoted_from_match_info (match_info, 4);
|
local_address_and_subnet = mm_get_string_unquoted_from_match_info (match_info, 4);
|
||||||
if (local_address_and_subnet && !split_local_address_and_subnet (local_address_and_subnet, &local_address, &subnet)) {
|
if (local_address_and_subnet && !split_local_address_and_subnet (local_address_and_subnet, &local_address, &subnet, &is_ipv6, error))
|
||||||
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing local address and subnet");
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
|
||||||
|
|
||||||
/* If we don't have a subnet in field 4, we're using the old format with subnet in an extra field */
|
/* If we have a field 4 but it doesn't contain a subnet, we're using the old
|
||||||
if (!subnet) {
|
* format with subnet in an extra field. Except for IPv6, which has no subnets.
|
||||||
|
*/
|
||||||
|
if (local_address_and_subnet && !subnet && !is_ipv6) {
|
||||||
subnet = mm_get_string_unquoted_from_match_info (match_info, 5);
|
subnet = mm_get_string_unquoted_from_match_info (match_info, 5);
|
||||||
field_format_extra_index = 1;
|
field_format_extra_index = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
gateway_address = mm_get_string_unquoted_from_match_info (match_info, 5 + field_format_extra_index);
|
if (!get_normalized_address_from_match_info (match_info,
|
||||||
dns_primary_address = mm_get_string_unquoted_from_match_info (match_info, 6 + field_format_extra_index);
|
5 + field_format_extra_index,
|
||||||
dns_secondary_address = mm_get_string_unquoted_from_match_info (match_info, 7 + field_format_extra_index);
|
&gateway_address,
|
||||||
|
error)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (!get_normalized_address_from_match_info (match_info,
|
||||||
|
6 + field_format_extra_index,
|
||||||
|
&dns_primary_address,
|
||||||
|
error)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (!get_normalized_address_from_match_info (match_info,
|
||||||
|
7 + field_format_extra_index,
|
||||||
|
&dns_secondary_address,
|
||||||
|
error)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
if (out_cid)
|
if (out_cid)
|
||||||
*out_cid = cid;
|
*out_cid = cid;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
* GNU General Public License for more details:
|
* GNU General Public License for more details:
|
||||||
*
|
*
|
||||||
* Copyright (C) 2010 Red Hat, Inc.
|
* Copyright (C) 2010 Red Hat, Inc.
|
||||||
|
* Copyright (C) 2024 JUCR GmbH
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
@@ -3834,6 +3835,43 @@ static const CgcontrdpResponseTest cgcontrdp_response_tests[] = {
|
|||||||
.apn = "ibox.tim.it.mnc001.mcc222.gprs",
|
.apn = "ibox.tim.it.mnc001.mcc222.gprs",
|
||||||
.local_address = "2.197.17.49",
|
.local_address = "2.197.17.49",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.str = "+CGCONTRDP: 1,5,\"internetipv6.mnc003.mcc260.gprs\",\"254.128.0.0.0.0.0.0.0.0.0.37.88.136.248.1\",\"\",\"42.1.23.0.0.2.255.255.0.0.0.0.0.0.159.1\",\"42.1.23.0.0.3.255.255.0.0.0.0.0.0.152.34\",\"\",\"\",0,0",
|
||||||
|
.cid = 1,
|
||||||
|
.bearer_id = 5,
|
||||||
|
.apn = "internetipv6.mnc003.mcc260.gprs",
|
||||||
|
.local_address = "fe80::25:5888:f801",
|
||||||
|
.dns_primary_address = "2a01:1700:2:ffff::9f01",
|
||||||
|
.dns_secondary_address = "2a01:1700:3:ffff::9822"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/* Quectel EG915Q */
|
||||||
|
.str = "+CGCONTRDP: 2,6,\"fast.t-mobile.com\",\"38.7.251.144.154.37.192.120.172.57.189.17.176.171.138.245\",,\"253.0.151.106.0.0.0.0.0.0.0.0.0.0.0.9\",\"253.0.151.106.0.0.0.0.0.0.0.0.0.0.0.16\"",
|
||||||
|
.cid = 2,
|
||||||
|
.bearer_id = 6,
|
||||||
|
.apn = "fast.t-mobile.com",
|
||||||
|
.local_address = "2607:fb90:9a25:c078:ac39:bd11:b0ab:8af5",
|
||||||
|
.dns_primary_address = "fd00:976a::9",
|
||||||
|
.dns_secondary_address = "fd00:976a::10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.str = "+CGCONTRDP: 1,0,\"fast.t-mobile.com\",\"2607:FB90:B59A:02C1:0AD3:1B57:4FD4:21F4\",\"FE80:0000:0000:0000:B46D:57FF:FE45:4545\",\"fd00:976a::9\",\"fd00:976a::10\"",
|
||||||
|
.cid = 1,
|
||||||
|
.bearer_id = 0,
|
||||||
|
.apn = "fast.t-mobile.com",
|
||||||
|
.local_address = "2607:fb90:b59a:2c1:ad3:1b57:4fd4:21f4",
|
||||||
|
.gateway_address = "fe80::b46d:57ff:fe45:4545",
|
||||||
|
.dns_primary_address = "fd00:976a::9",
|
||||||
|
.dns_secondary_address = "fd00:976a::10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.str = "+CGCONTRDP: 2,6,\"fast.t-mobile.com\",,,\"253.0.151.106.0.0.0.0.0.0.0.0.0.0.0.9\",\"253.0.151.106.0.0.0.0.0.0.0.0.0.0.0.16\"",
|
||||||
|
.cid = 2,
|
||||||
|
.bearer_id = 6,
|
||||||
|
.apn = "fast.t-mobile.com",
|
||||||
|
.dns_primary_address = "fd00:976a::9",
|
||||||
|
.dns_secondary_address = "fd00:976a::10",
|
||||||
|
},
|
||||||
/* Common */
|
/* Common */
|
||||||
{
|
{
|
||||||
.str = "+CGCONTRDP: 4,5,\"ibox.tim.it.mnc001.mcc222.gprs\"",
|
.str = "+CGCONTRDP: 4,5,\"ibox.tim.it.mnc001.mcc222.gprs\"",
|
||||||
|
|||||||
Reference in New Issue
Block a user