From 0fd959fdc4dcc32c05991264228c77d8b05b08fc Mon Sep 17 00:00:00 2001 From: Muhammad Asif Date: Thu, 25 Dec 2025 23:31:21 +0500 Subject: [PATCH] wwan: nm-modem-ofono: add support for IPv6 Implement IPv6 configuration in oFono driver, by handling IPv4 and IPv6 settings received from oFono separately in their own structures instead of precreating an L3CD. Now whenever IPv4/6 settings are received from oFono, they're stored in custom fields, and an L3CD is created only at stage 3 to store both IP configurations together, bringing the oFono driver closer to how the ModemManager driver handles it. Signed-off-by: Muhammad Asif --- src/core/devices/wwan/nm-modem-ofono.c | 433 ++++++++++++++++++------- 1 file changed, 317 insertions(+), 116 deletions(-) diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c index 51781d091e..3951f6e24c 100644 --- a/src/core/devices/wwan/nm-modem-ofono.c +++ b/src/core/devices/wwan/nm-modem-ofono.c @@ -39,6 +39,27 @@ typedef struct { GDBusProxy *proxy; } OfonoContextData; +typedef struct { + guint32 address; + guint32 gateway; + guint32 subnet_mask; + guint32 plen; + + guint32 proxy; + + guint32 *dns; + guint32 n_dns; +} IPv4ConfigData; + +typedef struct { + struct in6_addr address; + struct in6_addr gateway; + guint plen; + + struct in6_addr *dns; + guint32 n_dns; +} IPv6ConfigData; + typedef struct { GHashTable *connect_properties; GHashTable *connections; @@ -67,7 +88,9 @@ typedef struct { gboolean modem_powered; gboolean gprs_attached; - NML3ConfigData *l3cd_4; + IPv4ConfigData *ipv4_config; + IPv6ConfigData *ipv6_config; + char *ifname; NMSettings *settings; guint n_context_proxy_pending; @@ -368,8 +391,18 @@ deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager) NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); /* TODO: cancel SimpleConnect() if any */ - - nm_clear_l3cd(&priv->l3cd_4); + if (priv->ipv4_config) { + g_free(priv->ipv4_config->dns); + g_free(priv->ipv4_config); + priv->ipv4_config = NULL; + } + if (priv->ipv6_config) { + g_free(priv->ipv6_config->dns); + g_free(priv->ipv6_config); + priv->ipv6_config = NULL; + } + g_free(priv->ifname); + priv->ifname = NULL; NM_MODEM_CLASS(nm_modem_ofono_parent_class) ->deactivate_cleanup(modem, device, stop_ppp_manager); @@ -906,7 +939,8 @@ context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, g octx->preferred = g_variant_get_boolean(v_inner); update_connection_list(self); - } else if (nm_streq(property, "Settings") && priv->current_octx == octx) { + } else if ((nm_streq(property, "Settings") || nm_streq(property, "IPv6.Settings")) + && priv->current_octx == octx) { g_return_if_fail(g_variant_is_of_type(v_inner, G_VARIANT_TYPE_VARDICT)); handle_settings(self, v_inner); } @@ -1464,32 +1498,21 @@ stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data) } } -static void -handle_settings(NMModemOfono *self, GVariant *v_dict) +static bool +handle_ipv4_settings(NMModemOfono *self, GVariant *v_dict) { NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); - char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; - NMPlatformIP4Address address; - gboolean ret = FALSE; const char *interface; const char *s; - gs_free const char **array = NULL; - guint32 address_network, gateway_network; - int ifindex; - GError *error = NULL; + gs_free const char **array = NULL; + guint array_len = 0; + IPv4ConfigData *config = g_new0(IPv4ConfigData, 1); - /* - * TODO: might be a good idea and re-factor this to mimic bluez-device, - * ie. have this function just check the key, and call a sub-func to - * handle the action. - */ - - if (nm_modem_get_state(NM_MODEM(self)) < NM_MODEM_STATE_REGISTERED) { - /* - * Connection definitely isn't happening. Avoid trigering bogus - * failure which would put device in a wrong state. - */ - return; + if (priv->ipv4_config) { + _LOGI("freeing existing IPv4 config"); + g_free(priv->ipv4_config->dns); + g_free(priv->ipv4_config); + priv->ipv4_config = NULL; } _LOGI("IPv4 static Settings:"); @@ -1499,56 +1522,34 @@ handle_settings(NMModemOfono *self, GVariant *v_dict) goto out; } - _LOGD("Interface: %s", interface); - if (!nm_modem_set_data_port(NM_MODEM(self), - NM_PLATFORM_GET, - interface, - NM_MODEM_IP_METHOD_STATIC, - NM_MODEM_IP_METHOD_UNKNOWN, - 0, - &error)) { - _LOGW("failed to connect to modem: %s", error->message); - g_clear_error(&error); - goto out; - } + /* IPv4 always comes first, so if we have an ifname, clear it. */ + if (priv->ifname) + g_free(priv->ifname); - ifindex = nm_modem_get_ip_ifindex(NM_MODEM(self)); - g_return_if_fail(ifindex > 0); + priv->ifname = g_strdup(interface); - /* TODO: verify handling of l3cd_4; check other places it's used... */ - nm_clear_l3cd(&priv->l3cd_4); - - priv->l3cd_4 = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), - ifindex, - NM_IP_CONFIG_SOURCE_WWAN); + _LOGI("Interface: %s", interface); if (!g_variant_lookup(v_dict, "Address", "&s", &s)) { _LOGW("Settings 'Address' missing"); goto out; } - if (!s || !nm_inet_parse_bin(AF_INET, s, NULL, &address_network)) { + if (!s || !nm_inet_parse_bin(AF_INET, s, NULL, &config->address)) { _LOGW("can't convert 'Address' %s to addr", s ?: ""); goto out; } - address = (NMPlatformIP4Address) { - .ifindex = ifindex, - .address = address_network, - .addr_source = NM_IP_CONFIG_SOURCE_WWAN, - }; + _LOGI("IP: %s", s); if (!g_variant_lookup(v_dict, "Netmask", "&s", &s)) { _LOGW("Settings 'Netmask' missing"); goto out; } - if (!s || !nm_inet_parse_bin(AF_INET, s, NULL, &address_network)) { + if (!s || !nm_inet_parse_bin(AF_INET, s, NULL, &config->subnet_mask)) { _LOGW("invalid 'Netmask': %s", s ?: ""); goto out; } - address.plen = nm_ip4_addr_netmask_to_prefix(address_network); - - _LOGI("Address: %s", nm_platform_ip4_address_to_string(&address, sbuf, sizeof(sbuf))); - nm_l3_config_data_add_address_4(priv->l3cd_4, &address); + config->plen = nm_ip4_addr_netmask_to_prefix(config->subnet_mask); if (!g_variant_lookup(v_dict, "Gateway", "&s", &s) || !s) { /* It is normal for point-to-point connections to not have a gateway IP @@ -1557,40 +1558,32 @@ handle_settings(NMModemOfono *self, GVariant *v_dict) _LOGD("Settings 'Gateway' missing. Setting it to 0.0.0.0"); s = "0.0.0.0"; } - if (!nm_inet_parse_bin(AF_INET, s, NULL, &gateway_network)) { + if (!nm_inet_parse_bin(AF_INET, s, NULL, &config->gateway)) { _LOGW("invalid 'Gateway': %s", s); goto out; } - { - const NMPlatformIP4Route r = { - .rt_source = NM_IP_CONFIG_SOURCE_WWAN, - .gateway = gateway_network, - .table_any = TRUE, - .table_coerced = 0, - .metric_any = TRUE, - .metric = 0, - }; - - _LOGI("Gateway: %s", s); - nm_l3_config_data_add_route_4(priv->l3cd_4, &r); - } if (!g_variant_lookup(v_dict, "DomainNameServers", "^a&s", &array)) { _LOGW("Settings 'DomainNameServers' missing"); goto out; } + + _LOGI("Got DNS entries, %d", array_len); + if (array) { gboolean any_good = FALSE; gsize i; - for (i = 0; array[i]; i++) { - if (!nm_inet_parse_bin(AF_INET, array[i], NULL, &address_network) || !address_network) { + array_len = NM_PTRARRAY_LEN(array); + + config->dns = g_malloc0(sizeof(guint32) * array_len); + config->n_dns = array_len; + for (i = 0; i < array_len; i++) { + if (!nm_inet_parse_bin(AF_INET, array[i], NULL, &config->dns[i])) { _LOGW("invalid NameServer: %s", array[i]); continue; } any_good = TRUE; - _LOGI("DNS: %s", array[i]); - nm_l3_config_data_add_nameserver_addr(priv->l3cd_4, AF_INET, &address_network); } if (!any_good) { _LOGW("Settings: 'DomainNameServers': none specified"); @@ -1609,27 +1602,145 @@ handle_settings(NMModemOfono *self, GVariant *v_dict) if (colon) *colon = '\0'; - if (nm_inet_parse_bin(AF_INET, proxy, NULL, &address_network)) { - const NMPlatformIP4Route mms_route = { - .network = address_network, - .plen = 32, - .gateway = gateway_network, - .table_any = TRUE, - .table_coerced = 0, - .metric_any = TRUE, - .metric = 0, - }; - - nm_l3_config_data_add_route_4(priv->l3cd_4, &mms_route); - } else + if (!nm_inet_parse_bin(AF_INET, proxy, NULL, &config->proxy)) _LOGW("invalid (MMS) Proxy: %s", s); } - ret = TRUE; + priv->ipv4_config = config; + return TRUE; out: - if (priv->l3cd_4) - nm_l3_config_data_seal(priv->l3cd_4); + g_free(config); + return FALSE; +} + +static bool +handle_ipv6_settings(NMModemOfono *self, GVariant *v_dict) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + const char *s; + gs_free const char **array = NULL; + guint array_len = 0; + IPv6ConfigData *config = g_new0(IPv6ConfigData, 1); + + if (priv->ipv6_config) { + _LOGI("freeing existing IPv6 config"); + g_free(priv->ipv6_config->dns); + g_free(priv->ipv6_config); + priv->ipv6_config = NULL; + } + + _LOGI("IPv6 static Settings:"); + + if (!g_variant_lookup(v_dict, "Interface", "&s", &s)) { + _LOGW("Settings 'Interface' missing"); + goto out; + } + + /* IPv4+6 connections will use the same ifname, so only set it if we don't have one yet. */ + if (!priv->ifname) + priv->ifname = g_strdup(s); + + if (!g_variant_lookup(v_dict, "Address", "&s", &s) || !s) { + _LOGW("Settings 'Address' missing"); + goto out; + } + + if (!g_variant_lookup(v_dict, "PrefixLength", "y", &config->plen)) { + _LOGW("Settings 'PrefixLength' missing"); + goto out; + } + + if (!inet_pton(AF_INET6, s, &config->address)) { + _LOGW("can't convert 'Address' %s to addr", s); + goto out; + } + + /* TODO: Add proper handling for DHCP/SLAAC */ + if (!g_variant_lookup(v_dict, "Gateway", "&s", &s) || !s) { + _LOGD("Settings 'Gateway' missing. Setting it to ::"); + s = "::"; + } + + if (!inet_pton(AF_INET6, s, &config->gateway)) { + _LOGW("invalid 'Gateway': %s", s); + goto out; + } + + if (!g_variant_lookup(v_dict, "DomainNameServers", "^a&s", &array)) { + _LOGW("Settings 'DomainNameServers' missing"); + goto out; + } + + if (array) { + gint i; + array_len = NM_PTRARRAY_LEN(array); + + config->dns = g_new0(struct in6_addr, array_len); + config->n_dns = array_len; + + for (i = 0; i < array_len; i++) { + if (!inet_pton(AF_INET6, array[i], &config->dns[i])) { + _LOGW("invalid NameServer: %s", array[i]); + continue; + } + } + } + + priv->ipv6_config = config; + + return TRUE; + +out: + g_free(config); + return FALSE; +} + +static void +handle_settings(NMModemOfono *self, GVariant *settings) +{ + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + gs_unref_variant GVariant *v_ipv4 = NULL; + gs_unref_variant GVariant *v_ipv6 = NULL; + gboolean ipv4_setup = FALSE; + gboolean ipv6_setup = FALSE; + gboolean ret = FALSE; + + if (nm_modem_get_state(NM_MODEM(self)) < NM_MODEM_STATE_REGISTERED) + /* + * Connection definitely isn't happening. Avoid trigering bogus + * failure which would put device in a wrong state. + */ + return; + + v_ipv4 = g_variant_lookup_value(settings, "Settings", G_VARIANT_TYPE_VARDICT); + if (v_ipv4) + ipv4_setup = handle_ipv4_settings(self, v_ipv4); + + v_ipv6 = g_variant_lookup_value(settings, "IPv6.Settings", G_VARIANT_TYPE_VARDICT); + if (v_ipv6) + ipv6_setup = handle_ipv6_settings(self, v_ipv6); + + if (!v_ipv4 && !v_ipv6) { + _LOGW("no Settings found in context"); + nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY); + return; + } + + ret = ipv4_setup || ipv6_setup; + + if (!nm_modem_set_data_port(NM_MODEM(self), + NM_PLATFORM_GET, + ret ? priv->ifname : NULL, + ipv4_setup ? NM_MODEM_IP_METHOD_STATIC : NM_MODEM_IP_METHOD_UNKNOWN, + ipv6_setup ? NM_MODEM_IP_METHOD_STATIC : NM_MODEM_IP_METHOD_UNKNOWN, + 0, + NULL)) { + nm_modem_emit_prepare_result(NM_MODEM(self), + FALSE, + NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE); + return; + } if (nm_modem_get_state(NM_MODEM(self)) != NM_MODEM_STATE_CONNECTED) { _LOGI("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE"); @@ -1646,29 +1757,120 @@ out: static void stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_method) { - NMModemOfono *self = NM_MODEM_OFONO(modem); - NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); - gs_free_error GError *error = NULL; + const int is_ipv4 = NM_IS_IPv4(addr_family); + NMModemOfono *self = NM_MODEM_OFONO(modem); + NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self); + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + gboolean do_auto = FALSE; + gs_free_error GError *error = NULL; + NMUtilsIPv6IfaceId iid_data; + const NMUtilsIPv6IfaceId *iid = NULL; - _LOGD("IP4 config is done; setting modem_state -> CONNECTED"); + _LOGI("IP config is done; setting modem_state -> CONNECTED"); - if (!NM_IS_IPv4(addr_family) || ip_method == NM_MODEM_IP_METHOD_AUTO) { - nm_modem_emit_signal_new_config_success(modem, addr_family, NULL, TRUE, NULL); - goto out; + if (is_ipv4) { + g_return_if_fail(priv->ipv4_config); + + if (ip_method == NM_MODEM_IP_METHOD_AUTO) { + do_auto = TRUE; + goto out; + } + } else { + g_return_if_fail(priv->ipv6_config); } - if (!priv->l3cd_4) { - nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "IP config not received"); + if (is_ipv4) { + NMPlatformIP4Address address; + NMPlatformIP4Route route; + + l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), + nm_modem_get_ip_ifindex(modem), + NM_IP_CONFIG_SOURCE_WWAN); + + address = (NMPlatformIP4Address) { + .ifindex = nm_modem_get_ip_ifindex(modem), + .address = priv->ipv4_config->address, + .addr_source = NM_IP_CONFIG_SOURCE_WWAN, + .plen = nm_ip4_addr_netmask_to_prefix(priv->ipv4_config->subnet_mask), + }; + nm_l3_config_data_add_address_4(l3cd, &address); + + route = (NMPlatformIP4Route) { + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = priv->ipv4_config->gateway, + .table_any = TRUE, + .table_coerced = 0, + .metric_any = TRUE, + .metric = 0, + }; + nm_l3_config_data_add_route_4(l3cd, &route); + + if (priv->ipv4_config->proxy) { + const NMPlatformIP4Route mms_route = { + .network = priv->ipv4_config->proxy, + .plen = 32, + .gateway = priv->ipv4_config->gateway, + .table_any = TRUE, + .table_coerced = 0, + .metric_any = TRUE, + .metric = 0, + }; + nm_l3_config_data_add_route_4(l3cd, &mms_route); + } + + for (guint i = 0; i < priv->ipv4_config->n_dns; i++) { + nm_l3_config_data_add_nameserver_addr(l3cd, AF_INET, &priv->ipv4_config->dns[i]); + } + } else { + NMPlatformIP6Address address; + do_auto = TRUE; + + l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), + nm_modem_get_ip_ifindex(modem), + NM_IP_CONFIG_SOURCE_WWAN); + + address = (NMPlatformIP6Address) { + .ifindex = nm_modem_get_ip_ifindex(modem), + .address = priv->ipv6_config->address, + .addr_source = NM_IP_CONFIG_SOURCE_WWAN, + .plen = priv->ipv6_config->plen, + }; + if (IN6_IS_ADDR_LINKLOCAL(&address.address)) { + nm_utils_ipv6_interface_identifier_get_from_addr(&iid_data, &address.address); + iid = &iid_data; + } else + do_auto = FALSE; + + nm_l3_config_data_add_address_6(l3cd, &address); + + { + const NMPlatformIP6Route route = { + .rt_source = NM_IP_CONFIG_SOURCE_WWAN, + .gateway = priv->ipv6_config->gateway, + .table_any = TRUE, + .table_coerced = 0, + .metric_any = TRUE, + .metric = 0, + }; + nm_l3_config_data_add_route_6(l3cd, &route); + } + + for (guint i = 0; i < priv->ipv6_config->n_dns; i++) { + nm_l3_config_data_add_nameserver_addr(l3cd, AF_INET6, &priv->ipv6_config->dns[i]); + } + } + +out: + if (error) { nm_modem_emit_signal_new_config_failure(modem, addr_family, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE, error); - goto out; + return; } - nm_modem_emit_signal_new_config_success(modem, addr_family, priv->l3cd_4, FALSE, NULL); + nm_modem_emit_signal_new_config_success(modem, addr_family, l3cd, do_auto, iid); -out: nm_modem_set_state(NM_MODEM(self), NM_MODEM_STATE_CONNECTED, nm_modem_state_to_string(NM_MODEM_STATE_CONNECTED)); @@ -1716,13 +1918,7 @@ context_properties_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat if (active) { _LOGD("ofono: connection is already Active"); - settings = g_variant_lookup_value(v_dict, "Settings", G_VARIANT_TYPE_VARDICT); - if (settings == NULL) { - _LOGW("ofono: connection failed; can not read 'Settings' property"); - goto error; - } - - handle_settings(self, settings); + handle_settings(self, v_dict); } else { g_dbus_proxy_call(proxy, "SetProperty", @@ -1750,12 +1946,6 @@ do_context_activate(NMModemOfono *self) priv->connect_cancellable = g_cancellable_new(); - /* We have an old copy of the settings from a previous activation, - * clear it so that we can gate getting the IP config from oFono - * on whether or not we have already received them - */ - nm_clear_l3cd(&priv->l3cd_4); - /* We need to directly query ConnectionContextinteface to get the current * property values */ g_dbus_proxy_call(priv->current_octx->proxy, @@ -1980,7 +2170,18 @@ dispose(GObject *object) priv->contexts = NULL; } - nm_clear_l3cd(&priv->l3cd_4); + if (priv->ipv4_config) { + g_free(priv->ipv4_config->dns); + g_free(priv->ipv4_config); + priv->ipv4_config = NULL; + } + if (priv->ipv6_config) { + g_free(priv->ipv6_config->dns); + g_free(priv->ipv6_config); + priv->ipv6_config = NULL; + } + g_free(priv->ifname); + priv->ifname = NULL; if (priv->modem_proxy) { g_signal_handlers_disconnect_by_data(priv->modem_proxy, self);