diff options
-rw-r--r-- | src/mm-bearer-mbim.c | 812 | ||||
-rw-r--r-- | src/mm-bearer-mbim.h | 2 |
2 files changed, 545 insertions, 269 deletions
diff --git a/src/mm-bearer-mbim.c b/src/mm-bearer-mbim.c index 625b988e..bb0b80d5 100644 --- a/src/mm-bearer-mbim.c +++ b/src/mm-bearer-mbim.c @@ -36,13 +36,28 @@ G_DEFINE_TYPE (MMBearerMbim, mm_bearer_mbim, MM_TYPE_BASE_BEARER) +enum { + PROP_0, + PROP_ASYNC_SLAAC, + PROP_LAST +}; + struct _MMBearerMbimPrivate { MMPortMbim *mbim; MMPort *data; MMPort *link; guint32 session_id; + + /* Whether IP configuration indications should be expected before finalizing + * a connection attempt. */ + gboolean async_slaac; + + /* Ongoing connection attempt waiting for async SLAAC result */ + GTask *attempt_ongoing; }; +static GParamSpec *properties[PROP_LAST]; + /*****************************************************************************/ static gboolean @@ -230,6 +245,7 @@ build_disconnect_message (MMBearerMbim *self, /* Connect */ #define WAIT_LINK_PORT_TIMEOUT_MS 2500 +#define WAIT_IP_CONFIGURATION_ASYNC_TIMEOUT_S 20 typedef enum { CONNECT_STEP_FIRST, @@ -240,6 +256,7 @@ typedef enum { CONNECT_STEP_ENSURE_DISCONNECTED, CONNECT_STEP_CONNECT, CONNECT_STEP_IP_CONFIGURATION, + CONNECT_STEP_IP_CONFIGURATION_ASYNC, CONNECT_STEP_LAST } ConnectStep; @@ -264,11 +281,19 @@ typedef struct { gchar *link_prefix_hint; gchar *link_name; MMPort *link; + /* async slaac support */ + guint async_slaac_timeout_id; + gulong async_slaac_notification_id; + gulong async_slaac_cancellation_id; } ConnectContext; static void connect_context_free (ConnectContext *ctx) { + g_assert (!ctx->async_slaac_cancellation_id); + g_assert (!ctx->async_slaac_timeout_id); + g_assert (!ctx->async_slaac_notification_id); + if (ctx->abort_on_failure) { mbim_device_command (mm_port_mbim_peek_device (ctx->mbim), ctx->abort_on_failure, @@ -308,331 +333,523 @@ connect_finish (MMBaseBearer *self, static void connect_context_step (GTask *task); static void -ip_configuration_query_ready (MbimDevice *device, - GAsyncResult *res, - GTask *task) +process_ip_configuration (MMBearerMbim *self, + ConnectContext *ctx, + MbimIPConfigurationAvailableFlag ipv4configurationavailable, + MbimIPConfigurationAvailableFlag ipv6configurationavailable, + guint32 ipv4addresscount, + const MbimIPv4ElementArray *ipv4address, + guint32 ipv6addresscount, + const MbimIPv6ElementArray *ipv6address, + const MbimIPv4 *ipv4gateway, + const MbimIPv6 *ipv6gateway, + guint32 ipv4dnsservercount, + const MbimIPv4 *ipv4dnsserver, + guint32 ipv6dnsservercount, + const MbimIPv6 *ipv6dnsserver, + guint32 ipv4mtu, + guint32 ipv6mtu) { - MMBearerMbim *self; - ConnectContext *ctx; - GError *error = NULL; - g_autoptr(MbimMessage) response = NULL; - MbimIPConfigurationAvailableFlag ipv4configurationavailable; - MbimIPConfigurationAvailableFlag ipv6configurationavailable; - guint32 ipv4addresscount; - g_autoptr(MbimIPv4ElementArray) ipv4address = NULL; - guint32 ipv6addresscount; - g_autoptr(MbimIPv6ElementArray) ipv6address = NULL; - const MbimIPv4 *ipv4gateway; - const MbimIPv6 *ipv6gateway; - guint32 ipv4dnsservercount; - g_autofree MbimIPv4 *ipv4dnsserver = NULL; - guint32 ipv6dnsservercount; - g_autofree MbimIPv6 *ipv6dnsserver = NULL; - guint32 ipv4mtu; - guint32 ipv6mtu; + g_autofree gchar *ipv4configurationavailable_str = NULL; + g_autofree gchar *ipv6configurationavailable_str = NULL; + g_autoptr(MMBearerIpConfig) ipv4_config = NULL; + g_autoptr(MMBearerIpConfig) ipv6_config = NULL; + guint64 uplink_speed = 0; + guint64 downlink_speed = 0; + + /* Always recreate the connect result completely */ + g_clear_pointer (&ctx->connect_result, (GDestroyNotify)mm_bearer_connect_result_unref); - self = g_task_get_source_object (task); - ctx = g_task_get_task_data (task); + /* IPv4 info */ - response = mbim_device_command_finish (device, res, &error); - if (response && - mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) && - mbim_message_ip_configuration_response_parse ( - response, - NULL, /* sessionid */ - &ipv4configurationavailable, - &ipv6configurationavailable, - &ipv4addresscount, - &ipv4address, - &ipv6addresscount, - &ipv6address, - &ipv4gateway, - &ipv6gateway, - &ipv4dnsservercount, - &ipv4dnsserver, - &ipv6dnsservercount, - &ipv6dnsserver, - &ipv4mtu, - &ipv6mtu, - &error)) { - g_autofree gchar *ipv4configurationavailable_str = NULL; - g_autofree gchar *ipv6configurationavailable_str = NULL; - g_autoptr(MMBearerIpConfig) ipv4_config = NULL; - g_autoptr(MMBearerIpConfig) ipv6_config = NULL; - guint64 uplink_speed = 0; - guint64 downlink_speed = 0; + ipv4configurationavailable_str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv4configurationavailable); + mm_obj_dbg (self, "IPv4 configuration available: '%s'", ipv4configurationavailable_str); + + if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && ipv4addresscount) { + guint i; + + mm_obj_dbg (self, " IP addresses (%u)", ipv4addresscount); + for (i = 0; i < ipv4addresscount; i++) { + g_autoptr(GInetAddress) addr = NULL; + g_autofree gchar *str = NULL; - /* IPv4 info */ + addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4address[i]->ipv4_address, G_SOCKET_FAMILY_IPV4); + str = g_inet_address_to_string (addr); + mm_obj_dbg (self, " IP [%u]: '%s/%u'", i, str, ipv4address[i]->on_link_prefix_length); + } + } - ipv4configurationavailable_str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv4configurationavailable); - mm_obj_dbg (self, "IPv4 configuration available: '%s'", ipv4configurationavailable_str); + if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) && ipv4gateway) { + g_autoptr(GInetAddress) addr = NULL; + g_autofree gchar *str = NULL; - if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && ipv4addresscount) { - guint i; + addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4); + str = g_inet_address_to_string (addr); + mm_obj_dbg (self, " gateway: '%s'", str); + } + + if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && ipv4dnsservercount) { + guint i; + + mm_obj_dbg (self, " DNS addresses (%u)", ipv4dnsservercount); + for (i = 0; i < ipv4dnsservercount; i++) { + g_autoptr(GInetAddress) addr = NULL; - mm_obj_dbg (self, " IP addresses (%u)", ipv4addresscount); - for (i = 0; i < ipv4addresscount; i++) { - g_autoptr(GInetAddress) addr = NULL; - g_autofree gchar *str = NULL; + addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4); + if (!g_inet_address_get_is_any (addr)) { + g_autofree gchar *str = NULL; - addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4address[i]->ipv4_address, G_SOCKET_FAMILY_IPV4); str = g_inet_address_to_string (addr); - mm_obj_dbg (self, " IP [%u]: '%s/%u'", i, str, ipv4address[i]->on_link_prefix_length); + mm_obj_dbg (self, " DNS [%u]: '%s'", i, str); } } + } + + if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) && ipv4mtu) + mm_obj_dbg (self, " MTU: '%u'", ipv4mtu); + + /* IPv6 info */ + + ipv6configurationavailable_str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv6configurationavailable); + mm_obj_dbg (self, "IPv6 configuration available: '%s'", ipv6configurationavailable_str); - if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) && ipv4gateway) { + if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && ipv6addresscount) { + guint i; + + mm_obj_dbg (self, " IP addresses (%u)", ipv6addresscount); + for (i = 0; i < ipv6addresscount; i++) { g_autoptr(GInetAddress) addr = NULL; g_autofree gchar *str = NULL; - addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4); + addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6address[i]->ipv6_address, G_SOCKET_FAMILY_IPV6); str = g_inet_address_to_string (addr); - mm_obj_dbg (self, " gateway: '%s'", str); + mm_obj_dbg (self, " IP [%u]: '%s/%u'", i, str, ipv6address[i]->on_link_prefix_length); } + } + + if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) && ipv6gateway) { + g_autoptr(GInetAddress) addr = NULL; + g_autofree gchar *str = NULL; - if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && ipv4dnsservercount) { - guint i; + addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6); + str = g_inet_address_to_string (addr); + mm_obj_dbg (self, " gateway: '%s'", str); + } - mm_obj_dbg (self, " DNS addresses (%u)", ipv4dnsservercount); - for (i = 0; i < ipv4dnsservercount; i++) { - g_autoptr(GInetAddress) addr = NULL; + if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && ipv6dnsservercount) { + guint i; - addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4); - if (!g_inet_address_get_is_any (addr)) { - g_autofree gchar *str = NULL; + mm_obj_dbg (self, " DNS addresses (%u)", ipv6dnsservercount); + for (i = 0; i < ipv6dnsservercount; i++) { + g_autoptr(GInetAddress) addr = NULL; + + addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6dnsserver[i], G_SOCKET_FAMILY_IPV6); + if (!g_inet_address_get_is_any (addr)) { + g_autofree gchar *str = NULL; - str = g_inet_address_to_string (addr); - mm_obj_dbg (self, " DNS [%u]: '%s'", i, str); - } + str = g_inet_address_to_string (addr); + mm_obj_dbg (self, " DNS [%u]: '%s'", i, str); } } + } - if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) && ipv4mtu) - mm_obj_dbg (self, " MTU: '%u'", ipv4mtu); + if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) && ipv6mtu) + mm_obj_dbg (self, " MTU: '%u'", ipv6mtu); - /* IPv6 info */ + /* Build connection results */ - ipv6configurationavailable_str = mbim_ip_configuration_available_flag_build_string_from_mask (ipv6configurationavailable); - mm_obj_dbg (self, "IPv6 configuration available: '%s'", ipv6configurationavailable_str); + /* Build IPv4 config */ + if (ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4 || + ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 || + ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { + gboolean address_set = FALSE; - if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && ipv6addresscount) { - guint i; + ipv4_config = mm_bearer_ip_config_new (); - mm_obj_dbg (self, " IP addresses (%u)", ipv6addresscount); - for (i = 0; i < ipv6addresscount; i++) { - g_autoptr(GInetAddress) addr = NULL; - g_autofree gchar *str = NULL; + /* We assume that if we have an IP we can use static configuration. + * Not all modems or providers will return DNS servers or even a + * gateway, and not all modems support DHCP either. The IP management + * daemon/script just has to deal with this... + */ + if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && (ipv4addresscount > 0)) { + g_autoptr(GInetAddress) addr = NULL; + g_autofree gchar *str = NULL; - addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6address[i]->ipv6_address, G_SOCKET_FAMILY_IPV6); - str = g_inet_address_to_string (addr); - mm_obj_dbg (self, " IP [%u]: '%s/%u'", i, str, ipv6address[i]->on_link_prefix_length); + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_STATIC); + + /* IP address, pick the first one */ + addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4address[0]->ipv4_address, G_SOCKET_FAMILY_IPV4); + str = g_inet_address_to_string (addr); + mm_bearer_ip_config_set_address (ipv4_config, str); + address_set = TRUE; + + /* Netmask */ + mm_bearer_ip_config_set_prefix (ipv4_config, ipv4address[0]->on_link_prefix_length); + + /* Gateway */ + if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) { + g_autoptr(GInetAddress) gw_addr = NULL; + g_autofree gchar *gw_str = NULL; + + gw_addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4); + gw_str = g_inet_address_to_string (gw_addr); + mm_bearer_ip_config_set_gateway (ipv4_config, gw_str); } + } else + mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + + /* DNS */ + if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && (ipv4dnsservercount > 0)) { + g_auto(GStrv) strarr = NULL; + guint i; + guint n; + + strarr = g_new0 (gchar *, ipv4dnsservercount + 1); + for (i = 0, n = 0; i < ipv4dnsservercount; i++) { + g_autoptr(GInetAddress) addr = NULL; + + addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4); + if (!g_inet_address_get_is_any (addr)) + strarr[n++] = g_inet_address_to_string (addr); + } + mm_bearer_ip_config_set_dns (ipv4_config, (const gchar **)strarr); } - if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) && ipv6gateway) { + /* MTU */ + if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) + mm_bearer_ip_config_set_mtu (ipv4_config, ipv4mtu); + + /* We requested IPv4, but it wasn't reported as activated. If there is no IP address + * provided by the modem, we assume the IPv4 bearer wasn't truly activated */ + if (!address_set && + ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4 && + ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4V6 && + ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { + mm_obj_dbg (self, "IPv4 requested but no IPv4 activated and no IPv4 address set: ignoring"); + g_clear_object (&ipv4_config); + } + } + + /* Build IPv6 config */ + if (ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV6 || + ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 || + ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { + gboolean address_set = FALSE; + + ipv6_config = mm_bearer_ip_config_new (); + + if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && (ipv6addresscount > 0)) { g_autoptr(GInetAddress) addr = NULL; g_autofree gchar *str = NULL; - addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6); + /* IP address, pick the first one */ + addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6address[0]->ipv6_address, G_SOCKET_FAMILY_IPV6); str = g_inet_address_to_string (addr); - mm_obj_dbg (self, " gateway: '%s'", str); + mm_bearer_ip_config_set_address (ipv6_config, str); + address_set = TRUE; + + /* If the address is a link-local one, then SLAAC or DHCP must be used + * to get the real prefix and address. + * If the address is a global one, then the modem did SLAAC already and + * there is no need to run host SLAAC. + */ + if (g_inet_address_get_is_link_local (addr)) + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); + else + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_STATIC); + + /* Netmask */ + mm_bearer_ip_config_set_prefix (ipv6_config, ipv6address[0]->on_link_prefix_length); + + /* If the modem has done SLAAC itself, it is never expected to return a /128 prefix, + * warn if it happens and workaround it. Use /64 as default. */ + if ((mm_bearer_ip_config_get_method (ipv6_config) == MM_BEARER_IP_METHOD_STATIC) && + (mm_bearer_ip_config_get_prefix (ipv6_config) == 128)) { + mm_obj_warn (self, "unexpected link prefix returned with global IPv6 address (128): ignoring"); + mm_bearer_ip_config_set_prefix (ipv6_config, 64); + } + + /* Gateway */ + if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) { + g_autoptr(GInetAddress) gw_addr = NULL; + g_autofree gchar *gw_str = NULL; + + gw_addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6); + gw_str = g_inet_address_to_string (gw_addr); + mm_bearer_ip_config_set_gateway (ipv6_config, gw_str); + } + } else { + /* If no address is given, this is likely a bug in the modem firmware, because even in the + * case of needing to run host SLAAC, a link-local IPv6 address must be given. Either way, + * go on requesting the need of host SLAAC, and let the network decide whether our SLAAC + * Router Solicitation messages with an unexpected link-local address are accepted or not. */ + mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); } - if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && ipv6dnsservercount) { - guint i; + if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && (ipv6dnsservercount > 0)) { + g_auto(GStrv) strarr = NULL; + guint i; + guint n; - mm_obj_dbg (self, " DNS addresses (%u)", ipv6dnsservercount); - for (i = 0; i < ipv6dnsservercount; i++) { - g_autoptr(GInetAddress) addr = NULL; + /* DNS */ + strarr = g_new0 (gchar *, ipv6dnsservercount + 1); + for (i = 0, n = 0; i < ipv6dnsservercount; i++) { + g_autoptr(GInetAddress) addr = NULL; addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6dnsserver[i], G_SOCKET_FAMILY_IPV6); - if (!g_inet_address_get_is_any (addr)) { - g_autofree gchar *str = NULL; - - str = g_inet_address_to_string (addr); - mm_obj_dbg (self, " DNS [%u]: '%s'", i, str); - } + if (!g_inet_address_get_is_any (addr)) + strarr[n++] = g_inet_address_to_string (addr); } + mm_bearer_ip_config_set_dns (ipv6_config, (const gchar **)strarr); } - if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) && ipv6mtu) - mm_obj_dbg (self, " MTU: '%u'", ipv6mtu); + /* MTU */ + if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) + mm_bearer_ip_config_set_mtu (ipv6_config, ipv6mtu); + + /* We requested IPv6, but it wasn't reported as activated. If there is no IPv6 address + * provided by the modem, we assume the IPv6 bearer wasn't truly activated */ + if (!address_set && + ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV6 && + ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4V6 && + ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { + mm_obj_dbg (self, "IPv6 requested but no IPv6 activated and no IPv6 address set: ignoring"); + g_clear_object (&ipv6_config); + } + } + + /* Store result */ + ctx->connect_result = mm_bearer_connect_result_new (ctx->link ? ctx->link : ctx->data, + ipv4_config, + ipv6_config); + mm_bearer_connect_result_set_multiplexed (ctx->connect_result, !!ctx->link); - /* Build connection results */ + if (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN) + mm_bearer_connect_result_set_profile_id (ctx->connect_result, ctx->profile_id); - /* Build IPv4 config */ - if (ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4 || - ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 || - ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { - gboolean address_set = FALSE; + /* Propagate speeds from modem object */ + mm_broadband_modem_mbim_get_speeds (ctx->modem, &uplink_speed, &downlink_speed); + mm_bearer_connect_result_set_uplink_speed (ctx->connect_result, uplink_speed); + mm_bearer_connect_result_set_downlink_speed (ctx->connect_result, downlink_speed); +} - ipv4_config = mm_bearer_ip_config_new (); +static void +ip_configuration_async_cleanup (GTask *task) +{ + ConnectContext *ctx; - /* We assume that if we have an IP we can use static configuration. - * Not all modems or providers will return DNS servers or even a - * gateway, and not all modems support DHCP either. The IP management - * daemon/script just has to deal with this... - */ - if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && (ipv4addresscount > 0)) { - g_autoptr(GInetAddress) addr = NULL; - g_autofree gchar *str = NULL; + ctx = g_task_get_task_data (task); - mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_STATIC); + g_assert (ctx->async_slaac_cancellation_id); + g_cancellable_disconnect (g_task_get_cancellable (task), ctx->async_slaac_cancellation_id); + ctx->async_slaac_cancellation_id = 0; - /* IP address, pick the first one */ - addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4address[0]->ipv4_address, G_SOCKET_FAMILY_IPV4); - str = g_inet_address_to_string (addr); - mm_bearer_ip_config_set_address (ipv4_config, str); - address_set = TRUE; + g_assert (ctx->async_slaac_notification_id); + if (g_signal_handler_is_connected (ctx->mbim, ctx->async_slaac_notification_id)) + g_signal_handler_disconnect (ctx->mbim, ctx->async_slaac_notification_id); + ctx->async_slaac_notification_id = 0; - /* Netmask */ - mm_bearer_ip_config_set_prefix (ipv4_config, ipv4address[0]->on_link_prefix_length); + g_assert (ctx->async_slaac_timeout_id); + g_source_remove (ctx->async_slaac_timeout_id); + ctx->async_slaac_timeout_id = 0; +} - /* Gateway */ - if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) { - g_autoptr(GInetAddress) gw_addr = NULL; - g_autofree gchar *gw_str = NULL; +static gboolean +ip_configuration_async_timeout (GTask *task) +{ + ip_configuration_async_cleanup (task); + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_TIMEOUT, + "Timed out waiting for SLAAC notification"); + g_object_unref (task); - gw_addr = g_inet_address_new_from_bytes ((guint8 *)ipv4gateway, G_SOCKET_FAMILY_IPV4); - gw_str = g_inet_address_to_string (gw_addr); - mm_bearer_ip_config_set_gateway (ipv4_config, gw_str); - } - } else - mm_bearer_ip_config_set_method (ipv4_config, MM_BEARER_IP_METHOD_DHCP); + return G_SOURCE_REMOVE; +} - /* DNS */ - if ((ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && (ipv4dnsservercount > 0)) { - g_auto(GStrv) strarr = NULL; - guint i; - guint n; - - strarr = g_new0 (gchar *, ipv4dnsservercount + 1); - for (i = 0, n = 0; i < ipv4dnsservercount; i++) { - g_autoptr(GInetAddress) addr = NULL; - - addr = g_inet_address_new_from_bytes ((guint8 *)&ipv4dnsserver[i], G_SOCKET_FAMILY_IPV4); - if (!g_inet_address_get_is_any (addr)) - strarr[n++] = g_inet_address_to_string (addr); - } - mm_bearer_ip_config_set_dns (ipv4_config, (const gchar **)strarr); - } +static void +ip_configuration_async_cancelled (GTask *task) +{ + ip_configuration_async_cleanup (task); + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, + "Cancelled waiting for SLAAC notification"); + g_object_unref (task); +} - /* MTU */ - if (ipv4configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) - mm_bearer_ip_config_set_mtu (ipv4_config, ipv4mtu); - - /* We requested IPv4, but it wasn't reported as activated. If there is no IP address - * provided by the modem, we assume the IPv4 bearer wasn't truly activated */ - if (!address_set && - ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4 && - ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4V6 && - ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { - mm_obj_dbg (self, "IPv4 requested but no IPv4 activated and no IPv4 address set: ignoring"); - g_clear_object (&ipv4_config); - } - } +static void +ip_configuration_async_notification (GTask *task, + MbimMessage *notification, + MMPortMbim *port) +{ + MMBearerMbim *self; + ConnectContext *ctx; + MMBearerIpConfig *ipv6_config; + g_autoptr(GError) error = NULL; + guint32 session_id; + MbimIPConfigurationAvailableFlag ipv4configurationavailable; + MbimIPConfigurationAvailableFlag ipv6configurationavailable; + guint32 ipv4addresscount; + g_autoptr(MbimIPv4ElementArray) ipv4address = NULL; + guint32 ipv6addresscount; + g_autoptr(MbimIPv6ElementArray) ipv6address = NULL; + const MbimIPv4 *ipv4gateway; + const MbimIPv6 *ipv6gateway; + guint32 ipv4dnsservercount; + g_autofree MbimIPv4 *ipv4dnsserver = NULL; + guint32 ipv6dnsservercount; + g_autofree MbimIPv6 *ipv6dnsserver = NULL; + guint32 ipv4mtu; + guint32 ipv6mtu; - /* Build IPv6 config */ - if (ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV6 || - ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4V6 || - ctx->requested_ip_type == MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { - gboolean address_set = FALSE; + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); - ipv6_config = mm_bearer_ip_config_new (); + /* Only process notifications if the device still exists */ + if (!mm_port_mbim_peek_device (port)) + return; - if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_ADDRESS) && (ipv6addresscount > 0)) { - g_autoptr(GInetAddress) addr = NULL; - g_autofree gchar *str = NULL; + /* We only want the IP configuration updates */ + if ((mbim_message_indicate_status_get_service (notification) != MBIM_SERVICE_BASIC_CONNECT) || + (mbim_message_indicate_status_get_cid (notification) != MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION)) + return; - /* IP address, pick the first one */ - addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6address[0]->ipv6_address, G_SOCKET_FAMILY_IPV6); - str = g_inet_address_to_string (addr); - mm_bearer_ip_config_set_address (ipv6_config, str); - address_set = TRUE; - - /* If the address is a link-local one, then SLAAC or DHCP must be used - * to get the real prefix and address. - * If the address is a global one, then the modem did SLAAC already and - * there is no need to run host SLAAC. - */ - if (g_inet_address_get_is_link_local (addr)) - mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); - else - mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_STATIC); - - /* Netmask */ - mm_bearer_ip_config_set_prefix (ipv6_config, ipv6address[0]->on_link_prefix_length); - - /* If the modem has done SLAAC itself, it is never expected to return a /128 prefix, - * warn if it happens and workaround it. Use /64 as default. */ - if ((mm_bearer_ip_config_get_method (ipv6_config) == MM_BEARER_IP_METHOD_STATIC) && - (mm_bearer_ip_config_get_prefix (ipv6_config) == 128)) { - mm_obj_warn (self, "unexpected link prefix returned with global IPv6 address (128): ignoring"); - mm_bearer_ip_config_set_prefix (ipv6_config, 64); - } - - /* Gateway */ - if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_GATEWAY) { - g_autoptr(GInetAddress) gw_addr = NULL; - g_autofree gchar *gw_str = NULL; - - gw_addr = g_inet_address_new_from_bytes ((guint8 *)ipv6gateway, G_SOCKET_FAMILY_IPV6); - gw_str = g_inet_address_to_string (gw_addr); - mm_bearer_ip_config_set_gateway (ipv6_config, gw_str); - } - } else { - /* If no address is given, this is likely a bug in the modem firmware, because even in the - * case of needing to run host SLAAC, a link-local IPv6 address must be given. Either way, - * go on requesting the need of host SLAAC, and let the network decide whether our SLAAC - * Router Solicitation messages with an unexpected link-local address are accepted or not. */ - mm_bearer_ip_config_set_method (ipv6_config, MM_BEARER_IP_METHOD_DHCP); - } + if (!mbim_message_ip_configuration_notification_parse ( + notification, + &session_id, + &ipv4configurationavailable, + &ipv6configurationavailable, + &ipv4addresscount, &ipv4address, + &ipv6addresscount, &ipv6address, + &ipv4gateway, &ipv6gateway, + &ipv4dnsservercount, &ipv4dnsserver, + &ipv6dnsservercount, &ipv6dnsserver, + &ipv4mtu, &ipv6mtu, + &error)) { + mm_obj_warn (self, "couldn't parse IP configuration indication: %s", error->message); + return; + } - if ((ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_DNS) && (ipv6dnsservercount > 0)) { - g_auto(GStrv) strarr = NULL; - guint i; - guint n; - - /* DNS */ - strarr = g_new0 (gchar *, ipv6dnsservercount + 1); - for (i = 0, n = 0; i < ipv6dnsservercount; i++) { - g_autoptr(GInetAddress) addr = NULL; - - addr = g_inet_address_new_from_bytes ((guint8 *)&ipv6dnsserver[i], G_SOCKET_FAMILY_IPV6); - if (!g_inet_address_get_is_any (addr)) - strarr[n++] = g_inet_address_to_string (addr); - } - mm_bearer_ip_config_set_dns (ipv6_config, (const gchar **)strarr); - } + /* We only want the updates for the current session ongoing */ + if (ctx->session_id != session_id) + return; - /* MTU */ - if (ipv6configurationavailable & MBIM_IP_CONFIGURATION_AVAILABLE_FLAG_MTU) - mm_bearer_ip_config_set_mtu (ipv6_config, ipv6mtu); - - /* We requested IPv6, but it wasn't reported as activated. If there is no IPv6 address - * provided by the modem, we assume the IPv6 bearer wasn't truly activated */ - if (!address_set && - ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV6 && - ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4V6 && - ctx->activated_ip_type != MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6) { - mm_obj_dbg (self, "IPv6 requested but no IPv6 activated and no IPv6 address set: ignoring"); - g_clear_object (&ipv6_config); - } - } + /* Process content in a common way */ + process_ip_configuration (self, + ctx, + ipv4configurationavailable, + ipv6configurationavailable, + ipv4addresscount, ipv4address, + ipv6addresscount, ipv6address, + ipv4gateway, ipv6gateway, + ipv4dnsservercount, ipv4dnsserver, + ipv6dnsservercount, ipv6dnsserver, + ipv4mtu, ipv6mtu); + + /* We will be done once method is STATIC (i.e. when a global IPv6 is detected) */ + ipv6_config = mm_bearer_connect_result_peek_ipv6_config (ctx->connect_result); + if (!ipv6_config || (mm_bearer_ip_config_get_method (ipv6_config) != MM_BEARER_IP_METHOD_STATIC)) { + mm_obj_dbg (self, "SLAAC process didn't finish yet"); + return; + } + + mm_obj_dbg (self, "SLAAC process finished"); + ip_configuration_async_cleanup (task); + + /* Keep on */ + ctx->step++; + connect_context_step (task); +} + +static gboolean +ip_configuration_async_setup (GTask *task) +{ + MMBearerMbim *self; + ConnectContext *ctx; + MMBearerIpConfig *ipv6_config; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* On async SLAAC results if not supported by modem */ + if (!self->priv->async_slaac) + return FALSE; + + /* Only expect SLAAC results if the attempt required IPv6 and if we didn't already + * get a global IPv6 address reported. */ + ipv6_config = mm_bearer_connect_result_peek_ipv6_config (ctx->connect_result); + if (!ipv6_config || (mm_bearer_ip_config_get_method (ipv6_config) != MM_BEARER_IP_METHOD_DHCP)) + return FALSE; + + mm_obj_info (self, "connection attempt requires async updated with SLAAC results"); + + /* The task implementing the connection attempt is now shared between the timeout, the notification + * signal handler and the cancellation signal handler. Whichever decides to complete the task must + * cancel the others right away. */ + ctx->async_slaac_cancellation_id = g_cancellable_connect (g_task_get_cancellable (task), + G_CALLBACK (ip_configuration_async_cancelled), + task, + NULL); + ctx->async_slaac_timeout_id = g_timeout_add_seconds (WAIT_IP_CONFIGURATION_ASYNC_TIMEOUT_S, + (GSourceFunc) ip_configuration_async_timeout, + task); + ctx->async_slaac_notification_id = g_signal_connect_swapped (ctx->mbim, + MM_PORT_MBIM_SIGNAL_NOTIFICATION, + G_CALLBACK (ip_configuration_async_notification), + task); + return TRUE; +} - /* Store result */ - ctx->connect_result = mm_bearer_connect_result_new (ctx->link ? ctx->link : ctx->data, - ipv4_config, - ipv6_config); - mm_bearer_connect_result_set_multiplexed (ctx->connect_result, !!ctx->link); +static void +ip_configuration_query_ready (MbimDevice *device, + GAsyncResult *res, + GTask *task) +{ + MMBearerMbim *self; + ConnectContext *ctx; + GError *error = NULL; + g_autoptr(MbimMessage) response = NULL; + MbimIPConfigurationAvailableFlag ipv4configurationavailable; + MbimIPConfigurationAvailableFlag ipv6configurationavailable; + guint32 ipv4addresscount; + g_autoptr(MbimIPv4ElementArray) ipv4address = NULL; + guint32 ipv6addresscount; + g_autoptr(MbimIPv6ElementArray) ipv6address = NULL; + const MbimIPv4 *ipv4gateway; + const MbimIPv6 *ipv6gateway; + guint32 ipv4dnsservercount; + g_autofree MbimIPv4 *ipv4dnsserver = NULL; + guint32 ipv6dnsservercount; + g_autofree MbimIPv6 *ipv6dnsserver = NULL; + guint32 ipv4mtu; + guint32 ipv6mtu; - if (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN) - mm_bearer_connect_result_set_profile_id (ctx->connect_result, ctx->profile_id); + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); - /* Propagate speeds from modem object */ - mm_broadband_modem_mbim_get_speeds (ctx->modem, &uplink_speed, &downlink_speed); - mm_bearer_connect_result_set_uplink_speed (ctx->connect_result, uplink_speed); - mm_bearer_connect_result_set_downlink_speed (ctx->connect_result, downlink_speed); + response = mbim_device_command_finish (device, res, &error); + if (response && + mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) && + mbim_message_ip_configuration_response_parse ( + response, + NULL, /* sessionid */ + &ipv4configurationavailable, + &ipv6configurationavailable, + &ipv4addresscount, &ipv4address, + &ipv6addresscount, &ipv6address, + &ipv4gateway, &ipv6gateway, + &ipv4dnsservercount, &ipv4dnsserver, + &ipv6dnsservercount, &ipv6dnsserver, + &ipv4mtu, &ipv6mtu, + &error)) { + /* Process content in a common way */ + process_ip_configuration (self, + ctx, + ipv4configurationavailable, + ipv6configurationavailable, + ipv4addresscount, ipv4address, + ipv6addresscount, ipv6address, + ipv4gateway, ipv6gateway, + ipv4dnsservercount, ipv4dnsserver, + ipv6dnsservercount, ipv6dnsserver, + ipv4mtu, ipv6mtu); } if (error) { @@ -1181,6 +1398,17 @@ connect_context_step (GTask *task) task); return; + case CONNECT_STEP_IP_CONFIGURATION_ASYNC: + /* At this point, we must have IP settings ready. Now, if we know the modem + * supports modem-SLAAC and we're returning IPv6 settings, we must make sure + * we're returning already a global IPv6 address and method STATIC, or otherwise + * we need to wait for indications to happen. */ + if (ip_configuration_async_setup (task)) + return; + + ctx->step++; + /* fall through */ + case CONNECT_STEP_LAST: /* Cleanup the abort message so that we don't * run it */ @@ -1220,7 +1448,7 @@ connect_context_step (GTask *task) static gboolean load_settings_from_bearer (MMBearerMbim *self, ConnectContext *ctx, - MMBearerProperties *properties, + MMBearerProperties *props, GError **error) { MMBearerMultiplexSupport multiplex; @@ -1241,7 +1469,7 @@ load_settings_from_bearer (MMBearerMbim *self, multiplex_supported = FALSE; /* If no multiplex setting given by the user, assume none */ - multiplex = mm_bearer_properties_get_multiplex (properties); + multiplex = mm_bearer_properties_get_multiplex (props); if (multiplex == MM_BEARER_MULTIPLEX_SUPPORT_UNKNOWN) { if (mm_context_get_test_multiplex_requested ()) multiplex = MM_BEARER_MULTIPLEX_SUPPORT_REQUESTED; @@ -1279,14 +1507,14 @@ load_settings_from_bearer (MMBearerMbim *self, /* If profile id is given, we'll load all settings from the stored profile, * so ignore any other setting received in the bearer properties */ - ctx->profile_id = mm_bearer_properties_get_profile_id (properties); + ctx->profile_id = mm_bearer_properties_get_profile_id (props); if (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN) { MMBearerIpFamily ip_type; GError *inner_error = NULL; /* If we're loading settings from a profile, still read the ip-type * from the user input, as that is not stored in the profile */ - ip_type = mm_bearer_properties_get_ip_type (properties); + ip_type = mm_bearer_properties_get_ip_type (props); mm_3gpp_normalize_ip_family (&ip_type, FALSE); ctx->requested_ip_type = mm_bearer_ip_family_to_mbim_context_ip_type (ip_type, &inner_error); if (inner_error) { @@ -1301,7 +1529,7 @@ load_settings_from_bearer (MMBearerMbim *self, * (TYPE_DEFAULT) by default, which is what we've done until now. */ if (!load_settings_from_profile (self, ctx, - mm_bearer_properties_peek_3gpp_profile (properties), + mm_bearer_properties_peek_3gpp_profile (props), MM_BEARER_APN_TYPE_DEFAULT, error)) return FALSE; @@ -1328,7 +1556,7 @@ _connect (MMBaseBearer *self, GTask *task; GError *error = NULL; g_autoptr(MMBaseModem) modem = NULL; - g_autoptr(MMBearerProperties) properties = NULL; + g_autoptr(MMBearerProperties) props = NULL; if (!peek_ports (self, &mbim, &data, callback, user_data)) return; @@ -1337,7 +1565,7 @@ _connect (MMBaseBearer *self, g_object_get (self, MM_BASE_BEARER_MODEM, &modem, - MM_BASE_BEARER_CONFIG, &properties, + MM_BASE_BEARER_CONFIG, &props, NULL); g_assert (modem); @@ -1350,7 +1578,7 @@ _connect (MMBaseBearer *self, ctx->activated_ip_type = MBIM_CONTEXT_IP_TYPE_DEFAULT; g_task_set_task_data (task, ctx, (GDestroyNotify)connect_context_free); - if (!load_settings_from_bearer (MM_BEARER_MBIM (self), ctx, properties, &error)) { + if (!load_settings_from_bearer (MM_BEARER_MBIM (self), ctx, props, &error)) { g_prefix_error (&error, "Invalid bearer properties: "); g_task_return_error (task, error); g_object_unref (task); @@ -1797,6 +2025,42 @@ mm_bearer_mbim_init (MMBearerMbim *self) } static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBearerMbim *self = MM_BEARER_MBIM (object); + + switch (prop_id) { + case PROP_ASYNC_SLAAC: + self->priv->async_slaac = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMBearerMbim *self = MM_BEARER_MBIM (object); + + switch (prop_id) { + case PROP_ASYNC_SLAAC: + g_value_set_boolean (value, self->priv->async_slaac); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void dispose (GObject *object) { MMBearerMbim *self = MM_BEARER_MBIM (object); @@ -1815,6 +2079,8 @@ mm_bearer_mbim_class_init (MMBearerMbimClass *klass) g_type_class_add_private (object_class, sizeof (MMBearerMbimPrivate)); /* Virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; object_class->dispose = dispose; base_bearer_class->connect = _connect; @@ -1830,4 +2096,12 @@ mm_bearer_mbim_class_init (MMBearerMbimClass *klass) base_bearer_class->reload_connection_status = reload_connection_status; base_bearer_class->reload_connection_status_finish = reload_connection_status_finish; #endif + + properties[PROP_ASYNC_SLAAC] = + g_param_spec_boolean (MM_BEARER_MBIM_ASYNC_SLAAC, + "Async SLAAC", + "Whether async SLAAC updates are expected.", + FALSE, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_ASYNC_SLAAC, properties[PROP_ASYNC_SLAAC]); } diff --git a/src/mm-bearer-mbim.h b/src/mm-bearer-mbim.h index a1231989..d1867225 100644 --- a/src/mm-bearer-mbim.h +++ b/src/mm-bearer-mbim.h @@ -32,6 +32,8 @@ #define MM_IS_BEARER_MBIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BEARER_MBIM)) #define MM_BEARER_MBIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BEARER_MBIM, MMBearerMbimClass)) +#define MM_BEARER_MBIM_ASYNC_SLAAC "bearer-mbim-async-slaac" + typedef struct _MMBearerMbim MMBearerMbim; typedef struct _MMBearerMbimClass MMBearerMbimClass; typedef struct _MMBearerMbimPrivate MMBearerMbimPrivate; |