mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-26 18:10:08 +01:00
This is rather bad, because if we reach the "goto again" case,
the error variable is not cleared. Subsequently passing the
error location to nm_device_reapply_finish() will trigger a glib
warning.
Fixes: 29b0420be7 ('nm-cloud-setup: set preserve-external-ip flag during reapply')
617 lines
18 KiB
C
617 lines
18 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "libnm-client-aux-extern/nm-default-client.h"
|
|
|
|
#include "nm-cloud-setup-utils.h"
|
|
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_infiniband.h>
|
|
|
|
#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;
|
|
}
|