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