/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "libnm-client-aux-extern/nm-default-client.h" #include "nm-cloud-setup-utils.h" #include #include #include "libnm-glib-aux/nm-time-utils.h" #include "libnm-glib-aux/nm-logging-base.h" #include "libnm-glib-aux/nm-str-buf.h" /*****************************************************************************/ G_LOCK_DEFINE_STATIC(_wait_for_objects_lock); static GSList *_wait_for_objects_list; static GSList *_wait_for_objects_iterate_loops; static void _wait_for_objects_maybe_quit_mainloops_with_lock(void) { GSList *iter; if (!_wait_for_objects_list) { for (iter = _wait_for_objects_iterate_loops; iter; iter = iter->next) g_main_loop_quit(iter->data); } } static void _wait_for_objects_weak_cb(gpointer data, GObject *where_the_object_was) { G_LOCK(_wait_for_objects_lock); nm_assert(g_slist_find(_wait_for_objects_list, where_the_object_was)); _wait_for_objects_list = g_slist_remove(_wait_for_objects_list, where_the_object_was); _wait_for_objects_maybe_quit_mainloops_with_lock(); G_UNLOCK(_wait_for_objects_lock); } /** * nmcs_wait_for_objects_register: * @target: a #GObject to wait for. * * Registers @target as a pointer to wait during shutdown. Using * nmcs_wait_for_objects_iterate_until_done() we keep waiting until * @target gets destroyed, which means that it gets completely unreferenced. */ gpointer nmcs_wait_for_objects_register(gpointer target) { g_return_val_if_fail(G_IS_OBJECT(target), NULL); G_LOCK(_wait_for_objects_lock); _wait_for_objects_list = g_slist_prepend(_wait_for_objects_list, target); G_UNLOCK(_wait_for_objects_lock); g_object_weak_ref(target, _wait_for_objects_weak_cb, NULL); return target; } typedef struct { GMainLoop *loop; gboolean got_timeout; } WaitForObjectsData; static gboolean _wait_for_objects_iterate_until_done_timeout_cb(gpointer user_data) { WaitForObjectsData *data = user_data; data->got_timeout = TRUE; g_main_loop_quit(data->loop); return G_SOURCE_CONTINUE; } static gboolean _wait_for_objects_iterate_until_done_idle_cb(gpointer user_data) { /* This avoids a race where: * * - we check whether there are objects to wait for. * - the last object to wait for gets removed (issuing g_main_loop_quit()). * - we run the mainloop (and missed our signal). * * It's really a missing feature of GMainLoop where the "is-running" flag is always set to * TRUE by g_main_loop_run(). That means, you cannot catch a g_main_loop_quit() in a race * free way while not iterating the loop. * * Avoid this, by checking once again after we start running the mainloop. */ G_LOCK(_wait_for_objects_lock); _wait_for_objects_maybe_quit_mainloops_with_lock(); G_UNLOCK(_wait_for_objects_lock); return G_SOURCE_REMOVE; } /** * nmcs_wait_for_objects_iterate_until_done: * @context: the #GMainContext to iterate. * @timeout_msec: timeout or -1 for no timeout. * * Iterates the provided @context until all objects that we wait for * are destroyed. * * The purpose of this is to cleanup all objects that we have on exit. That * is especially because objects have asynchronous operations pending that * should be cancelled and properly completed during exit. * * Returns: %FALSE on timeout or %TRUE if all objects destroyed before timeout. */ gboolean nmcs_wait_for_objects_iterate_until_done(GMainContext *context, int timeout_msec) { nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(context, FALSE); nm_auto_destroy_and_unref_gsource GSource *timeout_source = NULL; WaitForObjectsData data; gboolean has_more_objects; G_LOCK(_wait_for_objects_lock); if (!_wait_for_objects_list) { G_UNLOCK(_wait_for_objects_lock); return TRUE; } _wait_for_objects_iterate_loops = g_slist_prepend(_wait_for_objects_iterate_loops, loop); G_UNLOCK(_wait_for_objects_lock); data = (WaitForObjectsData){ .loop = loop, .got_timeout = FALSE, }; if (timeout_msec >= 0) { timeout_source = nm_g_source_attach( nm_g_timeout_source_new(timeout_msec, G_PRIORITY_DEFAULT, _wait_for_objects_iterate_until_done_timeout_cb, &data, NULL), context); } has_more_objects = TRUE; while (has_more_objects && !data.got_timeout) { nm_auto_destroy_and_unref_gsource GSource *idle_source = NULL; idle_source = nm_g_source_attach(nm_g_idle_source_new(G_PRIORITY_DEFAULT_IDLE, _wait_for_objects_iterate_until_done_idle_cb, &data, NULL), context); g_main_loop_run(loop); G_LOCK(_wait_for_objects_lock); has_more_objects = (!!_wait_for_objects_list); if (data.got_timeout || !has_more_objects) _wait_for_objects_iterate_loops = g_slist_remove(_wait_for_objects_iterate_loops, loop); G_UNLOCK(_wait_for_objects_lock); } return !data.got_timeout; } /*****************************************************************************/ char * nmcs_utils_hwaddr_normalize(const char *hwaddr, gssize len) { gs_free char *hwaddr_clone = NULL; char *hw; guint8 buf[ETH_ALEN]; gsize l; nm_assert(len >= -1); if (len < 0) { if (!hwaddr) return NULL; l = strlen(hwaddr); } else { l = len; if (l > 0 && hwaddr[l - 1] == '\0') { /* we accept one '\0' at the end of the string. */ l--; } if (memchr(hwaddr, '\0', l)) { /* but we don't accept other NUL characters in the middle. */ return NULL; } } if (l == 0) return NULL; nm_assert(hwaddr); hw = nm_strndup_a(300, hwaddr, l, &hwaddr_clone); g_strstrip(hw); /* we cannot use _nm_utils_hwaddr_aton() because that requires a delimiter. * Azure exposes MAC addresses without delimiter, so accept that too. */ if (!nm_utils_hexstr2bin_full(hw, FALSE, FALSE, FALSE, ":-", sizeof(buf), buf, sizeof(buf), NULL)) return NULL; return nm_utils_hwaddr_ntoa(buf, sizeof(buf)); } /*****************************************************************************/ gboolean nmcs_utils_ipaddr_normalize_bin(int addr_family, const char *addr, gssize len, int *out_addr_family, gpointer out_addr_bin) { gs_free char *addr_clone = NULL; char *ad; gsize l; nm_assert(len >= -1); if (len < 0) { if (!addr) return FALSE; l = strlen(addr); } else { l = len; if (l > 0 && addr[l - 1] == '\0') { /* we accept one '\0' at the end of the string. */ l--; } if (memchr(addr, '\0', l)) { /* but we don't accept other NUL characters in the middle. */ return FALSE; } } if (l == 0) return FALSE; nm_assert(addr); ad = nm_strndup_a(300, addr, l, &addr_clone); g_strstrip(ad); return nm_inet_parse_bin(addr_family, ad, out_addr_family, out_addr_bin); } char * nmcs_utils_ipaddr_normalize(int addr_family, const char *addr, gssize len) { NMIPAddr ipaddr; if (!nmcs_utils_ipaddr_normalize_bin(addr_family, addr, len, &addr_family, &ipaddr)) return NULL; return nm_inet_ntop_dup(addr_family, &ipaddr); } /*****************************************************************************/ const char * nmcs_utils_parse_memmem(GBytes *mem, const char *needle) { const char *mem_data; gsize mem_size; g_return_val_if_fail(mem, NULL); g_return_val_if_fail(needle, NULL); mem_data = g_bytes_get_data(mem, &mem_size); return memmem(mem_data, mem_size, needle, strlen(needle)); } const char * nmcs_utils_parse_get_full_line(GBytes *mem, const char *needle) { const char *mem_data; gsize mem_size; gsize c; gsize l; const char *line; line = nmcs_utils_parse_memmem(mem, needle); if (!line) return NULL; mem_data = g_bytes_get_data(mem, &mem_size); if (line != mem_data && line[-1] != '\n') { /* the line must be preceeded either by the begin of the data or * by a newline. */ return NULL; } c = mem_size - (line - mem_data); l = strlen(needle); if (c != l && line[l] != '\n') { /* the end of the needle must be either a newline or the end of the buffer. */ return NULL; } return line; } /*****************************************************************************/ char * nmcs_utils_uri_build_concat_v(const char *base, const char **components, gsize n_components) { NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE); nm_assert(base); nm_assert(base[0]); nm_assert(!NM_STR_HAS_SUFFIX(base, "/")); nm_str_buf_append(&strbuf, base); if (n_components > 0 && components[0] && components[0][0] == '/') { /* the first component starts with a slash. We allow that, and don't add a duplicate * slash. Otherwise, we add a separator after base. * * We only do that for the first component. */ } else nm_str_buf_append_c(&strbuf, '/'); while (n_components > 0) { if (!components[0]) { /* we allow NULL, to indicate nothing to append */ } else nm_str_buf_append(&strbuf, components[0]); components++; n_components--; } return nm_str_buf_finalize(&strbuf, NULL); } const char * nmcs_utils_uri_complete_interned(const char *uri) { gs_free char *s = NULL; if (nm_str_is_empty(uri)) return NULL; if (NM_STR_HAS_PREFIX(uri, "http://") || NM_STR_HAS_PREFIX(uri, "https://") || strchr(uri, '/')) return g_intern_string(uri); s = g_strconcat("http://", uri, NULL); return g_intern_string(s); } /*****************************************************************************/ gboolean nmcs_setting_ip_replace_ipv4_addresses(NMSettingIPConfig *s_ip, NMIPAddress **entries_arr, guint entries_len) { gboolean any_changes = FALSE; guint i_next; guint num; guint i; num = nm_setting_ip_config_get_num_addresses(s_ip); i_next = 0; for (i = 0; i < entries_len; i++) { NMIPAddress *entry = entries_arr[i]; if (!any_changes) { if (i_next < num) { if (nm_ip_address_cmp_full(entry, nm_setting_ip_config_get_address(s_ip, i_next), NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS) == 0) { i_next++; continue; } } while (i_next < num) nm_setting_ip_config_remove_address(s_ip, --num); any_changes = TRUE; } if (!nm_setting_ip_config_add_address(s_ip, entry)) continue; i_next++; } if (!any_changes) { while (i_next < num) { nm_setting_ip_config_remove_address(s_ip, --num); any_changes = TRUE; } } return any_changes; } gboolean nmcs_setting_ip_replace_ipv4_routes(NMSettingIPConfig *s_ip, NMIPRoute **entries_arr, guint entries_len) { gboolean any_changes = FALSE; guint i_next; guint num; guint i; num = nm_setting_ip_config_get_num_routes(s_ip); i_next = 0; for (i = 0; i < entries_len; i++) { NMIPRoute *entry = entries_arr[i]; if (!any_changes) { if (i_next < num) { if (nm_ip_route_equal_full(entry, nm_setting_ip_config_get_route(s_ip, i_next), NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS)) { i_next++; continue; } } while (i_next < num) nm_setting_ip_config_remove_route(s_ip, --num); any_changes = TRUE; } if (!nm_setting_ip_config_add_route(s_ip, entry)) continue; i_next++; } if (!any_changes) { while (i_next < num) { nm_setting_ip_config_remove_route(s_ip, --num); any_changes = TRUE; } } return any_changes; } gboolean nmcs_setting_ip_replace_ipv4_rules(NMSettingIPConfig *s_ip, NMIPRoutingRule **entries_arr, guint entries_len) { gboolean any_changes = FALSE; guint i_next; guint num; guint i; num = nm_setting_ip_config_get_num_routing_rules(s_ip); i_next = 0; for (i = 0; i < entries_len; i++) { NMIPRoutingRule *entry = entries_arr[i]; if (!any_changes) { if (i_next < num) { if (nm_ip_routing_rule_cmp(entry, nm_setting_ip_config_get_routing_rule(s_ip, i_next)) == 0) { i_next++; continue; } } while (i_next < num) nm_setting_ip_config_remove_routing_rule(s_ip, --num); any_changes = TRUE; } nm_setting_ip_config_add_routing_rule(s_ip, entry); i_next++; } if (!any_changes) { while (i_next < num) { nm_setting_ip_config_remove_routing_rule(s_ip, --num); any_changes = TRUE; } } return any_changes; } /*****************************************************************************/ typedef struct { GMainLoop *main_loop; NMConnection *connection; GError *error; guint64 version_id; } DeviceGetAppliedConnectionData; static void _nmcs_device_get_applied_connection_cb(GObject *source, GAsyncResult *result, gpointer user_data) { DeviceGetAppliedConnectionData *data = user_data; data->connection = nm_device_get_applied_connection_finish(NM_DEVICE(source), result, &data->version_id, &data->error); g_main_loop_quit(data->main_loop); } NMConnection * nmcs_device_get_applied_connection(NMDevice *device, GCancellable *cancellable, guint64 *version_id, GError **error) { nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(NULL, FALSE); DeviceGetAppliedConnectionData data = { .main_loop = main_loop, }; nm_device_get_applied_connection_async(device, 0, cancellable, _nmcs_device_get_applied_connection_cb, &data); g_main_loop_run(main_loop); if (data.error) g_propagate_error(error, data.error); NM_SET_OUT(version_id, data.version_id); return data.connection; } /*****************************************************************************/ typedef struct { GMainLoop *main_loop; GError *error; } DeviceReapplyData; static void _nmcs_device_reapply_cb(GObject *source, GAsyncResult *result, gpointer user_data) { DeviceReapplyData *data = user_data; nm_device_reapply_finish(NM_DEVICE(source), result, &data->error); g_main_loop_quit(data->main_loop); } gboolean nmcs_device_reapply(NMDevice *device, GCancellable *sigterm_cancellable, NMConnection *connection, guint64 version_id, gboolean maybe_no_preserved_external_ip, gboolean *out_version_id_changed, GError **error) { nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(NULL, FALSE); DeviceReapplyData data = { .main_loop = main_loop, }; NMDeviceReapplyFlags reapply_flags = NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP; again: g_clear_error(&data.error); nm_device_reapply_async(device, connection, version_id, reapply_flags, sigterm_cancellable, _nmcs_device_reapply_cb, &data); g_main_loop_run(main_loop); if (data.error) { if (maybe_no_preserved_external_ip && reapply_flags == NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP && nm_g_error_matches(data.error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED)) { /* Hm? Maybe we running against an older version of NetworkManager that * doesn't support "preserve-external-ip" flags? Retry without the flag. * * Note that recent version would reject invalid flags with NM_DEVICE_ERROR_INVALID_ARGUMENT, * but we want to detect old daemon versions here. */ reapply_flags = NM_DEVICE_REAPPLY_FLAGS_NONE; goto again; } NM_SET_OUT( out_version_id_changed, g_error_matches(data.error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_VERSION_ID_MISMATCH)); g_propagate_error(error, data.error); return FALSE; } NM_SET_OUT(out_version_id_changed, FALSE); return TRUE; }