wwan: nm-modem-ofono: add support for IPv6

Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
This commit is contained in:
Muhammad Asif 2025-12-25 23:31:21 +05:00
parent 33357f8247
commit dc15530884

View file

@ -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);