diff options
Diffstat (limited to 'src/nm-core-utils.c')
| -rw-r--r-- | src/nm-core-utils.c | 5121 |
1 files changed, 0 insertions, 5121 deletions
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c deleted file mode 100644 index 7adc011e..00000000 --- a/src/nm-core-utils.c +++ /dev/null @@ -1,5121 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (C) 2004 - 2018 Red Hat, Inc. - * Copyright (C) 2005 - 2008 Novell, Inc. - */ - -#include "nm-default.h" - -#include "nm-core-utils.h" - -#include <fcntl.h> -#include <fnmatch.h> -#include <unistd.h> -#include <stdlib.h> -#include <resolv.h> -#include <byteswap.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <sys/stat.h> -#include <linux/if.h> -#include <linux/if_infiniband.h> -#include <net/if_arp.h> -#include <net/ethernet.h> - -#include "nm-std-aux/unaligned.h" -#include "nm-glib-aux/nm-random-utils.h" -#include "nm-glib-aux/nm-io-utils.h" -#include "nm-glib-aux/nm-secret-utils.h" -#include "nm-glib-aux/nm-time-utils.h" -#include "nm-utils.h" -#include "nm-core-internal.h" -#include "nm-setting-connection.h" -#include "nm-setting-ip4-config.h" -#include "nm-setting-ip6-config.h" -#include "nm-setting-wireless.h" -#include "nm-setting-wireless-security.h" - -#ifdef __NM_SD_UTILS_H__ - #error \ - "nm-core-utils.c should stay independent of systemd utils. Are you looking for NetworkMangerUtils.c? " -#endif - -G_STATIC_ASSERT(sizeof(NMUtilsTestFlags) <= sizeof(int)); - -/* we read _nm_utils_testing without memory barrier. This is thread-safe, - * because the static variable is initialized to zero, and only reset - * once to a non-zero value (via g_atomic_int_compare_and_exchange()). - * - * Since there is only one integer that contains the data, there is no - * caching problem reading this (atomic int) variable without - * synchronization/memory-barrier. Contrary to a double-checked locking, - * where one needs a memory barrier to read the variable and ensure - * that also the related data is coherent in cache. Here there is no - * related data. */ -static int _nm_utils_testing = 0; - -gboolean -nm_utils_get_testing_initialized() -{ - NMUtilsTestFlags flags; - - flags = (NMUtilsTestFlags) _nm_utils_testing; - if (flags == NM_UTILS_TEST_NONE) - flags = (NMUtilsTestFlags) g_atomic_int_get(&_nm_utils_testing); - return flags != NM_UTILS_TEST_NONE; -} - -NMUtilsTestFlags -nm_utils_get_testing() -{ - NMUtilsTestFlags flags; - -again: - flags = (NMUtilsTestFlags) _nm_utils_testing; - if (flags != NM_UTILS_TEST_NONE) { - /* Flags already initialized. Return them. */ - return flags & NM_UTILS_TEST_ALL; - } - - /* Accessing nm_utils_get_testing() causes us to set the flags to initialized. - * Detecting running tests also based on g_test_initialized(). */ - flags = _NM_UTILS_TEST_INITIALIZED; - if (g_test_initialized()) - flags |= _NM_UTILS_TEST_GENERAL; - - g_atomic_int_compare_and_exchange(&_nm_utils_testing, 0, (int) flags); - - /* regardless of whether we won the race of initializing _nm_utils_testing, - * go back and read the value again. It must be non-zero by now. */ - goto again; -} - -void -_nm_utils_set_testing(NMUtilsTestFlags flags) -{ - g_assert(!NM_FLAGS_ANY(flags, ~NM_UTILS_TEST_ALL)); - - /* mask out everything except ALL, and always set GENERAL. */ - flags = (flags & NM_UTILS_TEST_ALL) | (_NM_UTILS_TEST_GENERAL | _NM_UTILS_TEST_INITIALIZED); - - if (!g_atomic_int_compare_and_exchange(&_nm_utils_testing, 0, (int) flags)) { - /* We only allow setting _nm_utils_set_testing() once, before fetching the - * value with nm_utils_get_testing(). */ - g_return_if_reached(); - } -} - -/*****************************************************************************/ - -static GSList * _singletons = NULL; -static gboolean _singletons_shutdown = FALSE; - -static void -_nm_singleton_instance_weak_cb(gpointer data, GObject *where_the_object_was) -{ - nm_assert(g_slist_find(_singletons, where_the_object_was)); - - _singletons = g_slist_remove(_singletons, where_the_object_was); -} - -static void __attribute__((destructor)) _nm_singleton_instance_destroy(void) -{ - _singletons_shutdown = TRUE; - - while (_singletons) { - GObject *instance = _singletons->data; - - _singletons = g_slist_delete_link(_singletons, _singletons); - - g_object_weak_unref(instance, _nm_singleton_instance_weak_cb, NULL); - - if (instance->ref_count > 1) { - nm_log_dbg(LOGD_CORE, - "disown %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", - G_OBJECT_TYPE_NAME(instance), - NM_HASH_OBFUSCATE_PTR(instance)); - } - - g_object_unref(instance); - } -} - -void -_nm_singleton_instance_register_destruction(GObject *instance) -{ - g_return_if_fail(G_IS_OBJECT(instance)); - - /* Don't allow registration after shutdown. We only destroy the singletons - * once. */ - g_return_if_fail(!_singletons_shutdown); - - g_object_weak_ref(instance, _nm_singleton_instance_weak_cb, NULL); - - _singletons = g_slist_prepend(_singletons, instance); -} - -/*****************************************************************************/ - -static double -_exp10(guint16 ex) -{ - double v; - - if (ex == 0) - return 1.0; - - v = _exp10(ex / 2); - v = v * v; - if (ex % 2) - v *= 10; - return v; -} - -/* - * nm_utils_exp10: - * @ex: the exponent - * - * Returns: 10^ex, or pow(10, ex), or exp10(ex). - */ -double -nm_utils_exp10(gint16 ex) -{ - if (ex >= 0) - return _exp10(ex); - return 1.0 / _exp10(-((gint32) ex)); -} - -/*****************************************************************************/ - -/* - * nm_ethernet_address_is_valid: - * @addr: pointer to a binary or ASCII Ethernet address - * @len: length of @addr, or -1 if @addr is ASCII - * - * Compares an Ethernet address against known invalid addresses. - - * Returns: %TRUE if @addr is a valid Ethernet address, %FALSE if it is not. - */ -gboolean -nm_ethernet_address_is_valid(gconstpointer addr, gssize len) -{ - guint8 invalid_addr[4][ETH_ALEN] = { - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, - {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}, /* prism54 dummy MAC */ - }; - guint8 addr_bin[ETH_ALEN]; - guint i; - - if (!addr) { - g_return_val_if_fail(len == -1 || len == ETH_ALEN, FALSE); - return FALSE; - } - - if (len == -1) { - if (!nm_utils_hwaddr_aton(addr, addr_bin, ETH_ALEN)) - return FALSE; - addr = addr_bin; - } else if (len != ETH_ALEN) - g_return_val_if_reached(FALSE); - - /* Check for multicast address */ - if ((((guint8 *) addr)[0]) & 0x01) - return FALSE; - - for (i = 0; i < G_N_ELEMENTS(invalid_addr); i++) { - if (nm_utils_hwaddr_matches(addr, ETH_ALEN, invalid_addr[i], ETH_ALEN)) - return FALSE; - } - - return TRUE; -} - -/*****************************************************************************/ - -void -nm_utils_array_remove_at_indexes(GArray *array, const guint *indexes_to_delete, gsize len) -{ - gsize elt_size; - guint index_to_delete; - guint i_src; - guint mm_src, mm_dst, mm_len; - gsize i_itd; - guint res_length; - - g_return_if_fail(array); - if (!len) - return; - g_return_if_fail(indexes_to_delete); - - elt_size = g_array_get_element_size(array); - - i_itd = 0; - index_to_delete = indexes_to_delete[0]; - if (index_to_delete >= array->len) - g_return_if_reached(); - - res_length = array->len - 1; - - mm_dst = index_to_delete; - mm_src = index_to_delete; - mm_len = 0; - - for (i_src = index_to_delete; i_src < array->len; i_src++) { - if (i_src < index_to_delete) - mm_len++; - else { - /* we require indexes_to_delete to contain non-repeated, ascending - * indexes. Otherwise, we would need to presort the indexes. */ - while (TRUE) { - guint dd; - - if (i_itd + 1 >= len) { - index_to_delete = G_MAXUINT; - break; - } - - dd = indexes_to_delete[++i_itd]; - if (dd > index_to_delete) { - if (dd >= array->len) - g_warn_if_reached(); - else { - g_assert(res_length > 0); - res_length--; - } - index_to_delete = dd; - break; - } - g_warn_if_reached(); - } - - if (mm_len) { - memmove(&array->data[mm_dst * elt_size], - &array->data[mm_src * elt_size], - mm_len * elt_size); - mm_dst += mm_len; - mm_src += mm_len + 1; - mm_len = 0; - } else - mm_src++; - } - } - if (mm_len) { - memmove(&array->data[mm_dst * elt_size], - &array->data[mm_src * elt_size], - mm_len * elt_size); - } - g_array_set_size(array, res_length); -} - -static const char * -_trunk_first_line(char *str) -{ - char *s; - - s = strchr(str, '\n'); - if (s) - s[0] = '\0'; - return str; -} - -int -nm_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *arg1, ...) -{ - gs_unref_ptrarray GPtrArray *argv = NULL; - int exit_status; - gs_free char * _log_str = NULL; -#define ARGV_TO_STR(argv) \ - (_log_str ? _log_str : (_log_str = g_strjoinv(" ", (char **) argv->pdata))) - GError * local = NULL; - va_list ap; - NMLogLevel llevel = suppress_error_logging ? LOGL_DEBUG : LOGL_ERR; - gs_free char *std_out = NULL, *std_err = NULL; - - g_return_val_if_fail(!error || !*error, -1); - g_return_val_if_fail(arg1, -1); - - /* construct the argument list */ - argv = g_ptr_array_sized_new(4); - g_ptr_array_add(argv, "/sbin/modprobe"); - g_ptr_array_add(argv, "--use-blacklist"); - g_ptr_array_add(argv, (char *) arg1); - - va_start(ap, arg1); - while ((arg1 = va_arg(ap, const char *))) - g_ptr_array_add(argv, (char *) arg1); - va_end(ap); - - g_ptr_array_add(argv, NULL); - - nm_log_dbg(LOGD_CORE, "modprobe: '%s'", ARGV_TO_STR(argv)); - if (!g_spawn_sync(NULL, - (char **) argv->pdata, - NULL, - 0, - NULL, - NULL, - &std_out, - &std_err, - &exit_status, - &local)) { - nm_log(llevel, - LOGD_CORE, - NULL, - NULL, - "modprobe: '%s' failed: %s", - ARGV_TO_STR(argv), - local->message); - g_propagate_error(error, local); - return -1; - } else if (exit_status != 0) { - nm_log(llevel, - LOGD_CORE, - NULL, - NULL, - "modprobe: '%s' exited with error %d%s%s%s%s%s%s", - ARGV_TO_STR(argv), - exit_status, - std_out && *std_out ? " (" : "", - std_out && *std_out ? _trunk_first_line(std_out) : "", - std_out && *std_out ? ")" : "", - std_err && *std_err ? " (" : "", - std_err && *std_err ? _trunk_first_line(std_err) : "", - std_err && *std_err ? ")" : ""); - } - - return exit_status; -} - -/*****************************************************************************/ - -typedef struct { - pid_t pid; - NMLogDomain log_domain; - union { - struct { - gint64 wait_start_us; - guint source_timeout_kill_id; - } async; - struct { - gboolean success; - int child_status; - } sync; - }; - NMUtilsKillChildAsyncCb callback; - void * user_data; - - char log_name[1]; /* variable-length object, must be last element!! */ -} KillChildAsyncData; - -#define LOG_NAME_FMT "kill child process '%s' (%ld)" -#define LOG_NAME_PROCESS_FMT "kill process '%s' (%ld)" -#define LOG_NAME_ARGS log_name, (long) pid - -static KillChildAsyncData * -_kc_async_data_alloc(pid_t pid, - NMLogDomain log_domain, - const char * log_name, - NMUtilsKillChildAsyncCb callback, - void * user_data) -{ - KillChildAsyncData *data; - size_t log_name_len; - - /* append the name at the end of our KillChildAsyncData. */ - log_name_len = strlen(LOG_NAME_FMT) + 20 + strlen(log_name); - data = g_malloc(sizeof(KillChildAsyncData) - 1 + log_name_len); - g_snprintf(data->log_name, log_name_len, LOG_NAME_FMT, LOG_NAME_ARGS); - - data->pid = pid; - data->user_data = user_data; - data->callback = callback; - data->log_domain = log_domain; - - return data; -} - -#define KC_EXIT_TO_STRING_BUF_SIZE 128 -static const char * -_kc_exit_to_string(char *buf, int exit) -#define _kc_exit_to_string(buf, exit) \ - (G_STATIC_ASSERT_EXPR(sizeof(buf) == KC_EXIT_TO_STRING_BUF_SIZE && sizeof((buf)[0]) == 1), \ - _kc_exit_to_string(buf, exit)) -{ - if (WIFEXITED(exit)) - g_snprintf(buf, KC_EXIT_TO_STRING_BUF_SIZE, "normally with status %d", WEXITSTATUS(exit)); - else if (WIFSIGNALED(exit)) - g_snprintf(buf, KC_EXIT_TO_STRING_BUF_SIZE, "by signal %d", WTERMSIG(exit)); - else - g_snprintf(buf, KC_EXIT_TO_STRING_BUF_SIZE, "with unexpected status %d", exit); - return buf; -} - -static const char * -_kc_signal_to_string(int sig) -{ - switch (sig) { - case 0: - return "no signal (0)"; - case SIGKILL: - return "SIGKILL (" G_STRINGIFY(SIGKILL) ")"; - case SIGTERM: - return "SIGTERM (" G_STRINGIFY(SIGTERM) ")"; - default: - return "Unexpected signal"; - } -} - -#define KC_WAITED_TO_STRING 100 -static const char * -_kc_waited_to_string(char *buf, gint64 wait_start_us) -#define _kc_waited_to_string(buf, wait_start_us) \ - (G_STATIC_ASSERT_EXPR(sizeof(buf) == KC_WAITED_TO_STRING && sizeof((buf)[0]) == 1), \ - _kc_waited_to_string(buf, wait_start_us)) -{ - g_snprintf(buf, - KC_WAITED_TO_STRING, - " (%ld usec elapsed)", - (long) (nm_utils_get_monotonic_timestamp_usec() - wait_start_us)); - return buf; -} - -static void -_kc_cb_watch_child(GPid pid, int status, gpointer user_data) -{ - KillChildAsyncData *data = user_data; - char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE], buf_wait[KC_WAITED_TO_STRING]; - - if (data->async.source_timeout_kill_id) - g_source_remove(data->async.source_timeout_kill_id); - - nm_log_dbg(data->log_domain, - "%s: terminated %s%s", - data->log_name, - _kc_exit_to_string(buf_exit, status), - _kc_waited_to_string(buf_wait, data->async.wait_start_us)); - - if (data->callback) - data->callback(pid, TRUE, status, data->user_data); - - g_free(data); -} - -static gboolean -_kc_cb_timeout_grace_period(void *user_data) -{ - KillChildAsyncData *data = user_data; - int ret, errsv; - - data->async.source_timeout_kill_id = 0; - - if ((ret = kill(data->pid, SIGKILL)) != 0) { - errsv = errno; - /* ESRCH means, process does not exist or is already a zombie. */ - if (errsv != ESRCH) { - nm_log_err(LOGD_CORE | data->log_domain, - "%s: kill(SIGKILL) returned unexpected return value %d: (%s, %d)", - data->log_name, - ret, - nm_strerror_native(errsv), - errsv); - } - } else { - nm_log_dbg(data->log_domain, - "%s: process not terminated after %ld usec. Sending SIGKILL signal", - data->log_name, - (long) (nm_utils_get_monotonic_timestamp_usec() - data->async.wait_start_us)); - } - - return G_SOURCE_REMOVE; -} - -static gboolean -_kc_invoke_callback_idle(gpointer user_data) -{ - KillChildAsyncData *data = user_data; - - if (data->sync.success) { - char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; - - nm_log_dbg(data->log_domain, - "%s: invoke callback: terminated %s", - data->log_name, - _kc_exit_to_string(buf_exit, data->sync.child_status)); - } else - nm_log_dbg(data->log_domain, "%s: invoke callback: killing child failed", data->log_name); - - data->callback(data->pid, data->sync.success, data->sync.child_status, data->user_data); - g_free(data); - - return G_SOURCE_REMOVE; -} - -static void -_kc_invoke_callback(pid_t pid, - NMLogDomain log_domain, - const char * log_name, - NMUtilsKillChildAsyncCb callback, - void * user_data, - gboolean success, - int child_status) -{ - KillChildAsyncData *data; - - if (!callback) - return; - - data = _kc_async_data_alloc(pid, log_domain, log_name, callback, user_data); - data->sync.success = success; - data->sync.child_status = child_status; - - g_idle_add(_kc_invoke_callback_idle, data); -} - -/* nm_utils_kill_child_async: - * @pid: the process id of the process to kill - * @sig: signal to send initially. Set to 0 to send not signal. - * @log_domain: the logging domain used for logging (LOGD_NONE to suppress logging) - * @log_name: for logging, the name of the processes to kill - * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value - * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter is ignored. - * @callback: (allow-none): callback after the child terminated. This function will always - * be invoked asynchronously. - * @user_data: passed on to callback - * - * Uses g_child_watch_add(), so note the glib comment: if you obtain pid from g_spawn_async() or - * g_spawn_async_with_pipes() you will need to pass %G_SPAWN_DO_NOT_REAP_CHILD as flag to the spawn - * function for the child watching to work. - * Also note, that you must g_source_remove() any other child watchers for @pid because glib - * supports only one watcher per child. - **/ -void -nm_utils_kill_child_async(pid_t pid, - int sig, - NMLogDomain log_domain, - const char * log_name, - guint32 wait_before_kill_msec, - NMUtilsKillChildAsyncCb callback, - void * user_data) -{ - int status = 0, errsv; - pid_t ret; - KillChildAsyncData *data; - char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; - - g_return_if_fail(pid > 0); - g_return_if_fail(log_name != NULL); - - /* let's see if the child already terminated... */ - ret = waitpid(pid, &status, WNOHANG); - if (ret > 0) { - nm_log_dbg(log_domain, - LOG_NAME_FMT ": process %ld already terminated %s", - LOG_NAME_ARGS, - (long) ret, - _kc_exit_to_string(buf_exit, status)); - _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, TRUE, status); - return; - } else if (ret != 0) { - errsv = errno; - /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ - if (errsv != ECHILD) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", - LOG_NAME_ARGS, - nm_strerror_native(errsv), - errsv); - _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); - return; - } - } - - /* send the first signal. */ - if (kill(pid, sig) != 0) { - errsv = errno; - /* ESRCH means, process does not exist or is already a zombie. */ - if (errsv != ESRCH) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": unexpected error sending %s: %s (%d)", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - nm_strerror_native(errsv), - errsv); - _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); - return; - } - - /* let's try again with waitpid, probably there was a race... */ - ret = waitpid(pid, &status, 0); - if (ret > 0) { - nm_log_dbg(log_domain, - LOG_NAME_FMT ": process %ld already terminated %s", - LOG_NAME_ARGS, - (long) ret, - _kc_exit_to_string(buf_exit, status)); - _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, TRUE, status); - } else { - errsv = errno; - nm_log_err( - LOGD_CORE | log_domain, - LOG_NAME_FMT - ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s", - LOG_NAME_ARGS, - (long) ret, - nm_strerror_native(errsv), - errsv, - _kc_signal_to_string(sig)); - _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1); - } - return; - } - - data = _kc_async_data_alloc(pid, log_domain, log_name, callback, user_data); - data->async.wait_start_us = nm_utils_get_monotonic_timestamp_usec(); - - if (sig != SIGKILL && wait_before_kill_msec > 0) { - data->async.source_timeout_kill_id = - g_timeout_add(wait_before_kill_msec, _kc_cb_timeout_grace_period, data); - nm_log_dbg(log_domain, - "%s: wait for process to terminate after sending %s (send SIGKILL in %ld " - "milliseconds)...", - data->log_name, - _kc_signal_to_string(sig), - (long) wait_before_kill_msec); - } else { - data->async.source_timeout_kill_id = 0; - nm_log_dbg(log_domain, - "%s: wait for process to terminate after sending %s...", - data->log_name, - _kc_signal_to_string(sig)); - } - - g_child_watch_add(pid, _kc_cb_watch_child, data); -} - -static gulong -_sleep_duration_convert_ms_to_us(guint32 sleep_duration_msec) -{ - if (sleep_duration_msec > 0) { - guint64 x = (gint64) sleep_duration_msec * (guint64) 1000L; - - return x < G_MAXULONG ? (gulong) x : G_MAXULONG; - } - return G_USEC_PER_SEC / 20; -} - -/* nm_utils_kill_child_sync: - * @pid: process id to kill - * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the - * second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds. - * @log_domain: log debug information for this domain. Errors and warnings are logged both - * as %LOGD_CORE and @log_domain. - * @log_name: name of the process to kill for logging. - * @child_status: (out) (allow-none): return the exit status of the child, if no error occurred. - * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value - * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has not effect. - * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate. - * Set to zero, to use the default (meaning 20 wakeups per seconds). - * - * Kill a child process synchronously and wait. The function first checks if the child already terminated - * and if it did, return the exit status. Otherwise, send one @sig signal. @sig will always be - * sent unless the child already exited. If the child does not exit within @wait_before_kill_msec milliseconds, - * the function will send %SIGKILL and waits for the child indefinitely. If @wait_before_kill_msec is zero, no - * %SIGKILL signal will be sent. - * - * In case of error, errno is preserved to contain the last reason of failure. - **/ -gboolean -nm_utils_kill_child_sync(pid_t pid, - int sig, - NMLogDomain log_domain, - const char *log_name, - int * child_status, - guint32 wait_before_kill_msec, - guint32 sleep_duration_msec) -{ - int status = 0, errsv = 0; - pid_t ret; - gboolean success = FALSE; - gboolean was_waiting = FALSE, send_kill = FALSE; - char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE]; - char buf_wait[KC_WAITED_TO_STRING]; - gint64 wait_start_us; - - g_return_val_if_fail(pid > 0, FALSE); - g_return_val_if_fail(log_name != NULL, FALSE); - - /* check if the child process already terminated... */ - ret = waitpid(pid, &status, WNOHANG); - if (ret > 0) { - nm_log_dbg(log_domain, - LOG_NAME_FMT ": process %ld already terminated %s", - LOG_NAME_ARGS, - (long) ret, - _kc_exit_to_string(buf_exit, status)); - success = TRUE; - goto out; - } else if (ret != 0) { - errsv = errno; - /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ - if (errsv != ECHILD) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)", - LOG_NAME_ARGS, - nm_strerror_native(errsv), - errsv); - goto out; - } - } - - /* send first signal @sig */ - if (kill(pid, sig) != 0) { - errsv = errno; - /* ESRCH means, process does not exist or is already a zombie. */ - if (errsv != ESRCH) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": failed to send %s: %s (%d)", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - nm_strerror_native(errsv), - errsv); - } else { - /* let's try again with waitpid, probably there was a race... */ - ret = waitpid(pid, &status, 0); - if (ret > 0) { - nm_log_dbg(log_domain, - LOG_NAME_FMT ": process %ld already terminated %s", - LOG_NAME_ARGS, - (long) ret, - _kc_exit_to_string(buf_exit, status)); - success = TRUE; - } else { - errsv = errno; - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid " - "(%s, %d) after sending %s", - LOG_NAME_ARGS, - (long) ret, - nm_strerror_native(errsv), - errsv, - _kc_signal_to_string(sig)); - } - } - goto out; - } - - wait_start_us = nm_utils_get_monotonic_timestamp_usec(); - - /* wait for the process to terminated... */ - if (sig != SIGKILL) { - gint64 wait_until, now; - gulong sleep_time, sleep_duration_usec; - int loop_count = 0; - - sleep_duration_usec = _sleep_duration_convert_ms_to_us(sleep_duration_msec); - wait_until = wait_before_kill_msec <= 0 - ? 0 - : wait_start_us + (((gint64) wait_before_kill_msec) * 1000L); - - while (TRUE) { - ret = waitpid(pid, &status, WNOHANG); - if (ret > 0) { - nm_log_dbg(log_domain, - LOG_NAME_FMT ": after sending %s, process %ld exited %s%s", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - (long) ret, - _kc_exit_to_string(buf_exit, status), - was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); - success = TRUE; - goto out; - } - if (ret == -1) { - errsv = errno; - /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */ - if (errsv != ECHILD) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": after sending %s, waitpid failed with %s (%d)%s", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - nm_strerror_native(errsv), - errsv, - was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); - goto out; - } - } - - if (!wait_until) - break; - - now = nm_utils_get_monotonic_timestamp_usec(); - if (now >= wait_until) - break; - - if (!was_waiting) { - nm_log_dbg(log_domain, - LOG_NAME_FMT ": waiting up to %ld milliseconds for process to terminate " - "normally after sending %s...", - LOG_NAME_ARGS, - (long) MAX(wait_before_kill_msec, 0), - _kc_signal_to_string(sig)); - was_waiting = TRUE; - } - - sleep_time = MIN(wait_until - now, sleep_duration_usec); - if (loop_count < 20) { - /* At the beginning we expect the process to die fast. - * Limit the sleep time, the limit doubles with every iteration. */ - sleep_time = MIN(sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000); - loop_count++; - } - g_usleep(sleep_time); - } - - /* send SIGKILL, if called with @wait_before_kill_msec > 0 */ - if (wait_until) { - nm_log_dbg(log_domain, LOG_NAME_FMT ": sending SIGKILL...", LOG_NAME_ARGS); - - send_kill = TRUE; - if (kill(pid, SIGKILL) != 0) { - errsv = errno; - /* ESRCH means, process does not exist or is already a zombie. */ - if (errsv != ESRCH) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": failed to send SIGKILL (after sending %s), %s (%d)", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - nm_strerror_native(errsv), - errsv); - goto out; - } - } - } - } - - if (!was_waiting) { - nm_log_dbg(log_domain, - LOG_NAME_FMT ": waiting for process to terminate after sending %s%s...", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - send_kill ? " and SIGKILL" : ""); - } - - /* block until the child terminates. */ - while ((ret = waitpid(pid, &status, 0)) <= 0) { - errsv = errno; - - if (errsv != EINTR) { - nm_log_err(LOGD_CORE | log_domain, - LOG_NAME_FMT ": after sending %s%s, waitpid failed with %s (%d)%s", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - send_kill ? " and SIGKILL" : "", - nm_strerror_native(errsv), - errsv, - _kc_waited_to_string(buf_wait, wait_start_us)); - goto out; - } - } - - nm_log_dbg(log_domain, - LOG_NAME_FMT ": after sending %s%s, process %ld exited %s%s", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - send_kill ? " and SIGKILL" : "", - (long) ret, - _kc_exit_to_string(buf_exit, status), - _kc_waited_to_string(buf_wait, wait_start_us)); - success = TRUE; -out: - if (child_status) - *child_status = success ? status : -1; - errno = success ? 0 : errsv; - return success; -} - -/* nm_utils_kill_process_sync: - * @pid: process id to kill - * @start_time: the start time of the process to kill (as obtained by nm_utils_get_start_time_for_pid()). - * This is an optional argument, to avoid (somewhat) killing the wrong process as @pid - * might get recycled. You can pass 0, to not provide this parameter. - * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the - * second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds. - * @log_domain: log debug information for this domain. Errors and warnings are logged both - * as %LOGD_CORE and @log_domain. - * @log_name: name of the process to kill for logging. - * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value - * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has no effect. - * If @max_wait_msec is set but less then @wait_before_kill_msec, the final %SIGKILL will also - * not be send. - * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate. - * Set to zero, to use the default (meaning 20 wakeups per seconds). - * @max_wait_msec: if 0, waits indefinitely until the process is gone (or a zombie). Otherwise, this - * is the maximum wait time until returning. If @max_wait_msec is non-zero but smaller then @wait_before_kill_msec, - * we will not send a final %SIGKILL. - * - * Kill a non-child process synchronously and wait. This function will not return before the - * process with PID @pid is gone, the process is a zombie, or @max_wait_msec expires. - **/ -void -nm_utils_kill_process_sync(pid_t pid, - guint64 start_time, - int sig, - NMLogDomain log_domain, - const char *log_name, - guint32 wait_before_kill_msec, - guint32 sleep_duration_msec, - guint32 max_wait_msec) -{ - int errsv; - guint64 start_time0; - gint64 wait_until_sigkill, now, wait_start_us, max_wait_until; - gulong sleep_time, sleep_duration_usec; - int loop_count = 0; - gboolean was_waiting = FALSE; - char buf_wait[KC_WAITED_TO_STRING]; - char p_state; - - g_return_if_fail(pid > 0); - g_return_if_fail(log_name != NULL); - - start_time0 = nm_utils_get_start_time_for_pid(pid, &p_state, NULL); - if (start_time0 == 0) { - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT ": cannot kill process %ld because it seems already gone", - LOG_NAME_ARGS, - (long int) pid); - return; - } - if (start_time != 0 && start_time != start_time0) { - nm_log_dbg( - log_domain, - LOG_NAME_PROCESS_FMT - ": don't kill process %ld because the start_time is unexpectedly %lu instead of %ld", - LOG_NAME_ARGS, - (long int) pid, - (unsigned long) start_time0, - (unsigned long) start_time); - return; - } - - switch (p_state) { - case 'Z': - case 'x': - case 'X': - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT - ": cannot kill process %ld because it is already a zombie (%c)", - LOG_NAME_ARGS, - (long int) pid, - p_state); - return; - default: - break; - } - - if (kill(pid, sig) != 0) { - errsv = errno; - /* ESRCH means, process does not exist or is already a zombie. */ - if (errsv == ESRCH) { - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT ": failed to send %s because process seems gone", - LOG_NAME_ARGS, - _kc_signal_to_string(sig)); - } else { - nm_log_warn(LOGD_CORE | log_domain, - LOG_NAME_PROCESS_FMT ": failed to send %s: %s (%d)", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - nm_strerror_native(errsv), - errsv); - } - return; - } - - /* wait for the process to terminate... */ - - wait_start_us = nm_utils_get_monotonic_timestamp_usec(); - - sleep_duration_usec = _sleep_duration_convert_ms_to_us(sleep_duration_msec); - if (sig != SIGKILL && wait_before_kill_msec) - wait_until_sigkill = wait_start_us + (((gint64) wait_before_kill_msec) * 1000L); - else - wait_until_sigkill = 0; - if (max_wait_msec > 0) { - max_wait_until = wait_start_us + (((gint64) max_wait_msec) * 1000L); - if (wait_until_sigkill > 0 && wait_until_sigkill > max_wait_msec) - wait_until_sigkill = 0; - } else - max_wait_until = 0; - - while (TRUE) { - start_time = nm_utils_get_start_time_for_pid(pid, &p_state, NULL); - - if (start_time != start_time0) { - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT ": process is gone after sending signal %s%s", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); - return; - } - switch (p_state) { - case 'Z': - case 'x': - case 'X': - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT ": process is a zombie (%c) after sending signal %s%s", - LOG_NAME_ARGS, - p_state, - _kc_signal_to_string(sig), - was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); - return; - default: - break; - } - - if (kill(pid, 0) != 0) { - errsv = errno; - /* ESRCH means, process does not exist or is already a zombie. */ - if (errsv == ESRCH) { - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT - ": process is gone or a zombie after sending signal %s%s", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); - } else { - nm_log_warn(LOGD_CORE | log_domain, - LOG_NAME_PROCESS_FMT ": failed to kill(%ld, 0): %s (%d)%s", - LOG_NAME_ARGS, - (long int) pid, - nm_strerror_native(errsv), - errsv, - was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); - } - return; - } - - sleep_time = sleep_duration_usec; - now = nm_utils_get_monotonic_timestamp_usec(); - - if (max_wait_until != 0 && now >= max_wait_until) { - if (wait_until_sigkill != 0) { - /* wait_before_kill_msec is not larger then max_wait_until but we did not yet send - * SIGKILL. Although we already reached our timeout, we don't want to skip sending - * the signal. Even if we don't wait for the process to disappear. */ - nm_log_dbg(log_domain, LOG_NAME_PROCESS_FMT ": sending SIGKILL", LOG_NAME_ARGS); - kill(pid, SIGKILL); - } - nm_log_warn(log_domain, - LOG_NAME_PROCESS_FMT - ": timeout %u msec waiting for process to disappear (after sending %s)%s", - LOG_NAME_ARGS, - (unsigned) max_wait_until, - _kc_signal_to_string(sig), - was_waiting ? _kc_waited_to_string(buf_wait, wait_start_us) : ""); - return; - } - - if (wait_until_sigkill != 0) { - if (now >= wait_until_sigkill) { - /* Still not dead. SIGKILL now... */ - nm_log_dbg(log_domain, LOG_NAME_PROCESS_FMT ": sending SIGKILL", LOG_NAME_ARGS); - if (kill(pid, SIGKILL) != 0) { - errsv = errno; - /* ESRCH means, process does not exist or is already a zombie. */ - if (errsv != ESRCH) { - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT ": process is gone or a zombie%s", - LOG_NAME_ARGS, - _kc_waited_to_string(buf_wait, wait_start_us)); - } else { - nm_log_warn(LOGD_CORE | log_domain, - LOG_NAME_PROCESS_FMT - ": failed to send SIGKILL (after sending %s), %s (%d)%s", - LOG_NAME_ARGS, - _kc_signal_to_string(sig), - nm_strerror_native(errsv), - errsv, - _kc_waited_to_string(buf_wait, wait_start_us)); - } - return; - } - sig = SIGKILL; - wait_until_sigkill = 0; - loop_count = - 0; /* reset the loop_count. Now we really expect the process to die quickly. */ - } else - sleep_time = MIN(wait_until_sigkill - now, sleep_duration_usec); - } - - if (!was_waiting) { - if (wait_until_sigkill != 0) { - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT - ": waiting up to %ld milliseconds for process to disappear before " - "sending KILL signal after sending %s...", - LOG_NAME_ARGS, - (long) wait_before_kill_msec, - _kc_signal_to_string(sig)); - } else if (max_wait_until != 0) { - nm_log_dbg( - log_domain, - LOG_NAME_PROCESS_FMT - ": waiting up to %ld milliseconds for process to disappear after sending %s...", - LOG_NAME_ARGS, - (long) max_wait_msec, - _kc_signal_to_string(sig)); - } else { - nm_log_dbg(log_domain, - LOG_NAME_PROCESS_FMT - ": waiting for process to disappear after sending %s...", - LOG_NAME_ARGS, - _kc_signal_to_string(sig)); - } - was_waiting = TRUE; - } - - if (loop_count < 20) { - /* At the beginning we expect the process to die fast. - * Limit the sleep time, the limit doubles with every iteration. */ - sleep_time = MIN(sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000); - loop_count++; - } - g_usleep(sleep_time); - } -} -#undef LOG_NAME_FMT -#undef LOG_NAME_PROCESS_FMT -#undef LOG_NAME_ARGS - -const char *const NM_PATHS_DEFAULT[] = { - PREFIX "/sbin/", - PREFIX "/bin/", - "/usr/local/sbin/", - "/sbin/", - "/usr/sbin/", - "/usr/local/bin/", - "/bin/", - "/usr/bin/", - NULL, -}; - -const char * -nm_utils_find_helper(const char *progname, const char *try_first, GError **error) -{ - return nm_utils_file_search_in_paths(progname, - try_first, - NM_PATHS_DEFAULT, - G_FILE_TEST_IS_EXECUTABLE, - NULL, - NULL, - error); -} - -/*****************************************************************************/ - -/** - * nm_utils_read_link_absolute: - * @link_file: file name of the symbolic link - * @error: error reason in case of failure - * - * Uses to g_file_read_link()/readlink() to read the symlink - * and returns the result as absolute path. - **/ -char * -nm_utils_read_link_absolute(const char *link_file, GError **error) -{ - char *ln, *dirname, *ln_abs; - - ln = g_file_read_link(link_file, error); - if (!ln) - return NULL; - if (g_path_is_absolute(ln)) - return ln; - - dirname = g_path_get_dirname(link_file); - if (!g_path_is_absolute(dirname)) { - gs_free char *current_dir = g_get_current_dir(); - - /* @link_file argument was not an absolute path in the first place. - * That actually may be a bug, because the CWD is not well defined - * in most cases. Anyway, apparently we were able to load the file - * even from a relative path. So, when making the link absolute, we - * also need to prepend the CWD. */ - ln_abs = g_build_filename(current_dir, dirname, ln, NULL); - } else - ln_abs = g_build_filename(dirname, ln, NULL); - g_free(dirname); - g_free(ln); - return ln_abs; -} - -/*****************************************************************************/ - -#define DEVICE_TYPE_TAG "type:" -#define DRIVER_TAG "driver:" -#define DHCP_PLUGIN_TAG "dhcp-plugin:" -#define EXCEPT_TAG "except:" -#define MATCH_TAG_CONFIG_NM_VERSION "nm-version:" -#define MATCH_TAG_CONFIG_NM_VERSION_MIN "nm-version-min:" -#define MATCH_TAG_CONFIG_NM_VERSION_MAX "nm-version-max:" -#define MATCH_TAG_CONFIG_ENV "env:" - -typedef struct { - const char *interface_name; - const char *device_type; - const char *driver; - const char *driver_version; - const char *dhcp_plugin; - struct { - const char *value; - gboolean is_parsed; - guint len; - guint8 bin[NM_UTILS_HWADDR_LEN_MAX]; - } hwaddr; - struct { - const char *value; - gboolean is_parsed; - guint32 a; - guint32 b; - guint32 c; - } s390_subchannels; -} MatchDeviceData; - -static gboolean -match_device_s390_subchannels_parse(const char *s390_subchannels, - guint32 * out_a, - guint32 * out_b, - guint32 * out_c) -{ - char buf[30 + 1]; - const int BUFSIZE = G_N_ELEMENTS(buf) - 1; - guint i = 0; - char * pa = NULL, *pb = NULL, *pc = NULL; - gint64 a, b, c; - - nm_assert(s390_subchannels); - nm_assert(out_a); - nm_assert(out_b); - nm_assert(out_c); - - if (!g_ascii_isxdigit(s390_subchannels[0])) - return FALSE; - - /* Get the first channel */ - for (i = 0; s390_subchannels[i]; i++) { - char ch = s390_subchannels[i]; - - if (!g_ascii_isxdigit(ch) && ch != '.') { - if (ch == ',') { - /* FIXME: currently we consider the first channel and ignore - * everything after the first ',' separator. Maybe we should - * validate all present channels? */ - break; - } - return FALSE; /* Invalid chars */ - } - if (i >= BUFSIZE) - return FALSE; /* Too long to be a subchannel */ - buf[i] = ch; - } - buf[i] = '\0'; - - /* and grab each of its elements, there should be 3 */ - pa = &buf[0]; - pb = strchr(pa, '.'); - if (pb) - pc = strchr(pb + 1, '.'); - if (!pb || !pc) - return FALSE; - *pb++ = '\0'; - *pc++ = '\0'; - - a = _nm_utils_ascii_str_to_int64(pa, 16, 0, G_MAXUINT32, -1); - if (a == -1) - return FALSE; - b = _nm_utils_ascii_str_to_int64(pb, 16, 0, G_MAXUINT32, -1); - if (b == -1) - return FALSE; - c = _nm_utils_ascii_str_to_int64(pc, 16, 0, G_MAXUINT32, -1); - if (c == -1) - return FALSE; - - *out_a = (guint32) a; - *out_b = (guint32) b; - *out_c = (guint32) c; - return TRUE; -} - -static gboolean -match_data_s390_subchannels_eval(const char *spec_str, MatchDeviceData *match_data) -{ - guint32 a, b, c; - - if (G_UNLIKELY(!match_data->s390_subchannels.is_parsed)) { - match_data->s390_subchannels.is_parsed = TRUE; - - if (!match_data->s390_subchannels.value - || !match_device_s390_subchannels_parse(match_data->s390_subchannels.value, - &match_data->s390_subchannels.a, - &match_data->s390_subchannels.b, - &match_data->s390_subchannels.c)) { - match_data->s390_subchannels.value = NULL; - return FALSE; - } - } else if (!match_data->s390_subchannels.value) - return FALSE; - - if (!match_device_s390_subchannels_parse(spec_str, &a, &b, &c)) - return FALSE; - return match_data->s390_subchannels.a == a && match_data->s390_subchannels.b == b - && match_data->s390_subchannels.c == c; -} - -static gboolean -match_device_hwaddr_eval(const char *spec_str, MatchDeviceData *match_data) -{ - if (G_UNLIKELY(!match_data->hwaddr.is_parsed)) { - match_data->hwaddr.is_parsed = TRUE; - - if (match_data->hwaddr.value) { - gsize l; - - if (!_nm_utils_hwaddr_aton(match_data->hwaddr.value, - match_data->hwaddr.bin, - sizeof(match_data->hwaddr.bin), - &l)) - g_return_val_if_reached(FALSE); - match_data->hwaddr.len = l; - } else - return FALSE; - } else if (!match_data->hwaddr.len) - return FALSE; - - return nm_utils_hwaddr_matches(spec_str, -1, match_data->hwaddr.bin, match_data->hwaddr.len); -} - -#define _MATCH_CHECK(spec_str, tag) \ - ({ \ - gboolean _has = FALSE; \ - \ - if (!g_ascii_strncasecmp(spec_str, ("" tag ""), NM_STRLEN(tag))) { \ - spec_str += NM_STRLEN(tag); \ - _has = TRUE; \ - } \ - _has; \ - }) - -static NMMatchSpecMatchType -_match_result(gboolean has_except, - gboolean has_not_except, - gboolean has_match, - gboolean has_match_except) -{ - if (has_except && !has_not_except) { - /* a match spec that only consists of a list of except matches is treated specially. */ - nm_assert(!has_match); - if (has_match_except) { - /* one of the "except:" matches matched. The result is an explicit - * negative match. */ - return NM_MATCH_SPEC_NEG_MATCH; - } else { - /* none of the "except:" matches matched. The result is a positive match, - * despite there being no positive match. */ - return NM_MATCH_SPEC_MATCH; - } - } - - if (has_match_except) - return NM_MATCH_SPEC_NEG_MATCH; - if (has_match) - return NM_MATCH_SPEC_MATCH; - return NM_MATCH_SPEC_NO_MATCH; -} - -static const char * -match_except(const char *spec_str, gboolean *out_except) -{ - if (_MATCH_CHECK(spec_str, EXCEPT_TAG)) - *out_except = TRUE; - else - *out_except = FALSE; - return spec_str; -} - -static gboolean -match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *match_data) -{ - if (spec_str[0] == '*' && spec_str[1] == '\0') - return TRUE; - - if (_MATCH_CHECK(spec_str, DEVICE_TYPE_TAG)) { - return match_data->device_type && nm_streq(spec_str, match_data->device_type); - } - - if (_MATCH_CHECK(spec_str, NM_MATCH_SPEC_MAC_TAG)) - return match_device_hwaddr_eval(spec_str, match_data); - - if (_MATCH_CHECK(spec_str, NM_MATCH_SPEC_INTERFACE_NAME_TAG)) { - gboolean use_pattern = FALSE; - - if (spec_str[0] == '=') - spec_str += 1; - else { - if (spec_str[0] == '~') - spec_str += 1; - use_pattern = TRUE; - } - - if (match_data->interface_name) { - if (nm_streq(spec_str, match_data->interface_name)) - return TRUE; - if (use_pattern && g_pattern_match_simple(spec_str, match_data->interface_name)) - return TRUE; - } - return FALSE; - } - - if (_MATCH_CHECK(spec_str, DRIVER_TAG)) { - const char *t; - - if (!match_data->driver) - return FALSE; - - /* support: - * 1) "${DRIVER}" - * In this case, DRIVER may not contain a '/' character. - * It matches any driver version. - * 2) "${DRIVER}/${DRIVER_VERSION}" - * In this case, DRIVER may contains '/' but DRIVER_VERSION - * may not. A '/' in DRIVER_VERSION may be replaced by '?'. - * - * It follows, that "${DRIVER}/""*" is like 1), but allows - * '/' inside DRIVER. - * - * The fields match to what `nmcli -f GENERAL.DRIVER,GENERAL.DRIVER-VERSION device show` - * gives. However, DRIVER matches literally, while DRIVER_VERSION is a glob - * supporting ? and *. - */ - - t = strrchr(spec_str, '/'); - - if (!t) - return nm_streq(spec_str, match_data->driver); - - return (strncmp(spec_str, match_data->driver, t - spec_str) == 0) - && g_pattern_match_simple(&t[1], match_data->driver_version ?: ""); - } - - if (_MATCH_CHECK(spec_str, NM_MATCH_SPEC_S390_SUBCHANNELS_TAG)) - return match_data_s390_subchannels_eval(spec_str, match_data); - - if (_MATCH_CHECK(spec_str, DHCP_PLUGIN_TAG)) - return nm_streq0(spec_str, match_data->dhcp_plugin); - - if (allow_fuzzy) { - if (match_device_hwaddr_eval(spec_str, match_data)) - return TRUE; - if (match_data->interface_name && nm_streq(spec_str, match_data->interface_name)) - return TRUE; - } - - return FALSE; -} - -NMMatchSpecMatchType -nm_match_spec_device(const GSList *specs, - const char * interface_name, - const char * device_type, - const char * driver, - const char * driver_version, - const char * hwaddr, - const char * s390_subchannels, - const char * dhcp_plugin) -{ - const GSList * iter; - gboolean has_match = FALSE; - gboolean has_match_except = FALSE; - gboolean has_except = FALSE; - gboolean has_not_except = FALSE; - const char * spec_str; - MatchDeviceData match_data = { - .interface_name = interface_name, - .device_type = nm_str_not_empty(device_type), - .driver = nm_str_not_empty(driver), - .driver_version = nm_str_not_empty(driver_version), - .dhcp_plugin = nm_str_not_empty(dhcp_plugin), - .hwaddr = - { - .value = hwaddr, - }, - .s390_subchannels = - { - .value = s390_subchannels, - }, - }; - - nm_assert(!hwaddr || nm_utils_hwaddr_valid(hwaddr, -1)); - - if (!specs) - return NM_MATCH_SPEC_NO_MATCH; - - for (iter = specs; iter; iter = iter->next) { - gboolean except; - - spec_str = iter->data; - - if (!spec_str || !*spec_str) - continue; - - spec_str = match_except(spec_str, &except); - - if (except) - has_except = TRUE; - else - has_not_except = TRUE; - - if ((except && has_match_except) || (!except && has_match)) { - /* evaluating the match does not give new information. Skip it. */ - continue; - } - - if (!match_device_eval(spec_str, !except, &match_data)) - continue; - - if (except) - has_match_except = TRUE; - else - has_match = TRUE; - } - - return _match_result(has_except, has_not_except, has_match, has_match_except); -} - -static gboolean -match_config_eval(const char *str, const char *tag, guint cur_nm_version) -{ - gs_free char * s_ver = NULL; - gs_strfreev char **s_ver_tokens = NULL; - int v_maj = -1, v_min = -1, v_mic = -1; - guint c_maj = -1, c_min = -1, c_mic = -1; - guint n_tokens; - - s_ver = g_strdup(str); - g_strstrip(s_ver); - - /* Let's be strict with the accepted format here. No funny stuff!! */ - - if (s_ver[strspn(s_ver, ".0123456789")] != '\0') - return FALSE; - - s_ver_tokens = g_strsplit(s_ver, ".", -1); - n_tokens = g_strv_length(s_ver_tokens); - if (n_tokens == 0 || n_tokens > 3) - return FALSE; - - v_maj = _nm_utils_ascii_str_to_int64(s_ver_tokens[0], 10, 0, 0xFFFF, -1); - if (v_maj < 0) - return FALSE; - if (n_tokens >= 2) { - v_min = _nm_utils_ascii_str_to_int64(s_ver_tokens[1], 10, 0, 0xFF, -1); - if (v_min < 0) - return FALSE; - } - if (n_tokens >= 3) { - v_mic = _nm_utils_ascii_str_to_int64(s_ver_tokens[2], 10, 0, 0xFF, -1); - if (v_mic < 0) - return FALSE; - } - - nm_decode_version(cur_nm_version, &c_maj, &c_min, &c_mic); - -#define CHECK_AND_RETURN_FALSE(cur, val, tag, is_last_digit) \ - G_STMT_START \ - { \ - if (!strcmp(tag, MATCH_TAG_CONFIG_NM_VERSION_MIN)) { \ - if (cur < val) \ - return FALSE; \ - } else if (!strcmp(tag, MATCH_TAG_CONFIG_NM_VERSION_MAX)) { \ - if (cur > val) \ - return FALSE; \ - } else { \ - if (cur != val) \ - return FALSE; \ - } \ - if (!(is_last_digit)) { \ - if (cur != val) \ - return FALSE; \ - } \ - } \ - G_STMT_END - if (v_mic >= 0) - CHECK_AND_RETURN_FALSE(c_mic, v_mic, tag, TRUE); - if (v_min >= 0) - CHECK_AND_RETURN_FALSE(c_min, v_min, tag, v_mic < 0); - CHECK_AND_RETURN_FALSE(c_maj, v_maj, tag, v_min < 0); - return TRUE; -} - -NMMatchSpecMatchType -nm_match_spec_config(const GSList *specs, guint cur_nm_version, const char *env) -{ - const GSList *iter; - gboolean has_match = FALSE; - gboolean has_match_except = FALSE; - gboolean has_except = FALSE; - gboolean has_not_except = FALSE; - - if (!specs) - return NM_MATCH_SPEC_NO_MATCH; - - for (iter = specs; iter; iter = g_slist_next(iter)) { - const char *spec_str = iter->data; - gboolean except; - gboolean v_match; - - if (!spec_str || !*spec_str) - continue; - - spec_str = match_except(spec_str, &except); - - if (except) - has_except = TRUE; - else - has_not_except = TRUE; - - if ((except && has_match_except) || (!except && has_match)) { - /* evaluating the match does not give new information. Skip it. */ - continue; - } - - if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_NM_VERSION)) - v_match = match_config_eval(spec_str, MATCH_TAG_CONFIG_NM_VERSION, cur_nm_version); - else if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MIN)) - v_match = match_config_eval(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MIN, cur_nm_version); - else if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MAX)) - v_match = match_config_eval(spec_str, MATCH_TAG_CONFIG_NM_VERSION_MAX, cur_nm_version); - else if (_MATCH_CHECK(spec_str, MATCH_TAG_CONFIG_ENV)) - v_match = env && env[0] && !strcmp(spec_str, env); - else - v_match = FALSE; - - if (!v_match) - continue; - - if (except) - has_match_except = TRUE; - else - has_match = TRUE; - } - - return _match_result(has_except, has_not_except, has_match, has_match_except); -} - -#undef _MATCH_CHECK - -/** - * nm_match_spec_split: - * @value: the string of device specs - * - * Splits the specs from the string and returns them as individual - * entries in a #GSList. - * - * It does not validate any specs, it basically just does a special - * strsplit with ',' or ';' as separators and supporting '\\' as - * escape character. - * - * Leading and trailing spaces of each entry are removed. But the user - * can preserve them by specifying "\\s has 2 leading" or "has 2 trailing \\s". - * - * Specs can have a qualifier like "interface-name:". We still don't strip - * any whitespace after the colon, so "interface-name: X" matches an interface - * named " X". - * - * Returns: (transfer full): the list of device specs. - */ -GSList * -nm_match_spec_split(const char *value) -{ - char * string_value, *p, *q0, *q; - GSList *pieces = NULL; - int trailing_ws; - - if (!value || !*value) - return NULL; - - /* Copied from glibs g_key_file_parse_value_as_string() function - * and adjusted. */ - - string_value = g_new(char, strlen(value) + 1); - - p = (char *) value; - - /* skip over leading whitespace */ - while (g_ascii_isspace(*p)) - p++; - - q0 = q = string_value; - trailing_ws = 0; - while (*p) { - if (*p == '\\') { - p++; - - switch (*p) { - case 's': - *q = ' '; - break; - case 'n': - *q = '\n'; - break; - case 't': - *q = '\t'; - break; - case 'r': - *q = '\r'; - break; - case '\\': - *q = '\\'; - break; - case '\0': - break; - default: - if (NM_IN_SET(*p, ',', ';')) - *q = *p; - else { - *q++ = '\\'; - *q = *p; - } - break; - } - if (*p == '\0') - break; - p++; - trailing_ws = 0; - } else { - *q = *p; - if (*p == '\0') - break; - if (g_ascii_isspace(*p)) { - trailing_ws++; - p++; - } else if (NM_IN_SET(*p, ',', ';')) { - if (q0 < q - trailing_ws) - pieces = g_slist_prepend(pieces, g_strndup(q0, (q - q0) - trailing_ws)); - q0 = q + 1; - p++; - trailing_ws = 0; - while (g_ascii_isspace(*p)) - p++; - } else - p++; - } - q++; - } - - *q = '\0'; - if (q0 < q - trailing_ws) - pieces = g_slist_prepend(pieces, g_strndup(q0, (q - q0) - trailing_ws)); - g_free(string_value); - return g_slist_reverse(pieces); -} - -/** - * nm_match_spec_join: - * @specs: the device specs to join - * - * This is based on g_key_file_parse_string_as_value(), analog to - * nm_match_spec_split() which is based on g_key_file_parse_value_as_string(). - * - * Returns: (transfer full): a joined list of device specs that can be - * split again with nm_match_spec_split(). Note that - * nm_match_spec_split (nm_match_spec_join (specs)) yields the original - * result (which is not true the other way around because there are multiple - * ways to encode the same joined specs string). - */ -char * -nm_match_spec_join(GSList *specs) -{ - const char *p; - GString * str; - - str = g_string_new(""); - - for (; specs; specs = specs->next) { - p = specs->data; - - if (!p || !*p) - continue; - - if (str->len > 0) - g_string_append_c(str, ','); - - /* escape leading whitespace */ - switch (*p) { - case ' ': - g_string_append(str, "\\s"); - p++; - break; - case '\t': - g_string_append(str, "\\t"); - p++; - break; - } - - for (; *p; p++) { - switch (*p) { - case '\n': - g_string_append(str, "\\n"); - break; - case '\r': - g_string_append(str, "\\r"); - break; - case '\\': - g_string_append(str, "\\\\"); - break; - case ',': - g_string_append(str, "\\,"); - break; - case ';': - g_string_append(str, "\\;"); - break; - default: - g_string_append_c(str, *p); - break; - } - } - - /* escape trailing whitespaces */ - switch (str->str[str->len - 1]) { - case ' ': - g_string_overwrite(str, str->len - 1, "\\s"); - break; - case '\t': - g_string_overwrite(str, str->len - 1, "\\t"); - break; - } - } - - return g_string_free(str, FALSE); -} - -static void -_pattern_parse(const char * input, - const char **out_pattern, - gboolean * out_is_inverted, - gboolean * out_is_mandatory) -{ - gboolean is_inverted = FALSE; - gboolean is_mandatory = FALSE; - - if (input[0] == '&') { - input++; - is_mandatory = TRUE; - if (input[0] == '!') { - input++; - is_inverted = TRUE; - } - goto out; - } - - if (input[0] == '|') { - input++; - if (input[0] == '!') { - input++; - is_inverted = TRUE; - } - goto out; - } - - if (input[0] == '!') { - input++; - is_inverted = TRUE; - is_mandatory = TRUE; - goto out; - } - -out: - if (input[0] == '\\') - input++; - - *out_pattern = input; - *out_is_inverted = is_inverted; - *out_is_mandatory = is_mandatory; -} - -gboolean -nm_wildcard_match_check(const char *str, const char *const *patterns, guint num_patterns) -{ - gboolean has_optional = FALSE; - gboolean has_any_optional = FALSE; - guint i; - - for (i = 0; i < num_patterns; i++) { - gboolean is_inverted; - gboolean is_mandatory; - gboolean match; - const char *p; - - _pattern_parse(patterns[i], &p, &is_inverted, &is_mandatory); - - match = (fnmatch(p, str, 0) == 0); - if (is_inverted) - match = !match; - - if (is_mandatory) { - if (!match) - return FALSE; - } else { - has_any_optional = TRUE; - if (match) - has_optional = TRUE; - } - } - - return has_optional || !has_any_optional; -} - -/*****************************************************************************/ - -static gboolean -_kernel_cmdline_match(const char *const *proc_cmdline, const char *pattern) -{ - if (proc_cmdline) { - gboolean has_equal = (!!strchr(pattern, '=')); - gsize pattern_len = strlen(pattern); - - for (; proc_cmdline[0]; proc_cmdline++) { - const char *c = proc_cmdline[0]; - - if (has_equal) { - /* if pattern contains '=' compare full key=value */ - if (nm_streq(c, pattern)) - return TRUE; - continue; - } - - /* otherwise consider pattern as key only */ - if (strncmp(c, pattern, pattern_len) == 0 && NM_IN_SET(c[pattern_len], '\0', '=')) - return TRUE; - } - } - - return FALSE; -} - -gboolean -nm_utils_kernel_cmdline_match_check(const char *const *proc_cmdline, - const char *const *patterns, - guint num_patterns, - GError ** error) -{ - gboolean has_optional = FALSE; - gboolean has_any_optional = FALSE; - guint i; - - for (i = 0; i < num_patterns; i++) { - const char *element = patterns[i]; - gboolean is_inverted = FALSE; - gboolean is_mandatory = FALSE; - gboolean match; - const char *p; - - _pattern_parse(element, &p, &is_inverted, &is_mandatory); - - match = _kernel_cmdline_match(proc_cmdline, p); - if (is_inverted) - match = !match; - - if (is_mandatory) { - if (!match) { - nm_utils_error_set(error, - NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "device does not satisfy match.kernel-command-line property %s", - patterns[i]); - return FALSE; - } - } else { - has_any_optional = TRUE; - if (match) - has_optional = TRUE; - } - } - - if (!has_optional && has_any_optional) { - nm_utils_error_set(error, - NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "device does not satisfy any match.kernel-command-line property"); - return FALSE; - } - - return TRUE; -} - -/*****************************************************************************/ - -char * -nm_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id) -{ - guint id_len; - gsize parent_len; - char *ifname; - - g_return_val_if_fail(parent_iface && *parent_iface, NULL); - - if (vlan_id < 10) - id_len = 2; - else if (vlan_id < 100) - id_len = 3; - else if (vlan_id < 1000) - id_len = 4; - else { - g_return_val_if_fail(vlan_id < 4095, NULL); - id_len = 5; - } - - ifname = g_new(char, IFNAMSIZ); - - parent_len = strlen(parent_iface); - parent_len = MIN(parent_len, IFNAMSIZ - 1 - id_len); - memcpy(ifname, parent_iface, parent_len); - g_snprintf(&ifname[parent_len], IFNAMSIZ - parent_len, ".%u", vlan_id); - - return ifname; -} - -/* nm_utils_new_infiniband_name: - * @name: the output-buffer where the value will be written. Must be - * not %NULL and point to a string buffer of at least IFNAMSIZ bytes. - * @parent_name: the parent interface name - * @p_key: the partition key. - * - * Returns: the infiniband name will be written to @name and @name - * is returned. - */ -const char * -nm_utils_new_infiniband_name(char *name, const char *parent_name, int p_key) -{ - g_return_val_if_fail(name, NULL); - g_return_val_if_fail(parent_name && parent_name[0], NULL); - g_return_val_if_fail(strlen(parent_name) < IFNAMSIZ, NULL); - - /* technically, p_key of 0x0000 and 0x8000 is not allowed either. But we don't - * want to assert against that in nm_utils_new_infiniband_name(). So be more - * resilient here, and accept those. */ - g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, NULL); - - /* If parent+suffix is too long, kernel would just truncate - * the name. We do the same. See ipoib_vlan_add(). */ - g_snprintf(name, IFNAMSIZ, "%s.%04x", parent_name, p_key); - return name; -} - -/*****************************************************************************/ - -/** - * nm_utils_cmp_connection_by_autoconnect_priority: - * @a: - * @b: - * - * compare connections @a and @b for their autoconnect property - * (with sorting the connection that has autoconnect enabled before - * the other) - * If they both have autoconnect enabled, sort them depending on their - * autoconnect-priority (with the higher priority first). - * - * If their autoconnect/autoconnect-priority is the same, 0 is returned. - * That is, they compare equal. - * - * Returns: -1, 0, or 1 - */ -int -nm_utils_cmp_connection_by_autoconnect_priority(NMConnection *a, NMConnection *b) -{ - NMSettingConnection *a_s_con; - NMSettingConnection *b_s_con; - int a_ap, b_ap; - gboolean can_autoconnect; - - if (a == b) - return 0; - if (!a) - return 1; - if (!b) - return -1; - - a_s_con = nm_connection_get_setting_connection(a); - b_s_con = nm_connection_get_setting_connection(b); - - if (!a_s_con) - return !b_s_con ? 0 : 1; - if (!b_s_con) - return -1; - - can_autoconnect = !!nm_setting_connection_get_autoconnect(a_s_con); - if (can_autoconnect != (!!nm_setting_connection_get_autoconnect(b_s_con))) - return can_autoconnect ? -1 : 1; - - if (can_autoconnect) { - a_ap = nm_setting_connection_get_autoconnect_priority(a_s_con); - b_ap = nm_setting_connection_get_autoconnect_priority(b_s_con); - if (a_ap != b_ap) - return (a_ap > b_ap) ? -1 : 1; - } - - return 0; -} - -/*****************************************************************************/ - -typedef struct { - const char *name; - NMSetting * setting; - NMSetting * diff_base_setting; - GHashTable *setting_diff; -} LogConnectionSettingData; - -typedef struct { - const char * item_name; - NMSettingDiffResult diff_result; -} LogConnectionSettingItem; - -static int -_log_connection_sort_hashes_fcn(gconstpointer a, gconstpointer b) -{ - const LogConnectionSettingData *v1 = a; - const LogConnectionSettingData *v2 = b; - NMSettingPriority p1, p2; - NMSetting * s1, *s2; - - s1 = v1->setting ?: v1->diff_base_setting; - s2 = v2->setting ?: v2->diff_base_setting; - - g_assert(s1 && s2); - - p1 = _nm_setting_get_setting_priority(s1); - p2 = _nm_setting_get_setting_priority(s2); - - if (p1 != p2) - return p1 > p2 ? 1 : -1; - - return strcmp(v1->name, v2->name); -} - -static GArray * -_log_connection_sort_hashes(NMConnection *connection, - NMConnection *diff_base, - GHashTable * connection_diff) -{ - GHashTableIter iter; - GArray * sorted_hashes; - LogConnectionSettingData setting_data; - - sorted_hashes = g_array_sized_new(TRUE, - FALSE, - sizeof(LogConnectionSettingData), - g_hash_table_size(connection_diff)); - - g_hash_table_iter_init(&iter, connection_diff); - while (g_hash_table_iter_next(&iter, - (gpointer) &setting_data.name, - (gpointer) &setting_data.setting_diff)) { - setting_data.setting = nm_connection_get_setting_by_name(connection, setting_data.name); - setting_data.diff_base_setting = - diff_base ? nm_connection_get_setting_by_name(diff_base, setting_data.name) : NULL; - g_assert(setting_data.setting || setting_data.diff_base_setting); - g_array_append_val(sorted_hashes, setting_data); - } - - g_array_sort(sorted_hashes, _log_connection_sort_hashes_fcn); - return sorted_hashes; -} - -static int -_log_connection_sort_names_fcn(gconstpointer a, gconstpointer b) -{ - const LogConnectionSettingItem *v1 = a; - const LogConnectionSettingItem *v2 = b; - - /* we want to first show the items, that disappeared, then the one that changed and - * then the ones that were added. */ - - if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) - != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_A)) - return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) ? -1 : 1; - if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) - != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_B)) - return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) ? 1 : -1; - return strcmp(v1->item_name, v2->item_name); -} - -static char * -_log_connection_get_property(NMSetting *setting, const char *name) -{ - GValue val = G_VALUE_INIT; - char * s; - - g_return_val_if_fail(setting, NULL); - - if (!NM_IS_SETTING_VPN(setting) && nm_setting_get_secret_flags(setting, name, NULL, NULL)) - return g_strdup("****"); - - if (!_nm_setting_get_property(setting, name, &val)) - return g_strdup("<unknown>"); - - if (G_VALUE_HOLDS_STRING(&val)) { - const char *val_s; - - val_s = g_value_get_string(&val); - if (!val_s) { - /* for NULL, we want to return the unquoted string "NULL". */ - s = g_strdup("NULL"); - } else { - char *escaped = g_strescape(val_s, "'"); - - s = g_strdup_printf("'%s'", escaped); - g_free(escaped); - } - } else { - s = g_strdup_value_contents(&val); - if (s == NULL) - s = g_strdup("NULL"); - else { - char *escaped = g_strescape(s, "'"); - - g_free(s); - s = escaped; - } - } - g_value_unset(&val); - return s; -} - -static void -_log_connection_sort_names(LogConnectionSettingData *setting_data, GArray *sorted_names) -{ - GHashTableIter iter; - LogConnectionSettingItem item; - gpointer p; - - g_array_set_size(sorted_names, 0); - - g_hash_table_iter_init(&iter, setting_data->setting_diff); - while (g_hash_table_iter_next(&iter, (gpointer) &item.item_name, &p)) { - item.diff_result = GPOINTER_TO_UINT(p); - g_array_append_val(sorted_names, item); - } - - g_array_sort(sorted_names, _log_connection_sort_names_fcn); -} - -void -nm_utils_log_connection_diff(NMConnection *connection, - NMConnection *diff_base, - guint32 level, - guint64 domain, - const char * name, - const char * prefix, - const char * dbus_path) -{ - GHashTable *connection_diff = NULL; - GArray * sorted_hashes; - GArray * sorted_names = NULL; - int i, j; - gboolean connection_diff_are_same; - gboolean print_header = TRUE; - gboolean print_setting_header; - GString * str1; - - g_return_if_fail(NM_IS_CONNECTION(connection)); - g_return_if_fail(!diff_base || (NM_IS_CONNECTION(diff_base) && diff_base != connection)); - - /* For VPN setting types, this is broken, because we cannot (generically) print the content of data/secrets. Bummer... */ - - if (!nm_logging_enabled(level, domain)) - return; - - if (!prefix) - prefix = ""; - if (!name) - name = ""; - - connection_diff_are_same = nm_connection_diff( - connection, - diff_base, - NM_SETTING_COMPARE_FLAG_EXACT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT, - &connection_diff); - if (connection_diff_are_same) { - const char *t1, *t2; - - t1 = nm_connection_get_connection_type(connection); - if (diff_base) { - t2 = nm_connection_get_connection_type(diff_base); - nm_log(level, - domain, - NULL, - NULL, - "%sconnection '%s' (%p/%s/%s%s%s and %p/%s/%s%s%s): no difference", - prefix, - name, - connection, - G_OBJECT_TYPE_NAME(connection), - NM_PRINT_FMT_QUOTE_STRING(t1), - diff_base, - G_OBJECT_TYPE_NAME(diff_base), - NM_PRINT_FMT_QUOTE_STRING(t2)); - } else { - nm_log(level, - domain, - NULL, - NULL, - "%sconnection '%s' (%p/%s/%s%s%s): no properties set", - prefix, - name, - connection, - G_OBJECT_TYPE_NAME(connection), - NM_PRINT_FMT_QUOTE_STRING(t1)); - } - g_assert(!connection_diff); - return; - } - - /* FIXME: it doesn't nicely show the content of NMSettingVpn, because nm_connection_diff() does not - * expand the hash values. */ - - sorted_hashes = _log_connection_sort_hashes(connection, diff_base, connection_diff); - if (sorted_hashes->len <= 0) - goto out; - - sorted_names = g_array_new(FALSE, FALSE, sizeof(LogConnectionSettingItem)); - str1 = g_string_new(NULL); - - for (i = 0; i < sorted_hashes->len; i++) { - LogConnectionSettingData *setting_data = - &g_array_index(sorted_hashes, LogConnectionSettingData, i); - - _log_connection_sort_names(setting_data, sorted_names); - print_setting_header = TRUE; - for (j = 0; j < sorted_names->len; j++) { - char * str_conn, *str_diff; - LogConnectionSettingItem *item = - &g_array_index(sorted_names, LogConnectionSettingItem, j); - - str_conn = (item->diff_result & NM_SETTING_DIFF_RESULT_IN_A) - ? _log_connection_get_property(setting_data->setting, item->item_name) - : NULL; - str_diff = - (item->diff_result & NM_SETTING_DIFF_RESULT_IN_B) - ? _log_connection_get_property(setting_data->diff_base_setting, item->item_name) - : NULL; - - if (print_header) { - GError * err_verify = NULL; - const char *t1, *t2; - - t1 = nm_connection_get_connection_type(connection); - if (diff_base) { - t2 = nm_connection_get_connection_type(diff_base); - nm_log(level, - domain, - NULL, - NULL, - "%sconnection '%s' (%p/%s/%s%s%s < %p/%s/%s%s%s)%s%s%s:", - prefix, - name, - connection, - G_OBJECT_TYPE_NAME(connection), - NM_PRINT_FMT_QUOTE_STRING(t1), - diff_base, - G_OBJECT_TYPE_NAME(diff_base), - NM_PRINT_FMT_QUOTE_STRING(t2), - NM_PRINT_FMT_QUOTED(dbus_path, " [", dbus_path, "]", "")); - } else { - nm_log(level, - domain, - NULL, - NULL, - "%sconnection '%s' (%p/%s/%s%s%s):%s%s%s", - prefix, - name, - connection, - G_OBJECT_TYPE_NAME(connection), - NM_PRINT_FMT_QUOTE_STRING(t1), - NM_PRINT_FMT_QUOTED(dbus_path, " [", dbus_path, "]", "")); - } - print_header = FALSE; - - if (!nm_connection_verify(connection, &err_verify)) { - nm_log(level, - domain, - NULL, - NULL, - "%sconnection %p does not verify: %s", - prefix, - connection, - err_verify->message); - g_clear_error(&err_verify); - } - } -#define _NM_LOG_ALIGN "-25" - if (print_setting_header) { - if (diff_base) { - if (setting_data->setting && setting_data->diff_base_setting) - g_string_printf(str1, - "%p < %p", - setting_data->setting, - setting_data->diff_base_setting); - else if (setting_data->diff_base_setting) - g_string_printf(str1, "*missing* < %p", setting_data->diff_base_setting); - else - g_string_printf(str1, "%p < *missing*", setting_data->setting); - nm_log(level, - domain, - NULL, - NULL, - "%s%"_NM_LOG_ALIGN - "s [ %s ]", - prefix, - setting_data->name, - str1->str); - } else - nm_log(level, - domain, - NULL, - NULL, - "%s%"_NM_LOG_ALIGN - "s [ %p ]", - prefix, - setting_data->name, - setting_data->setting); - print_setting_header = FALSE; - } - g_string_printf(str1, "%s.%s", setting_data->name, item->item_name); - switch (item->diff_result - & (NM_SETTING_DIFF_RESULT_IN_A | NM_SETTING_DIFF_RESULT_IN_B)) { - case NM_SETTING_DIFF_RESULT_IN_B: - nm_log(level, - domain, - NULL, - NULL, - "%s%"_NM_LOG_ALIGN - "s < %s", - prefix, - str1->str, - str_diff ?: "NULL"); - break; - case NM_SETTING_DIFF_RESULT_IN_A: - nm_log(level, - domain, - NULL, - NULL, - "%s%"_NM_LOG_ALIGN - "s = %s", - prefix, - str1->str, - str_conn ?: "NULL"); - break; - default: - nm_log(level, - domain, - NULL, - NULL, - "%s%"_NM_LOG_ALIGN - "s = %s < %s", - prefix, - str1->str, - str_conn ?: "NULL", - str_diff ?: "NULL"); - break; -#undef _NM_LOG_ALIGN - } - g_free(str_conn); - g_free(str_diff); - } - } - - g_array_free(sorted_names, TRUE); - g_string_free(str1, TRUE); -out: - g_hash_table_destroy(connection_diff); - g_array_free(sorted_hashes, TRUE); -} - -#define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/" -#define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/" -G_STATIC_ASSERT(sizeof(IPV4_PROPERTY_DIR) == sizeof(IPV6_PROPERTY_DIR)); -G_STATIC_ASSERT(NM_STRLEN(IPV6_PROPERTY_DIR) + IFNAMSIZ + 60 - == NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE); - -/** - * nm_utils_sysctl_ip_conf_path: - * @addr_family: either AF_INET or AF_INET6. - * @buf: the output buffer where to write the path. It - * must be at least NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE bytes - * long. - * @ifname: an interface name - * @property: a property name - * - * Returns: the path to IPv6 property @property on @ifname. Note that - * this returns the input argument @buf. - */ -const char * -nm_utils_sysctl_ip_conf_path(int addr_family, char *buf, const char *ifname, const char *property) -{ - int len; - - nm_assert(buf); - nm_assert_addr_family(addr_family); - - g_assert(nm_utils_ifname_valid_kernel(ifname, NULL)); - property = NM_ASSERT_VALID_PATH_COMPONENT(property); - - len = g_snprintf(buf, - NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE, - "%s%s/%s", - addr_family == AF_INET6 ? IPV6_PROPERTY_DIR : IPV4_PROPERTY_DIR, - ifname, - property); - g_assert(len < NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE - 1); - return buf; -} - -gboolean -nm_utils_sysctl_ip_conf_is_path(int addr_family, - const char *path, - const char *ifname, - const char *property) -{ - g_return_val_if_fail(path, FALSE); - NM_ASSERT_VALID_PATH_COMPONENT(property); - g_assert(!ifname || nm_utils_ifname_valid_kernel(ifname, NULL)); - - if (addr_family == AF_INET) { - if (!g_str_has_prefix(path, IPV4_PROPERTY_DIR)) - return FALSE; - path += NM_STRLEN(IPV4_PROPERTY_DIR); - } else if (addr_family == AF_INET6) { - if (!g_str_has_prefix(path, IPV6_PROPERTY_DIR)) - return FALSE; - path += NM_STRLEN(IPV6_PROPERTY_DIR); - } else - g_return_val_if_reached(FALSE); - - if (ifname) { - if (!g_str_has_prefix(path, ifname)) - return FALSE; - path += strlen(ifname); - if (path[0] != '/') - return FALSE; - path++; - } else { - const char *slash; - char buf[IFNAMSIZ]; - gsize l; - - slash = strchr(path, '/'); - if (!slash) - return FALSE; - l = slash - path; - if (l >= IFNAMSIZ) - return FALSE; - memcpy(buf, path, l); - buf[l] = '\0'; - if (!nm_utils_ifname_valid_kernel(buf, NULL)) - return FALSE; - path = slash + 1; - } - - if (!nm_streq(path, property)) - return FALSE; - - return TRUE; -} - -gboolean -nm_utils_is_valid_path_component(const char *name) -{ - const char *n; - - if (name == NULL || name[0] == '\0') - return FALSE; - - if (name[0] == '.') { - if (name[1] == '\0') - return FALSE; - if (name[1] == '.' && name[2] == '\0') - return FALSE; - } - n = name; - do { - if (*n == '/') - return FALSE; - } while (*(++n) != '\0'); - - return TRUE; -} - -const char * -NM_ASSERT_VALID_PATH_COMPONENT(const char *name) -{ - if (G_LIKELY(nm_utils_is_valid_path_component(name))) - return name; - - nm_log_err(LOGD_CORE, - "Failed asserting path component: %s%s%s", - NM_PRINT_FMT_QUOTED(name, "\"", name, "\"", "(null)")); - g_error("FATAL: Failed asserting path component: %s%s%s", - NM_PRINT_FMT_QUOTED(name, "\"", name, "\"", "(null)")); - g_assert_not_reached(); -} - -/*****************************************************************************/ - -typedef struct { - NMUuid bin; - char - _nul_sentinel; /* just for safety, if somebody accidentally uses the binary in a string context. */ - - /* depending on whether the string is packed or not (with/without hyphens), - * it's 32 or 36 characters long (plus the trailing NUL). - * - * The difference is that boot-id is a valid RFC 4211 UUID and represented - * as a 36 ascii string (with hyphens). The machine-id technically is not - * a UUID, but just a 32 byte sequence of hexchars. */ - char str[37]; - bool is_fake; -} UuidData; - -static UuidData * -_uuid_data_init(UuidData *uuid_data, gboolean packed, gboolean is_fake, const NMUuid *uuid) -{ - nm_assert(uuid_data); - nm_assert(uuid); - - uuid_data->bin = *uuid; - uuid_data->_nul_sentinel = '\0'; - uuid_data->is_fake = is_fake; - if (packed) { - G_STATIC_ASSERT_EXPR(sizeof(uuid_data->str) >= (sizeof(*uuid) * 2 + 1)); - nm_utils_bin2hexstr_full(uuid, sizeof(*uuid), '\0', FALSE, uuid_data->str); - } else { - G_STATIC_ASSERT_EXPR(sizeof(uuid_data->str) >= 37); - _nm_utils_uuid_unparse(uuid, uuid_data->str); - } - return uuid_data; -} - -/*****************************************************************************/ - -static const UuidData * -_machine_id_get(gboolean allow_fake) -{ - static const UuidData *volatile p_uuid_data; - const UuidData *d; - -again: - d = g_atomic_pointer_get(&p_uuid_data); - if (G_UNLIKELY(!d)) { - static gsize lock; - static UuidData uuid_data; - gs_free char * content = NULL; - gboolean is_fake = TRUE; - const char * fake_type = NULL; - NMUuid uuid; - - /* Get the machine ID from /etc/machine-id; it's always in /etc no matter - * where our configured SYSCONFDIR is. Alternatively, it might be in - * LOCALSTATEDIR /lib/dbus/machine-id. - */ - if (nm_utils_file_get_contents(-1, - "/etc/machine-id", - 100 * 1024, - 0, - &content, - NULL, - NULL, - NULL) - || nm_utils_file_get_contents(-1, - LOCALSTATEDIR "/lib/dbus/machine-id", - 100 * 1024, - 0, - &content, - NULL, - NULL, - NULL)) { - g_strstrip(content); - if (nm_utils_hexstr2bin_full(content, - FALSE, - FALSE, - FALSE, - NULL, - 16, - (guint8 *) &uuid, - sizeof(uuid), - NULL)) { - if (!nm_utils_uuid_is_null(&uuid)) { - /* an all-zero machine-id is not valid. */ - is_fake = FALSE; - } - } - } - - if (is_fake) { - const guint8 *seed_bin; - const char * hash_seed; - gsize seed_len; - - if (!allow_fake) { - /* we don't allow generating (and memorizing) a fake key. - * Signal that no valid machine-id exists. */ - return NULL; - } - - if (nm_utils_host_id_get(&seed_bin, &seed_len)) { - /* We have no valid machine-id but we have a valid secrey_key. - * Generate a fake machine ID by hashing the secret-key. The secret_key - * is commonly persisted, so it should be stable across reboots (despite - * having a broken system without proper machine-id). - * - * Note that we access the host-id here, which is based on secret_key. - * Also not that the secret_key may be generated based on the machine-id, - * so we have to be careful that they don't depend on each other (and - * no infinite recursion happens. This is done correctly, because the secret-key - * will call _machine_id_get(FALSE), so it won't allow accessing a fake - * machine-id, thus avoiding the problem. */ - fake_type = "secret-key"; - hash_seed = "ab085f06-b629-46d1-a553-84eeba5683b6"; - } else { - /* the secret-key is not valid/persistent either. That happens when we fail - * to read/write the secret-key to disk. Fallback to boot-id. The boot-id - * itself may be fake and randomly generated ad-hoc, but that is as best - * as it gets. */ - seed_bin = (const guint8 *) nm_utils_boot_id_bin(); - seed_len = sizeof(NMUuid); - fake_type = "boot-id"; - hash_seed = "7ff0c8f5-5399-4901-ab63-61bf594abe8b"; - } - - /* the fake machine-id is based on secret-key/boot-id, but we hash it - * again, so that they are not literally the same. */ - nm_utils_uuid_generate_from_string_bin(&uuid, - (const char *) seed_bin, - seed_len, - NM_UTILS_UUID_TYPE_VERSION5, - (gpointer) hash_seed); - } - - if (!g_once_init_enter(&lock)) - goto again; - - d = _uuid_data_init(&uuid_data, TRUE, is_fake, &uuid); - g_atomic_pointer_set(&p_uuid_data, d); - g_once_init_leave(&lock, 1); - - if (is_fake) { - nm_log_err(LOGD_CORE, - "/etc/machine-id: no valid machine-id. Use fake one based on %s: %s", - fake_type, - d->str); - } else - nm_log_dbg(LOGD_CORE, "/etc/machine-id: %s", d->str); - } - - return d; -} - -const char * -nm_utils_machine_id_str(void) -{ - return _machine_id_get(TRUE)->str; -} - -const NMUuid * -nm_utils_machine_id_bin(void) -{ - return &_machine_id_get(TRUE)->bin; -} - -gboolean -nm_utils_machine_id_is_fake(void) -{ - return _machine_id_get(TRUE)->is_fake; -} - -/*****************************************************************************/ - -/* prefix for version2 secret key. The secret key is hashed with /etc/machine-id. */ -#define SECRET_KEY_V2_PREFIX "nm-v2:" -#define SECRET_KEY_FILE NMSTATEDIR "/secret_key" - -static gboolean -_host_id_read_timestamp(gboolean use_secret_key_file, - const guint8 *host_id, - gsize host_id_len, - gint64 * out_timestamp_ns) -{ - struct stat st; - gint64 now; - guint64 v; - - if (use_secret_key_file && stat(SECRET_KEY_FILE, &st) == 0) { - /* don't check for overflow or timestamps in the future. We get whatever - * (bogus) date is on the file. */ - *out_timestamp_ns = nm_utils_timespec_to_nsec(&st.st_mtim); - return TRUE; - } - - /* generate a fake timestamp based on the host-id. - * - * This really should never happen under normal circumstances. We already - * are in a code path, where the system has a problem (unable to get good randomness - * and/or can't access the secret_key). In such a scenario, a fake timestamp is the - * least of our problems. - * - * At least, generate something sensible so we don't have to worry about the - * timestamp. It is wrong to worry about using a fake timestamp (which is tied to - * the secret_key) if we are unable to access the secret_key file in the first place. - * - * Pick a random timestamp from the past two years. Yes, this timestamp - * is not stable across restarts, but apparently neither is the host-id - * nor the secret_key itself. */ - -#define EPOCH_TWO_YEARS (G_GINT64_CONSTANT(2 * 365 * 24 * 3600) * NM_UTILS_NSEC_PER_SEC) - - v = nm_hash_siphash42(1156657133u, host_id, host_id_len); - - now = time(NULL); - *out_timestamp_ns = - NM_MAX((gint64) 1, - (now * NM_UTILS_NSEC_PER_SEC) - ((gint64)(v % ((guint64)(EPOCH_TWO_YEARS))))); - return FALSE; -} - -static const guint8 * -_host_id_hash_v2(const guint8 *seed_arr, - gsize seed_len, - guint8 * out_digest /* 32 bytes (NM_UTILS_CHECKSUM_LENGTH_SHA256) */) -{ - nm_auto_free_checksum GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256); - const UuidData * machine_id_data; - char slen[100]; - - /* - (stat -c '%s' /var/lib/NetworkManager/secret_key; - echo -n ' '; - cat /var/lib/NetworkManager/secret_key; - cat /etc/machine-id | tr -d '\n' | sed -n 's/[a-f0-9-]/\0/pg') | sha256sum - */ - - nm_sprintf_buf(slen, "%" G_GSIZE_FORMAT " ", seed_len); - g_checksum_update(sum, (const guchar *) slen, strlen(slen)); - - g_checksum_update(sum, (const guchar *) seed_arr, seed_len); - - machine_id_data = _machine_id_get(FALSE); - if (machine_id_data && !machine_id_data->is_fake) - g_checksum_update(sum, (const guchar *) machine_id_data->str, strlen(machine_id_data->str)); - - nm_utils_checksum_get_digest_len(sum, out_digest, NM_UTILS_CHECKSUM_LENGTH_SHA256); - return out_digest; -} - -static gboolean -_host_id_read(guint8 **out_host_id, gsize *out_host_id_len) -{ -#define SECRET_KEY_LEN 32u - guint8 sha256_digest[NM_UTILS_CHECKSUM_LENGTH_SHA256]; - nm_auto_clear_secret_ptr NMSecretPtr file_content = {0}; - const guint8 * secret_arr; - gsize secret_len; - GError * error = NULL; - gboolean success; - - if (!nm_utils_file_get_contents(-1, - SECRET_KEY_FILE, - 10 * 1024, - NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET, - &file_content.str, - &file_content.len, - NULL, - &error)) { - if (!nm_utils_error_is_notfound(error)) { - nm_log_warn(LOGD_CORE, - "secret-key: failure reading secret key in \"%s\": %s (generate new key)", - SECRET_KEY_FILE, - error->message); - } - g_clear_error(&error); - } else if (file_content.len >= NM_STRLEN(SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN - && memcmp(file_content.bin, SECRET_KEY_V2_PREFIX, NM_STRLEN(SECRET_KEY_V2_PREFIX)) - == 0) { - /* for this type of secret key, we require a prefix followed at least SECRET_KEY_LEN (32) bytes. We - * (also) do that, because older versions of NetworkManager wrote exactly 32 bytes without - * prefix, so we won't wrongly interpret such legacy keys as v2 (if they accidentally have - * a SECRET_KEY_V2_PREFIX prefix, they'll still have the wrong size). - * - * Note that below we generate the random seed in base64 encoding. But that is only done - * to write an ASCII file. There is no base64 decoding and the ASCII is hashed as-is. - * We would accept any binary data just as well (provided a suitable prefix and at least - * 32 bytes). - * - * Note that when hashing the v2 content, we also hash the prefix. There is no strong reason, - * except that it seems simpler not to distinguish between the v2 prefix and the content. - * It's all just part of the seed. */ - - secret_arr = _host_id_hash_v2(file_content.bin, file_content.len, sha256_digest); - secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256; - success = TRUE; - nm_log_dbg(LOGD_CORE, - "secret-key: v2 secret key loaded from \"%s\" (%zu bytes)", - SECRET_KEY_FILE, - file_content.len); - goto out; - } else if (file_content.len >= 16) { - secret_arr = file_content.bin; - secret_len = file_content.len; - success = TRUE; - nm_log_dbg(LOGD_CORE, - "secret-key: v1 secret key loaded from \"%s\" (%zu bytes)", - SECRET_KEY_FILE, - file_content.len); - goto out; - } else { - /* the secret key is borked. Log a warning, but proceed below to generate - * a new one. */ - nm_log_warn(LOGD_CORE, - "secret-key: too short secret key in \"%s\" (generate new key)", - SECRET_KEY_FILE); - } - - /* generate and persist new key */ - { -#define SECRET_KEY_LEN_BASE64 ((((SECRET_KEY_LEN / 3) + 1) * 4) + 4) - guint8 rnd_buf[SECRET_KEY_LEN]; - guint8 new_content[NM_STRLEN(SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN_BASE64]; - int base64_state = 0; - int base64_save = 0; - gsize len; - - success = nm_utils_random_bytes(rnd_buf, sizeof(rnd_buf)); - - /* Our key is really binary data. But since we anyway generate a random seed - * (with 32 random bytes), don't write it in binary, but instead create - * an pure ASCII (base64) representation. Note that the ASCII will still be taken - * as-is (no base64 decoding is done). The sole purpose is to write a ASCII file - * instead of a binary. The content is gibberish either way. */ - memcpy(new_content, SECRET_KEY_V2_PREFIX, NM_STRLEN(SECRET_KEY_V2_PREFIX)); - len = NM_STRLEN(SECRET_KEY_V2_PREFIX); - len += g_base64_encode_step(rnd_buf, - sizeof(rnd_buf), - FALSE, - (char *) &new_content[len], - &base64_state, - &base64_save); - len += - g_base64_encode_close(FALSE, (char *) &new_content[len], &base64_state, &base64_save); - nm_assert(len <= sizeof(new_content)); - - secret_arr = _host_id_hash_v2(new_content, len, sha256_digest); - secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256; - - if (!success) - nm_log_warn(LOGD_CORE, - "secret-key: failure to generate good random data for secret-key (use " - "non-persistent key)"); - else if (nm_utils_get_testing()) { - /* for test code, we don't write the generated secret-key to disk. */ - } else if (!nm_utils_file_set_contents(SECRET_KEY_FILE, - (const char *) new_content, - len, - 0600, - NULL, - &error)) { - nm_log_warn( - LOGD_CORE, - "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)", - SECRET_KEY_FILE, - error->message); - g_clear_error(&error); - success = FALSE; - } else - nm_log_dbg(LOGD_CORE, - "secret-key: persist new v2 secret key to \"%s\" (%zu bytes)", - SECRET_KEY_FILE, - len); - - nm_explicit_bzero(rnd_buf, sizeof(rnd_buf)); - nm_explicit_bzero(new_content, sizeof(new_content)); - } - -out: - *out_host_id_len = secret_len; - *out_host_id = nm_memdup(secret_arr, secret_len); - return success; -} - -typedef struct { - guint8 *host_id; - gsize host_id_len; - gint64 timestamp_ns; - bool is_good : 1; - bool timestamp_is_good : 1; -} HostIdData; - -static const HostIdData * -_host_id_get(void) -{ - static const HostIdData *volatile host_id_static; - const HostIdData *host_id; - -again: - host_id = g_atomic_pointer_get(&host_id_static); - if (G_UNLIKELY(!host_id)) { - static HostIdData host_id_data; - static gsize init_value = 0; - - if (!g_once_init_enter(&init_value)) - goto again; - - host_id_data.is_good = _host_id_read(&host_id_data.host_id, &host_id_data.host_id_len); - - host_id_data.timestamp_is_good = _host_id_read_timestamp(host_id_data.is_good, - host_id_data.host_id, - host_id_data.host_id_len, - &host_id_data.timestamp_ns); - if (!host_id_data.timestamp_is_good && host_id_data.is_good) - nm_log_warn(LOGD_CORE, "secret-key: failure reading host timestamp (use fake one)"); - - host_id = &host_id_data; - g_atomic_pointer_set(&host_id_static, host_id); - g_once_init_leave(&init_value, 1); - } - - return host_id; -} - -/** - * nm_utils_host_id_get: - * @out_host_id: (out) (transfer none): the binary host key - * @out_host_id_len: the length of the host key. - * - * This returns a per-host key that depends on /var/lib/NetworkManage/secret_key - * and (depending on the version) on /etc/machine-id. If /var/lib/NetworkManage/secret_key - * does not exist, it will be generated and persisted for next boot. - * - * Returns: %TRUE, if the host key is "good". Note that this function - * will always succeed to return a host-key, and that this key - * won't change during the run of the program (no matter what). - * A %FALSE return possibly means, that the secret_key is not persisted - * to disk, and/or that it was generated with bad randomness. - */ -gboolean -nm_utils_host_id_get(const guint8 **out_host_id, gsize *out_host_id_len) -{ - const HostIdData *host_id; - - host_id = _host_id_get(); - *out_host_id = host_id->host_id; - *out_host_id_len = host_id->host_id_len; - return host_id->is_good; -} - -gint64 -nm_utils_host_id_get_timestamp_ns(void) -{ - return _host_id_get()->timestamp_ns; -} - -/*****************************************************************************/ - -static const UuidData * -_boot_id_get(void) -{ - static const UuidData *volatile p_boot_id; - const UuidData *d; - -again: - d = g_atomic_pointer_get(&p_boot_id); - if (G_UNLIKELY(!d)) { - static gsize lock; - static UuidData boot_id; - gs_free char * contents = NULL; - NMUuid uuid; - gboolean is_fake = FALSE; - - nm_utils_file_get_contents(-1, - "/proc/sys/kernel/random/boot_id", - 0, - NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, - &contents, - NULL, - NULL, - NULL); - if (!contents || !_nm_utils_uuid_parse(nm_strstrip(contents), &uuid)) { - /* generate a random UUID instead. */ - is_fake = TRUE; - _nm_utils_uuid_generate_random(&uuid); - } - - if (!g_once_init_enter(&lock)) - goto again; - - d = _uuid_data_init(&boot_id, FALSE, is_fake, &uuid); - g_atomic_pointer_set(&p_boot_id, d); - g_once_init_leave(&lock, 1); - } - - return d; -} - -const char * -nm_utils_boot_id_str(void) -{ - return _boot_id_get()->str; -} - -const NMUuid * -nm_utils_boot_id_bin(void) -{ - return &_boot_id_get()->bin; -} - -/*****************************************************************************/ - -const char * -nm_utils_proc_cmdline(void) -{ - static const char *volatile proc_cmdline_cached = NULL; - const char *proc_cmdline; - -again: - proc_cmdline = g_atomic_pointer_get(&proc_cmdline_cached); - if (G_UNLIKELY(!proc_cmdline)) { - gs_free char *str = NULL; - - g_file_get_contents("/proc/cmdline", &str, NULL, NULL); - str = nm_str_realloc(str); - - proc_cmdline = str ?: ""; - if (!g_atomic_pointer_compare_and_exchange(&proc_cmdline_cached, NULL, proc_cmdline)) - goto again; - - g_steal_pointer(&str); - } - - return proc_cmdline; -} - -const char *const * -nm_utils_proc_cmdline_split(void) -{ - static const char *const *volatile proc_cmdline_cached = NULL; - const char *const *proc_cmdline; - -again: - proc_cmdline = g_atomic_pointer_get(&proc_cmdline_cached); - if (G_UNLIKELY(!proc_cmdline)) { - gs_strfreev char **split = NULL; - - split = nm_utils_strsplit_quoted(nm_utils_proc_cmdline()); - if (!g_atomic_pointer_compare_and_exchange(&proc_cmdline_cached, NULL, (gpointer) split)) - goto again; - - proc_cmdline = (const char *const *) g_steal_pointer(&split); - } - - return proc_cmdline; -} - -/*****************************************************************************/ - -/** - * nm_utils_arp_type_detect_from_hwaddrlen: - * @hwaddr_len: the length of the hardware address in bytes. - * - * Detects the arp-type based on the length of the MAC address. - * On success, this returns a (positive) value in uint16_t range, - * like ARPHRD_ETHER or ARPHRD_INFINIBAND. - * - * On failure, returns a negative error code. - * - * Returns: the arp-type or negative value on error. */ -int -nm_utils_arp_type_detect_from_hwaddrlen(gsize hwaddr_len) -{ - switch (hwaddr_len) { - case ETH_ALEN: - return ARPHRD_ETHER; - case INFINIBAND_ALEN: - return ARPHRD_INFINIBAND; - default: - /* Note: if you ever support anything but ethernet and infiniband, - * make sure to look at all callers. They assert that it's one of - * these two. */ - return -EINVAL; - } -} - -gboolean -nm_utils_arp_type_validate_hwaddr(int arp_type, const guint8 *hwaddr, gsize hwaddr_len) -{ - if (!hwaddr) - return FALSE; - - if (arp_type == ARPHRD_ETHER) { - G_STATIC_ASSERT(ARPHRD_ETHER >= 0 && ARPHRD_ETHER <= 0xFF); - if (hwaddr_len != ETH_ALEN) - return FALSE; - } else if (arp_type == ARPHRD_INFINIBAND) { - G_STATIC_ASSERT(ARPHRD_INFINIBAND >= 0 && ARPHRD_INFINIBAND <= 0xFF); - if (hwaddr_len != INFINIBAND_ALEN) - return FALSE; - } else - return FALSE; - - nm_assert(arp_type == nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len)); - return TRUE; -} - -gboolean -nm_utils_arp_type_get_hwaddr_relevant_part(int arp_type, const guint8 **hwaddr, gsize *hwaddr_len) -{ - g_return_val_if_fail(hwaddr && hwaddr_len - && nm_utils_arp_type_validate_hwaddr(arp_type, *hwaddr, *hwaddr_len), - FALSE); - - /* for infiniband, we only consider the last 8 bytes. */ - if (arp_type == ARPHRD_INFINIBAND) { - *hwaddr += (INFINIBAND_ALEN - 8); - *hwaddr_len = 8; - } - - return TRUE; -} - -/*****************************************************************************/ - -/* Returns the "u" (universal/local) bit value for a Modified EUI-64 */ -static gboolean -get_gre_eui64_u_bit(guint32 addr) -{ - static const struct { - guint32 mask; - guint32 result; - } items[] = { - {0xff000000}, {0x7f000000}, /* IPv4 loopback */ - {0xf0000000}, {0xe0000000}, /* IPv4 multicast */ - {0xffffff00}, {0xe0000000}, /* IPv4 local multicast */ - {0xffffffff}, {INADDR_BROADCAST}, /* limited broadcast */ - {0xff000000}, {0x00000000}, /* zero net */ - {0xff000000}, {0x0a000000}, /* private 10 (RFC3330) */ - {0xfff00000}, {0xac100000}, /* private 172 */ - {0xffff0000}, {0xc0a80000}, /* private 192 */ - {0xffff0000}, {0xa9fe0000}, /* IPv4 link-local */ - {0xffffff00}, {0xc0586300}, /* anycast 6-to-4 */ - {0xffffff00}, {0xc0000200}, /* test 192 */ - {0xfffe0000}, {0xc6120000}, /* test 198 */ - }; - guint i; - - for (i = 0; i < G_N_ELEMENTS(items); i++) { - if ((addr & htonl(items[i].mask)) == htonl(items[i].result)) - return 0x00; /* "local" scope */ - } - return 0x02; /* "universal" scope */ -} - -/** - * nm_utils_get_ipv6_interface_identifier: - * @link_type: the hardware link type - * @hwaddr: the hardware address of the interface - * @hwaddr_len: the length (in bytes) of @hwaddr - * @dev_id: the device identifier, if any - * @out_iid: on success, filled with the interface identifier; on failure - * zeroed out - * - * Constructs an interface identifier in "Modified EUI-64" format which is - * suitable for constructing IPv6 addresses. Note that the identifier is - * not obscured in any way (eg, RFC3041). - * - * Returns: %TRUE if the interface identifier could be constructed, %FALSE if - * if could not be constructed. - */ -gboolean -nm_utils_get_ipv6_interface_identifier(NMLinkType link_type, - const guint8 * hwaddr, - guint hwaddr_len, - guint dev_id, - NMUtilsIPv6IfaceId *out_iid) -{ - guint32 addr; - - g_return_val_if_fail(hwaddr != NULL, FALSE); - g_return_val_if_fail(hwaddr_len > 0, FALSE); - g_return_val_if_fail(out_iid != NULL, FALSE); - - out_iid->id = 0; - - switch (link_type) { - case NM_LINK_TYPE_INFINIBAND: - /* Use the port GUID per http://tools.ietf.org/html/rfc4391#section-8, - * making sure to set the 'u' bit to 1. The GUID is the lower 64 bits - * of the IPoIB interface's hardware address. - */ - g_return_val_if_fail(hwaddr_len == INFINIBAND_ALEN, FALSE); - memcpy(out_iid->id_u8, hwaddr + INFINIBAND_ALEN - 8, 8); - out_iid->id_u8[0] |= 0x02; - return TRUE; - case NM_LINK_TYPE_GRE: - /* Hardware address is the network-endian IPv4 address */ - g_return_val_if_fail(hwaddr_len == 4, FALSE); - addr = *(guint32 *) hwaddr; - out_iid->id_u8[0] = get_gre_eui64_u_bit(addr); - out_iid->id_u8[1] = 0x00; - out_iid->id_u8[2] = 0x5E; - out_iid->id_u8[3] = 0xFE; - memcpy(out_iid->id_u8 + 4, &addr, 4); - return TRUE; - case NM_LINK_TYPE_6LOWPAN: - /* The hardware address is already 64-bit. This is the case for - * IEEE 802.15.4 networks. */ - memcpy(out_iid->id_u8, hwaddr, sizeof(out_iid->id_u8)); - return TRUE; - default: - if (hwaddr_len == ETH_ALEN) { - /* Translate 48-bit MAC address to a 64-bit Modified EUI-64. See - * http://tools.ietf.org/html/rfc4291#appendix-A and the Linux - * kernel's net/ipv6/addrconf.c::ipv6_generate_eui64() function. - */ - out_iid->id_u8[0] = hwaddr[0]; - out_iid->id_u8[1] = hwaddr[1]; - out_iid->id_u8[2] = hwaddr[2]; - if (dev_id) { - out_iid->id_u8[3] = (dev_id >> 8) & 0xff; - out_iid->id_u8[4] = dev_id & 0xff; - } else { - out_iid->id_u8[0] ^= 0x02; - out_iid->id_u8[3] = 0xff; - out_iid->id_u8[4] = 0xfe; - } - out_iid->id_u8[5] = hwaddr[3]; - out_iid->id_u8[6] = hwaddr[4]; - out_iid->id_u8[7] = hwaddr[5]; - return TRUE; - } - break; - } - return FALSE; -} - -/*****************************************************************************/ - -/** - * nm_utils_ipv6_addr_set_interface_identifier: - * @addr: output token encoded as %in6_addr - * @iid: %NMUtilsIPv6IfaceId interface identifier - * - * Converts the %NMUtilsIPv6IfaceId to an %in6_addr (suitable for use - * with Linux platform). This only copies the lower 8 bytes, ignoring - * the /64 network prefix which is expected to be all-zero for a valid - * token. - */ -void -nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr *addr, const NMUtilsIPv6IfaceId iid) -{ - memcpy(addr->s6_addr + 8, &iid.id_u8, 8); -} - -/** - * nm_utils_ipv6_interface_identifier_get_from_addr: - * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token - * @addr: token encoded as %in6_addr - * - * Converts the %in6_addr encoded token (as used by Linux platform) to - * the interface identifier. - */ -void -nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId * iid, - const struct in6_addr *addr) -{ - memcpy(iid, addr->s6_addr + 8, 8); -} - -/** - * nm_utils_ipv6_interface_identifier_get_from_token: - * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token - * @token: token encoded as string - * - * Converts the %in6_addr encoded token (as used in ip6 settings) to - * the interface identifier. - * - * Returns: %TRUE if the @token is a valid token, %FALSE otherwise - */ -gboolean -nm_utils_ipv6_interface_identifier_get_from_token(NMUtilsIPv6IfaceId *iid, const char *token) -{ - struct in6_addr i6_token; - - g_return_val_if_fail(token, FALSE); - - if (!inet_pton(AF_INET6, token, &i6_token)) - return FALSE; - - if (!_nm_utils_inet6_is_token(&i6_token)) - return FALSE; - - nm_utils_ipv6_interface_identifier_get_from_addr(iid, &i6_token); - return TRUE; -} - -/** - * nm_utils_inet6_interface_identifier_to_token: - * @iid: %NMUtilsIPv6IfaceId interface identifier - * @buf: the destination buffer of at least %NM_UTILS_INET_ADDRSTRLEN - * bytes. - * - * Converts the interface identifier to a string token. - * - * Returns: the input buffer filled with the id as string. - */ -const char * -nm_utils_inet6_interface_identifier_to_token(NMUtilsIPv6IfaceId iid, - char buf[static INET6_ADDRSTRLEN]) -{ - struct in6_addr i6_token = {.s6_addr = { - 0, - }}; - - nm_assert(buf); - nm_utils_ipv6_addr_set_interface_identifier(&i6_token, iid); - return _nm_utils_inet6_ntop(&i6_token, buf); -} - -/*****************************************************************************/ - -char * -nm_utils_stable_id_random(void) -{ - char buf[15]; - - nm_utils_random_bytes(buf, sizeof(buf)); - return g_base64_encode((guchar *) buf, sizeof(buf)); -} - -char * -nm_utils_stable_id_generated_complete(const char *stable_id_generated) -{ - nm_auto_free_checksum GChecksum *sum = NULL; - guint8 buf[NM_UTILS_CHECKSUM_LENGTH_SHA1]; - char * base64; - - /* for NM_UTILS_STABLE_TYPE_GENERATED we generate a possibly long string - * by doing text-substitutions in nm_utils_stable_id_parse(). - * - * Let's shorten the (possibly) long stable_id to something more compact. */ - - g_return_val_if_fail(stable_id_generated, NULL); - - sum = g_checksum_new(G_CHECKSUM_SHA1); - g_checksum_update(sum, (guchar *) stable_id_generated, strlen(stable_id_generated)); - nm_utils_checksum_get_digest(sum, buf); - - /* we don't care to use the sha1 sum in common hex representation. - * Use instead base64, it's 27 chars (stripping the padding) vs. - * 40. */ - - base64 = g_base64_encode((guchar *) buf, sizeof(buf)); - nm_assert(strlen(base64) == 28); - nm_assert(base64[27] == '='); - - base64[27] = '\0'; - return base64; -} - -static void -_stable_id_append(GString *str, const char *substitution) -{ - if (!substitution) - substitution = ""; - g_string_append_printf(str, "=%zu{%s}", strlen(substitution), substitution); -} - -NMUtilsStableType -nm_utils_stable_id_parse(const char *stable_id, - const char *deviceid, - const char *hwaddr, - const char *bootid, - const char *uuid, - char ** out_generated) -{ - gsize i, idx_start; - GString *str = NULL; - - g_return_val_if_fail(out_generated, NM_UTILS_STABLE_TYPE_RANDOM); - - if (!stable_id) { - *out_generated = NULL; - return NM_UTILS_STABLE_TYPE_UUID; - } - - /* the stable-id allows for some dynamic by performing text-substitutions - * of ${...} patterns. - * - * At first, it looks a bit like bash parameter substitution. - * In contrast however, the process is unambiguous so that the resulting - * effective id differs if: - * - the original, untranslated stable-id differs - * - or any of the subsitutions differs. - * - * The reason for that is, for example if you specify "${CONNECTION}" in the - * stable-id, then the resulting ID should be always(!) unique for this connection. - * There should be no way another connection could specify any stable-id that results - * in the same addresses to be generated (aside hash collisions). - * - * - * For example: say you have a connection with UUID - * "123e4567-e89b-12d3-a456-426655440000" which happens also to be - * the current boot-id. - * Then: - * (1) connection.stable-id = <NULL> - * (2) connection.stable-id = "123e4567-e89b-12d3-a456-426655440000" - * (3) connection.stable-id = "${CONNECTION}" - * (3) connection.stable-id = "${BOOT}" - * will all generate different addresses, although in one way or the - * other, they all mangle the uuid "123e4567-e89b-12d3-a456-426655440000". - * - * For example, with stable-id="${FOO}${BAR}" the substitutions - * - FOO="ab", BAR="c" - * - FOO="a", BAR="bc" - * should give a different effective id. - * - * For example, with FOO="x" and BAR="x", the stable-ids - * - "${FOO}${BAR}" - * - "${BAR}${FOO}" - * should give a different effective id. - */ - - idx_start = 0; - for (i = 0; stable_id[i];) { - if (stable_id[i] != '$') { - i++; - continue; - } - -#define CHECK_PREFIX(prefix) \ - ({ \ - gboolean _match = FALSE; \ - \ - if (g_str_has_prefix(&stable_id[i], "" prefix "")) { \ - _match = TRUE; \ - if (!str) \ - str = g_string_sized_new(256); \ - i += NM_STRLEN(prefix); \ - g_string_append_len(str, &(stable_id)[idx_start], i - idx_start); \ - idx_start = i; \ - } \ - _match; \ - }) - if (CHECK_PREFIX("${CONNECTION}")) - _stable_id_append(str, uuid); - else if (CHECK_PREFIX("${BOOT}")) - _stable_id_append(str, bootid); - else if (CHECK_PREFIX("${DEVICE}")) - _stable_id_append(str, deviceid); - else if (CHECK_PREFIX("${MAC}")) - _stable_id_append(str, hwaddr); - else if (g_str_has_prefix(&stable_id[i], "${RANDOM}")) { - /* RANDOM makes not so much sense for cloned-mac-address - * as the result is similar to specifying "cloned-mac-address=random". - * It makes however sense for RFC 7217 Stable Privacy IPv6 addresses - * where this is effectively the only way to generate a different - * (random) host identifier for each connect. - * - * With RANDOM, the user can switch the lifetime of the - * generated cloned-mac-address and IPv6 host identifier - * by toggling only the stable-id property of the connection. - * With RANDOM being the most short-lived, ~non-stable~ variant. - */ - if (str) - g_string_free(str, TRUE); - *out_generated = NULL; - return NM_UTILS_STABLE_TYPE_RANDOM; - } else { - /* The text following the '$' is not recognized as valid - * substitution pattern. Treat it verbatim. */ - i++; - - /* Note that using unrecognized substitution patterns might - * yield different results with future versions. Avoid that, - * by not using '$' (except for actual substitutions) or escape - * it as "$$" (which is guaranteed to be treated verbatim - * in future). */ - if (stable_id[i] == '$') - i++; - } - } -#undef CHECK_PREFIX - - if (!str) { - *out_generated = NULL; - return NM_UTILS_STABLE_TYPE_STABLE_ID; - } - - if (idx_start < i) - g_string_append_len(str, &stable_id[idx_start], i - idx_start); - *out_generated = g_string_free(str, FALSE); - return NM_UTILS_STABLE_TYPE_GENERATED; -} - -/*****************************************************************************/ - -static gboolean -_is_reserved_ipv6_iid(const guint8 *iid) -{ - /* https://tools.ietf.org/html/rfc5453 */ - /* https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xml */ - - /* 0000:0000:0000:0000 (Subnet-Router Anycast [RFC4291]) */ - if (memcmp(iid, &nm_ip_addr_zero.addr6.s6_addr[8], 8) == 0) - return TRUE; - - /* 0200:5EFF:FE00:0000 - 0200:5EFF:FE00:5212 (Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block [RFC4291]) - * 0200:5EFF:FE00:5213 (Proxy Mobile IPv6 [RFC6543]) - * 0200:5EFF:FE00:5214 - 0200:5EFF:FEFF:FFFF (Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block [RFC4291]) */ - if (memcmp(iid, (const guint8[]){0x02, 0x00, 0x5E, 0xFF, 0xFE}, 5) == 0) - return TRUE; - - /* FDFF:FFFF:FFFF:FF80 - FDFF:FFFF:FFFF:FFFF (Reserved Subnet Anycast Addresses [RFC2526]) */ - if (memcmp(iid, (const guint8[]){0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 7) == 0) { - if (iid[7] & 0x80) - return TRUE; - } - - return FALSE; -} - -static gboolean -_set_stable_privacy(NMUtilsStableType stable_type, - struct in6_addr * addr, - const char * ifname, - const char * network_id, - guint32 dad_counter, - const guint8 * host_id, - gsize host_id_len, - GError ** error) -{ - nm_auto_free_checksum GChecksum *sum = NULL; - guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA256]; - guint32 tmp[2]; - - nm_assert(host_id_len); - nm_assert(network_id); - - sum = g_checksum_new(G_CHECKSUM_SHA256); - - host_id_len = MIN(host_id_len, G_MAXUINT32); - - if (stable_type != NM_UTILS_STABLE_TYPE_UUID) { - guint8 stable_type_uint8; - - nm_assert(stable_type < (NMUtilsStableType) 255); - stable_type_uint8 = (guint8) stable_type; - - /* Preferably, we would always like to include the stable-type, - * but for backward compatibility reasons, we cannot for UUID. - * - * That is no real problem and it is still impossible to - * force a collision here, because of how the remaining - * fields are hashed. That is, as we also hash @host_id_len - * and the terminating '\0' of @network_id, it is unambiguously - * possible to revert the process and deduce the @stable_type. - */ - g_checksum_update(sum, &stable_type_uint8, sizeof(stable_type_uint8)); - } - - g_checksum_update(sum, addr->s6_addr, 8); - g_checksum_update(sum, (const guchar *) ifname, strlen(ifname) + 1); - g_checksum_update(sum, (const guchar *) network_id, strlen(network_id) + 1); - tmp[0] = htonl(dad_counter); - tmp[1] = htonl(host_id_len); - g_checksum_update(sum, (const guchar *) tmp, sizeof(tmp)); - g_checksum_update(sum, (const guchar *) host_id, host_id_len); - nm_utils_checksum_get_digest(sum, digest); - - while (_is_reserved_ipv6_iid(digest)) { - g_checksum_reset(sum); - tmp[0] = htonl(++dad_counter); - g_checksum_update(sum, digest, sizeof(digest)); - g_checksum_update(sum, (const guchar *) &tmp[0], sizeof(tmp[0])); - nm_utils_checksum_get_digest(sum, digest); - } - - memcpy(addr->s6_addr + 8, &digest[0], 8); - return TRUE; -} - -gboolean -nm_utils_ipv6_addr_set_stable_privacy_impl(NMUtilsStableType stable_type, - struct in6_addr * addr, - const char * ifname, - const char * network_id, - guint32 dad_counter, - guint8 * host_id, - gsize host_id_len, - GError ** error) -{ - return _set_stable_privacy(stable_type, - addr, - ifname, - network_id, - dad_counter, - host_id, - host_id_len, - error); -} - -#define RFC7217_IDGEN_RETRIES 3 -/** - * nm_utils_ipv6_addr_set_stable_privacy: - * - * Extend the address prefix with an interface identifier using the - * RFC 7217 Stable Privacy mechanism. - * - * Returns: %TRUE on success, %FALSE if the address could not be generated. - */ -gboolean -nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType stable_type, - struct in6_addr * addr, - const char * ifname, - const char * network_id, - guint32 dad_counter, - GError ** error) -{ - const guint8 *host_id; - gsize host_id_len; - - g_return_val_if_fail(network_id, FALSE); - - if (dad_counter >= RFC7217_IDGEN_RETRIES) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - "Too many DAD collisions"); - return FALSE; - } - - nm_utils_host_id_get(&host_id, &host_id_len); - - return _set_stable_privacy(stable_type, - addr, - ifname, - network_id, - dad_counter, - host_id, - host_id_len, - error); -} - -/*****************************************************************************/ - -static void -_hw_addr_eth_complete(struct ether_addr *addr, - const char * current_mac_address, - const char * generate_mac_address_mask) -{ - struct ether_addr mask; - struct ether_addr oui; - struct ether_addr *ouis; - gsize ouis_len; - guint i; - - /* the second LSB of the first octet means - * "globally unique, OUI enforced, BIA (burned-in-address)" - * vs. "locally-administered". By default, set it to - * generate locally-administered addresses. - * - * Maybe be overwritten by a mask below. */ - addr->ether_addr_octet[0] |= 2; - - if (!generate_mac_address_mask || !*generate_mac_address_mask) - goto out; - if (!_nm_utils_generate_mac_address_mask_parse(generate_mac_address_mask, - &mask, - &ouis, - &ouis_len, - NULL)) - goto out; - - nm_assert((ouis == NULL) ^ (ouis_len != 0)); - if (ouis) { - /* g_random_int() is good enough here. It uses a static GRand instance - * that is seeded from /dev/urandom. */ - oui = ouis[g_random_int() % ouis_len]; - g_free(ouis); - } else { - if (!nm_utils_hwaddr_aton(current_mac_address, &oui, ETH_ALEN)) - goto out; - } - - for (i = 0; i < ETH_ALEN; i++) { - const guint8 a = addr->ether_addr_octet[i]; - const guint8 o = oui.ether_addr_octet[i]; - const guint8 m = mask.ether_addr_octet[i]; - - addr->ether_addr_octet[i] = (a & ~m) | (o & m); - } - -out: - /* The LSB of the first octet must always be cleared, - * it means Unicast vs. Multicast */ - addr->ether_addr_octet[0] &= ~1; -} - -char * -nm_utils_hw_addr_gen_random_eth(const char *current_mac_address, - const char *generate_mac_address_mask) -{ - struct ether_addr bin_addr; - - nm_utils_random_bytes(&bin_addr, ETH_ALEN); - _hw_addr_eth_complete(&bin_addr, current_mac_address, generate_mac_address_mask); - return nm_utils_hwaddr_ntoa(&bin_addr, ETH_ALEN); -} - -static char * -_hw_addr_gen_stable_eth(NMUtilsStableType stable_type, - const char * stable_id, - const guint8 * host_id, - gsize host_id_len, - const char * ifname, - const char * current_mac_address, - const char * generate_mac_address_mask) -{ - nm_auto_free_checksum GChecksum *sum = NULL; - guint32 tmp; - guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA256]; - struct ether_addr bin_addr; - guint8 stable_type_uint8; - - nm_assert(stable_id); - nm_assert(host_id); - - sum = g_checksum_new(G_CHECKSUM_SHA256); - - host_id_len = MIN(host_id_len, G_MAXUINT32); - - nm_assert(stable_type < (NMUtilsStableType) 255); - stable_type_uint8 = stable_type; - g_checksum_update(sum, (const guchar *) &stable_type_uint8, sizeof(stable_type_uint8)); - - tmp = htonl((guint32) host_id_len); - g_checksum_update(sum, (const guchar *) &tmp, sizeof(tmp)); - g_checksum_update(sum, (const guchar *) host_id, host_id_len); - g_checksum_update(sum, (const guchar *) (ifname ?: ""), ifname ? (strlen(ifname) + 1) : 1); - g_checksum_update(sum, (const guchar *) stable_id, strlen(stable_id) + 1); - - nm_utils_checksum_get_digest(sum, digest); - - memcpy(&bin_addr, digest, ETH_ALEN); - _hw_addr_eth_complete(&bin_addr, current_mac_address, generate_mac_address_mask); - return nm_utils_hwaddr_ntoa(&bin_addr, ETH_ALEN); -} - -char * -nm_utils_hw_addr_gen_stable_eth_impl(NMUtilsStableType stable_type, - const char * stable_id, - const guint8 * host_id, - gsize host_id_len, - const char * ifname, - const char * current_mac_address, - const char * generate_mac_address_mask) -{ - return _hw_addr_gen_stable_eth(stable_type, - stable_id, - host_id, - host_id_len, - ifname, - current_mac_address, - generate_mac_address_mask); -} - -char * -nm_utils_hw_addr_gen_stable_eth(NMUtilsStableType stable_type, - const char * stable_id, - const char * ifname, - const char * current_mac_address, - const char * generate_mac_address_mask) -{ - const guint8 *host_id; - gsize host_id_len; - - g_return_val_if_fail(stable_id, NULL); - - nm_utils_host_id_get(&host_id, &host_id_len); - - return _hw_addr_gen_stable_eth(stable_type, - stable_id, - host_id, - host_id_len, - ifname, - current_mac_address, - generate_mac_address_mask); -} - -/*****************************************************************************/ - -GBytes * -nm_utils_dhcp_client_id_mac(int arp_type, const guint8 *hwaddr, gsize hwaddr_len) -{ - guint8 * client_id_buf; - const guint8 hwaddr_type = arp_type; - - if (!nm_utils_arp_type_get_hwaddr_relevant_part(arp_type, &hwaddr, &hwaddr_len)) - g_return_val_if_reached(NULL); - - client_id_buf = g_malloc(hwaddr_len + 1); - client_id_buf[0] = hwaddr_type; - memcpy(&client_id_buf[1], hwaddr, hwaddr_len); - return g_bytes_new_take(client_id_buf, hwaddr_len + 1); -} - -#define HASH_KEY \ - ((const guint8[16]){0x80, \ - 0x11, \ - 0x8c, \ - 0xc2, \ - 0xfe, \ - 0x4a, \ - 0x03, \ - 0xee, \ - 0x3e, \ - 0xd6, \ - 0x0c, \ - 0x6f, \ - 0x36, \ - 0x39, \ - 0x14, \ - 0x09}) - -/** - * nm_utils_create_dhcp_iaid: - * @legacy_unstable_byteorder: legacy behavior is to generate a u32 iaid which - * is endianness dependent. This is to preserve backward compatibility. - * For non-legacy behavior, the returned integer is in stable endianness, - * and corresponds to legacy behavior on little endian systems. - * @interface_id: the seed for hashing when generating the ID. Usually, - * this is the interface name. - * @interface_id_len: length of @interface_id - * - * This corresponds to systemd's dhcp_identifier_set_iaid() for generating - * a IAID for the interface. - * - * Returns: the IAID in host byte order. */ -guint32 -nm_utils_create_dhcp_iaid(gboolean legacy_unstable_byteorder, - const guint8 *interface_id, - gsize interface_id_len) -{ - guint64 u64; - guint32 u32; - - u64 = c_siphash_hash(HASH_KEY, interface_id, interface_id_len); - u32 = (u64 & 0xffffffffu) ^ (u64 >> 32); - if (legacy_unstable_byteorder) { - /* legacy systemd code dhcp_identifier_set_iaid() generates the iaid - * dependent on the host endianness. Since this function returns the IAID - * in native-byte order, we need to account for that. - * - * On little endian systems, we want the legacy-behavior is identical to - * the endianness-agnostic behavior. So, we need to swap the bytes on - * big-endian systems. - * - * (https://github.com/systemd/systemd/pull/10614). */ - return htole32(u32); - } else { - /* we return the value as-is, in native byte order. */ - return u32; - } -} - -/** - * nm_utils_dhcp_client_id_systemd_node_specific_full: - * @iaid: the IAID (identity association identifier) in native byte order - * @machine_id: the binary identifier for the machine. It is hashed - * into the DUID. It commonly is /etc/machine-id (parsed in binary as NMUuid). - * @machine_id_len: the length of the @machine_id. - * - * Systemd's sd_dhcp_client generates a default client ID (type 255, node-specific, - * RFC 4361) if no explicit client-id is set. This function duplicates that - * implementation and exposes it as (internal) API. - * - * Returns: a %GBytes of generated client-id. This function cannot fail. - */ -GBytes * -nm_utils_dhcp_client_id_systemd_node_specific_full(guint32 iaid, - const guint8 *machine_id, - gsize machine_id_len) -{ - const guint16 DUID_TYPE_EN = 2; - const guint32 SYSTEMD_PEN = 43793; - struct _nm_packed { - guint8 type; - guint32 iaid; - struct _nm_packed { - guint16 type; - union { - struct _nm_packed { - /* DUID_TYPE_EN */ - guint32 pen; - uint8_t id[8]; - } en; - }; - } duid; - } * client_id; - guint64 u64; - - g_return_val_if_fail(machine_id, NULL); - g_return_val_if_fail(machine_id_len > 0, NULL); - - client_id = g_malloc(sizeof(*client_id)); - - client_id->type = 255; - unaligned_write_be32(&client_id->iaid, iaid); - unaligned_write_be16(&client_id->duid.type, DUID_TYPE_EN); - unaligned_write_be32(&client_id->duid.en.pen, SYSTEMD_PEN); - - u64 = htole64(c_siphash_hash(HASH_KEY, machine_id, machine_id_len)); - memcpy(client_id->duid.en.id, &u64, sizeof(client_id->duid.en.id)); - - G_STATIC_ASSERT_EXPR(sizeof(*client_id) == 19); - return g_bytes_new_take(client_id, 19); -} - -GBytes * -nm_utils_dhcp_client_id_systemd_node_specific(guint32 iaid) -{ - return nm_utils_dhcp_client_id_systemd_node_specific_full( - iaid, - (const guint8 *) nm_utils_machine_id_bin(), - sizeof(NMUuid)); -} - -/*****************************************************************************/ - -GBytes * -nm_utils_generate_duid_llt(int arp_type, const guint8 *hwaddr, gsize hwaddr_len, gint64 time) -{ - guint8 * arr; - const guint16 duid_type = htons(1); - const guint16 hw_type = htons(arp_type); - const guint32 duid_time = htonl(NM_MAX(0, time - NM_UTILS_EPOCH_DATETIME_200001010000)); - - if (!nm_utils_arp_type_get_hwaddr_relevant_part(arp_type, &hwaddr, &hwaddr_len)) - nm_assert_not_reached(); - - arr = g_new(guint8, (2u + 2u + 4u) + hwaddr_len); - - memcpy(&arr[0], &duid_type, 2); - memcpy(&arr[2], &hw_type, 2); - memcpy(&arr[4], &duid_time, 4); - memcpy(&arr[8], hwaddr, hwaddr_len); - - return g_bytes_new_take(arr, (2u + 2u + 4u) + hwaddr_len); -} - -GBytes * -nm_utils_generate_duid_ll(int arp_type, const guint8 *hwaddr, gsize hwaddr_len) -{ - guint8 * arr; - const guint16 duid_type = htons(3); - const guint16 hw_type = htons(arp_type); - - if (!nm_utils_arp_type_get_hwaddr_relevant_part(arp_type, &hwaddr, &hwaddr_len)) - nm_assert_not_reached(); - - arr = g_new(guint8, (2u + 2u) + hwaddr_len); - - memcpy(&arr[0], &duid_type, 2); - memcpy(&arr[2], &hw_type, 2); - memcpy(&arr[4], hwaddr, hwaddr_len); - - return g_bytes_new_take(arr, (2u + 2u) + hwaddr_len); -} - -GBytes * -nm_utils_generate_duid_uuid(const NMUuid *uuid) -{ - const guint16 duid_type = htons(4); - guint8 * duid_buffer; - - nm_assert(uuid); - - /* Generate a DHCP Unique Identifier for DHCPv6 using the - * DUID-UUID method (see RFC 6355 section 4). Format is: - * - * u16: type (DUID-UUID = 4) - * u8[16]: UUID bytes - */ - G_STATIC_ASSERT_EXPR(sizeof(duid_type) == 2); - G_STATIC_ASSERT_EXPR(sizeof(*uuid) == 16); - duid_buffer = g_malloc(18); - memcpy(&duid_buffer[0], &duid_type, 2); - memcpy(&duid_buffer[2], uuid, 16); - return g_bytes_new_take(duid_buffer, 18); -} - -GBytes * -nm_utils_generate_duid_from_machine_id(void) -{ - static GBytes *volatile global_duid = NULL; - GBytes *p; - -again: - p = g_atomic_pointer_get(&global_duid); - if (G_UNLIKELY(!p)) { - nm_auto_free_checksum GChecksum *sum = NULL; - const NMUuid * machine_id; - union { - guint8 sha256[NM_UTILS_CHECKSUM_LENGTH_SHA256]; - NMUuid uuid; - } digest; - - machine_id = nm_utils_machine_id_bin(); - - /* Hash the machine ID so it's not leaked to the network. - * - * Optimally, we would choose an use case specific seed, but for historic - * reasons we didn't. */ - sum = g_checksum_new(G_CHECKSUM_SHA256); - g_checksum_update(sum, (const guchar *) machine_id, sizeof(*machine_id)); - nm_utils_checksum_get_digest(sum, digest.sha256); - - G_STATIC_ASSERT_EXPR(sizeof(digest.sha256) > sizeof(digest.uuid)); - p = nm_utils_generate_duid_uuid(&digest.uuid); - - if (!g_atomic_pointer_compare_and_exchange(&global_duid, NULL, p)) { - g_bytes_unref(p); - goto again; - } - } - - return g_bytes_ref(p); -} - -/*****************************************************************************/ - -/** - * nm_utils_setpgid: - * @unused: unused - * - * This can be passed as a child setup function to the g_spawn*() family - * of functions, to ensure that the child is in its own process group - * (and thus, in some situations, will not be killed when NetworkManager - * is killed). - */ -void -nm_utils_setpgid(gpointer unused G_GNUC_UNUSED) -{ - pid_t pid; - - pid = getpid(); - setpgid(pid, pid); -} - -/** - * nm_utils_g_value_set_strv: - * @value: a #GValue, initialized to store a #G_TYPE_STRV - * @strings: a #GPtrArray of strings. %NULL values are not - * allowed. - * - * Converts @strings to a #GStrv and stores it in @value. - */ -void -nm_utils_g_value_set_strv(GValue *value, GPtrArray *strings) -{ - char **strv; - guint i; - - strv = g_new(char *, strings->len + 1); - for (i = 0; i < strings->len; i++) { - nm_assert(strings->pdata[i]); - strv[i] = g_strdup(strings->pdata[i]); - } - strv[i] = NULL; - - g_value_take_boxed(value, strv); -} - -/*****************************************************************************/ - -void -nm_utils_ifname_cpy(char *dst, const char *name) -{ - int i; - - g_return_if_fail(dst); - g_return_if_fail(name && name[0]); - - nm_assert(nm_utils_ifname_valid_kernel(name, NULL)); - - /* ensures NUL padding of the entire IFNAMSIZ buffer. */ - - for (i = 0; i < (int) IFNAMSIZ && name[i] != '\0'; i++) - dst[i] = name[i]; - - nm_assert(name[i] == '\0'); - - for (; i < (int) IFNAMSIZ; i++) - dst[i] = '\0'; -} - -/*****************************************************************************/ - -/** - * Takes a pair @timestamp and @duration, and returns the remaining duration based - * on the new timestamp @now. - */ -guint32 -nm_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 duration, gint32 now) -{ - gint64 t; - - nm_assert(now >= 0); - - if (duration == NM_PLATFORM_LIFETIME_PERMANENT) - return NM_PLATFORM_LIFETIME_PERMANENT; - - if (timestamp == 0) { - /* if the @timestamp is zero, assume it was just left unset and that the relative - * @duration starts counting from @now. This is convenient to construct an address - * and print it in nm_platform_ip4_address_to_string(). - * - * In general it does not make sense to set the @duration without anchoring at - * @timestamp because you don't know the absolute expiration time when looking - * at the address at a later moment. */ - timestamp = now; - } - - /* For timestamp > now, just accept it and calculate the expected(?) result. */ - t = (gint64) timestamp + (gint64) duration - (gint64) now; - - if (t <= 0) - return 0; - if (t >= NM_PLATFORM_LIFETIME_PERMANENT) - return NM_PLATFORM_LIFETIME_PERMANENT - 1; - return t; -} - -guint32 -nm_utils_lifetime_get(guint32 timestamp, - guint32 lifetime, - guint32 preferred, - gint32 now, - guint32 *out_preferred) -{ - guint32 t_lifetime, t_preferred; - - nm_assert(now >= 0); - - if (timestamp == 0 && lifetime == 0) { - /* We treat lifetime==0 && timestamp==0 addresses as permanent addresses to allow easy - * creation of such addresses (without requiring to set the lifetime fields to - * NM_PLATFORM_LIFETIME_PERMANENT). The real lifetime==0 addresses (E.g. DHCP6 telling us - * to drop an address will have timestamp set. - */ - NM_SET_OUT(out_preferred, NM_PLATFORM_LIFETIME_PERMANENT); - g_return_val_if_fail(preferred == 0, NM_PLATFORM_LIFETIME_PERMANENT); - return NM_PLATFORM_LIFETIME_PERMANENT; - } - - if (now <= 0) - now = nm_utils_get_monotonic_timestamp_sec(); - - t_lifetime = nm_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now); - if (!t_lifetime) { - NM_SET_OUT(out_preferred, 0); - return 0; - } - - t_preferred = nm_utils_lifetime_rebase_relative_time_on_now(timestamp, preferred, now); - - NM_SET_OUT(out_preferred, MIN(t_preferred, t_lifetime)); - - /* Assert that non-permanent addresses have a (positive) @timestamp. nm_utils_lifetime_rebase_relative_time_on_now() - * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always - * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew. - */ - g_return_val_if_fail(timestamp != 0 - || (lifetime == NM_PLATFORM_LIFETIME_PERMANENT - && preferred == NM_PLATFORM_LIFETIME_PERMANENT), - t_lifetime); - g_return_val_if_fail(t_preferred <= t_lifetime, t_lifetime); - - return t_lifetime; -} - -const char * -nm_utils_dnsmasq_status_to_string(int status, char *dest, gsize size) -{ - const char *msg; - - nm_utils_to_string_buffer_init(&dest, &size); - - if (status == 0) - msg = "Success"; - else if (status == 1) - msg = "Configuration problem"; - else if (status == 2) - msg = "Network access problem (address in use, permissions)"; - else if (status == 3) - msg = "Filesystem problem (missing file/directory, permissions)"; - else if (status == 4) - msg = "Memory allocation failure"; - else if (status == 5) - msg = "Other problem"; - else if (status >= 11) { - g_snprintf(dest, size, "Lease script failed with error %d", status - 10); - return dest; - } else - msg = "Unknown problem"; - - g_snprintf(dest, size, "%s (%d)", msg, status); - return dest; -} - -/** - * nm_utils_get_reverse_dns_domains_ip4: - * @addr: IP address in network order - * @plen: prefix length - * @domains: array for results - * - * Creates reverse DNS domains for the given address and prefix length, and - * append them to @domains. - */ -void -nm_utils_get_reverse_dns_domains_ip4(guint32 addr, guint8 plen, GPtrArray *domains) -{ - guint32 ip, ip2, mask; - guchar *p; - guint octets; - guint i; - gsize len0, len; - char * str, *s; - - g_return_if_fail(domains); - g_return_if_fail(plen <= 32); - - if (!plen) - return; - - octets = (plen - 1) / 8 + 1; - ip = ntohl(addr); - mask = 0xFFFFFFFF << (32 - plen); - ip &= mask; - ip2 = ip; - - len0 = NM_STRLEN("in-addr.arpa") + (4 * octets) + 1; - while ((ip2 & mask) == ip) { - addr = htonl(ip2); - p = (guchar *) &addr; - - len = len0; - str = s = g_malloc(len); - for (i = octets; i > 0; i--) - nm_utils_strbuf_append(&s, &len, "%u.", p[i - 1] & 0xff); - nm_utils_strbuf_append_str(&s, &len, "in-addr.arpa"); - - g_ptr_array_add(domains, str); - - ip2 += 1 << ((32 - plen) & ~7); - } -} - -/** - * nm_utils_get_reverse_dns_domains_ip6: - * @addr: IPv6 address - * @plen: prefix length - * @domains: array for results - * - * Creates reverse DNS domains for the given address and prefix length, and - * append them to @domains. - */ -void -nm_utils_get_reverse_dns_domains_ip6(const struct in6_addr *ip, guint8 plen, GPtrArray *domains) -{ - struct in6_addr addr; - guint nibbles, bits, entries; - int i, j; - gsize len0, len; - char * str, *s; - - g_return_if_fail(domains); - g_return_if_fail(plen <= 128); - - if (!plen) - return; - - memcpy(&addr, ip, sizeof(struct in6_addr)); - nm_utils_ip6_address_clear_host_address(&addr, NULL, plen); - - /* Number of nibbles to include in domains */ - nibbles = (plen - 1) / 4 + 1; - /* Prefix length in nibble */ - bits = plen - ((plen - 1) / 4 * 4); - /* Number of domains */ - entries = 1 << (4 - bits); - - len0 = NM_STRLEN("ip6.arpa") + (2 * nibbles) + 1; - -#define N_SHIFT(x) ((x) % 2 ? 0 : 4) - - for (i = 0; i < entries; i++) { - len = len0; - str = s = g_malloc(len); - - for (j = nibbles - 1; j >= 0; j--) - nm_utils_strbuf_append(&s, &len, "%x.", (addr.s6_addr[j / 2] >> N_SHIFT(j)) & 0xf); - nm_utils_strbuf_append_str(&s, &len, "ip6.arpa"); - - g_ptr_array_add(domains, str); - - addr.s6_addr[(nibbles - 1) / 2] += 1 << N_SHIFT(nibbles - 1); - } - -#undef N_SHIFT -} - -struct plugin_info { - char * path; - struct stat st; -}; - -static int -read_device_factory_paths_sort_fcn(gconstpointer a, gconstpointer b) -{ - const struct plugin_info *da = a; - const struct plugin_info *db = b; - time_t ta, tb; - - ta = MAX(da->st.st_mtime, da->st.st_ctime); - tb = MAX(db->st.st_mtime, db->st.st_ctime); - - if (ta < tb) - return 1; - if (ta > tb) - return -1; - return 0; -} - -gboolean -nm_utils_validate_plugin(const char *path, struct stat *st, GError **error) -{ - g_return_val_if_fail(path, FALSE); - g_return_val_if_fail(st, FALSE); - g_return_val_if_fail(!error || !*error, FALSE); - - if (!S_ISREG(st->st_mode)) { - g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "not a regular file"); - return FALSE; - } - - if (st->st_uid != 0) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - "file has invalid owner (should be root)"); - return FALSE; - } - - if (st->st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - "file has invalid permissions"); - return FALSE; - } - - return TRUE; -} - -char ** -nm_utils_read_plugin_paths(const char *dirname, const char *prefix) -{ - GDir * dir; - GError * error = NULL; - const char *item; - GArray * paths; - char ** result; - guint i; - - g_return_val_if_fail(dirname, NULL); - g_return_val_if_fail(prefix, NULL); - - dir = g_dir_open(dirname, 0, &error); - if (!dir) { - nm_log_warn(LOGD_CORE, - "device plugin: failed to open directory %s: %s", - dirname, - error->message); - g_clear_error(&error); - return NULL; - } - - paths = g_array_new(FALSE, FALSE, sizeof(struct plugin_info)); - - while ((item = g_dir_read_name(dir))) { - int errsv; - struct plugin_info data; - - if (!g_str_has_prefix(item, prefix)) - continue; - if (!g_str_has_suffix(item, ".so")) - continue; - - data.path = g_build_filename(dirname, item, NULL); - - if (stat(data.path, &data.st) != 0) { - errsv = errno; - nm_log_warn(LOGD_CORE, - "plugin: skip invalid file %s (error during stat: %s)", - data.path, - nm_strerror_native(errsv)); - goto skip; - } - - if (!nm_utils_validate_plugin(data.path, &data.st, &error)) { - nm_log_warn(LOGD_CORE, "plugin: skip invalid file %s: %s", data.path, error->message); - g_clear_error(&error); - goto skip; - } - - g_array_append_val(paths, data); - continue; -skip: - g_free(data.path); - } - g_dir_close(dir); - - /* sort filenames by modification time. */ - g_array_sort(paths, read_device_factory_paths_sort_fcn); - - result = g_new(char *, paths->len + 1); - for (i = 0; i < paths->len; i++) - result[i] = g_array_index(paths, struct plugin_info, i).path; - result[i] = NULL; - - g_array_free(paths, TRUE); - return result; -} - -char * -nm_utils_format_con_diff_for_audit(GHashTable *diff) -{ - GHashTable * setting_diff; - char * setting_name, *prop_name; - GHashTableIter iter, iter2; - GString * str; - - str = g_string_sized_new(32); - g_hash_table_iter_init(&iter, diff); - - while (g_hash_table_iter_next(&iter, (gpointer *) &setting_name, (gpointer *) &setting_diff)) { - if (!setting_diff) - continue; - - g_hash_table_iter_init(&iter2, setting_diff); - - while (g_hash_table_iter_next(&iter2, (gpointer *) &prop_name, NULL)) - g_string_append_printf(str, "%s.%s,", setting_name, prop_name); - } - - if (str->len) - str->str[str->len - 1] = '\0'; - - return g_string_free(str, FALSE); -} - -const char * -nm_utils_parse_dns_domain(const char *domain, gboolean *is_routing) -{ - g_return_val_if_fail(domain, NULL); - g_return_val_if_fail(domain[0], NULL); - - if (domain[0] == '~') { - domain++; - NM_SET_OUT(is_routing, TRUE); - } else - NM_SET_OUT(is_routing, FALSE); - - return domain; -} - -/*****************************************************************************/ - -static guint32 -get_max_rate_ht_20(int mcs) -{ - switch (mcs) { - case 0: - return 6500000; - case 1: - case 8: - return 13000000; - case 2: - case 16: - return 19500000; - case 3: - case 9: - case 24: - return 26000000; - case 4: - case 10: - case 17: - return 39000000; - case 5: - case 11: - case 25: - return 52000000; - case 6: - case 18: - return 58500000; - case 7: - return 65000000; - case 12: - case 19: - case 26: - return 78000000; - case 13: - case 27: - return 104000000; - case 14: - case 20: - return 117000000; - case 15: - return 130000000; - case 21: - case 28: - return 156000000; - case 22: - return 175500000; - case 23: - return 195000000; - case 29: - return 208000000; - case 30: - return 234000000; - case 31: - return 260000000; - } - return 0; -} - -static guint32 -get_max_rate_ht_40(int mcs) -{ - switch (mcs) { - case 0: - return 13500000; - case 1: - case 8: - return 27000000; - case 2: - return 40500000; - case 3: - case 9: - case 24: - return 54000000; - case 4: - case 10: - case 17: - return 81000000; - case 5: - case 11: - case 25: - return 108000000; - case 6: - case 18: - return 121500000; - case 7: - return 135000000; - case 12: - case 19: - case 26: - return 162000000; - case 13: - case 27: - return 216000000; - case 14: - case 20: - return 243000000; - case 15: - return 270000000; - case 16: - return 40500000; - case 21: - case 28: - return 324000000; - case 22: - return 364500000; - case 23: - return 405000000; - case 29: - return 432000000; - case 30: - return 486000000; - case 31: - return 540000000; - } - return 0; -} - -static guint32 -get_max_rate_vht_80_ss1(int mcs) -{ - switch (mcs) { - case 0: - return 29300000; - case 1: - return 58500000; - case 2: - return 87800000; - case 3: - return 117000000; - case 4: - return 175500000; - case 5: - return 234000000; - case 6: - return 263300000; - case 7: - return 292500000; - case 8: - return 351000000; - case 9: - return 390000000; - } - return 0; -} - -static guint32 -get_max_rate_vht_80_ss2(int mcs) -{ - switch (mcs) { - case 0: - return 58500000; - case 1: - return 117000000; - case 2: - return 175500000; - case 3: - return 234000000; - case 4: - return 351000000; - case 5: - return 468000000; - case 6: - return 526500000; - case 7: - return 585000000; - case 8: - return 702000000; - case 9: - return 780000000; - } - return 0; -} - -static guint32 -get_max_rate_vht_80_ss3(int mcs) -{ - switch (mcs) { - case 0: - return 87800000; - case 1: - return 175500000; - case 2: - return 263300000; - case 3: - return 351000000; - case 4: - return 526500000; - case 5: - return 702000000; - case 6: - return 0; - case 7: - return 877500000; - case 8: - return 105300000; - case 9: - return 117000000; - } - return 0; -} - -static guint32 -get_max_rate_vht_160_ss1(int mcs) -{ - switch (mcs) { - case 0: - return 58500000; - case 1: - return 117000000; - case 2: - return 175500000; - case 3: - return 234000000; - case 4: - return 351000000; - case 5: - return 468000000; - case 6: - return 526500000; - case 7: - return 585000000; - case 8: - return 702000000; - case 9: - return 780000000; - } - return 0; -} - -static guint32 -get_max_rate_vht_160_ss2(int mcs) -{ - switch (mcs) { - case 0: - return 117000000; - case 1: - return 234000000; - case 2: - return 351000000; - case 3: - return 468000000; - case 4: - return 702000000; - case 5: - return 936000000; - case 6: - return 1053000000; - case 7: - return 1170000000; - case 8: - return 1404000000; - case 9: - return 1560000000; - } - return 0; -} - -static guint32 -get_max_rate_vht_160_ss3(int mcs) -{ - switch (mcs) { - case 0: - return 175500000; - case 1: - return 351000000; - case 2: - return 526500000; - case 3: - return 702000000; - case 4: - return 1053000000; - case 5: - return 1404000000; - case 6: - return 1579500000; - case 7: - return 1755000000; - case 8: - return 2106000000; - case 9: - return 0; - } - return 0; -} - -static gboolean -get_max_rate_ht(const guint8 *bytes, guint len, guint32 *out_maxrate) -{ - guint32 i; - guint8 ht_cap_info; - const guint8 *supported_mcs_set; - guint32 rate; - - /* http://standards.ieee.org/getieee802/download/802.11-2012.pdf - * https://mrncciew.com/2014/10/19/cwap-ht-capabilities-ie/ - */ - - if (len != 26) - return FALSE; - - ht_cap_info = bytes[0]; - supported_mcs_set = &bytes[3]; - *out_maxrate = 0; - - /* Find the maximum supported mcs rate */ - for (i = 0; i <= 76; i++) { - const unsigned mcs_octet = i / 8; - const unsigned MCS_RATE_BIT = 1 << i % 8; - - if (supported_mcs_set[mcs_octet] & MCS_RATE_BIT) { - /* Check for 40Mhz wide channel support */ - if (ht_cap_info & (1 << 1)) - rate = get_max_rate_ht_40(i); - else - rate = get_max_rate_ht_20(i); - - if (rate > *out_maxrate) - *out_maxrate = rate; - } - } - - return TRUE; -} - -static gboolean -get_max_rate_vht(const guint8 *bytes, guint len, guint32 *out_maxrate) -{ - guint32 mcs, m; - guint8 vht_cap, tx_map; - - /* https://tda802dot11.blogspot.it/2014/10/vht-capabilities-element-vht.html - * http://chimera.labs.oreilly.com/books/1234000001739/ch03.html#management_frames */ - - if (len != 12) - return FALSE; - - vht_cap = bytes[0]; - tx_map = bytes[8]; - - /* Check for mcs rates 8 and 9 support */ - if (tx_map & 0x2a) - mcs = 9; - else if (tx_map & 0x15) - mcs = 8; - else - mcs = 7; - - /* Check for 160Mhz wide channel support and - * spatial stream support */ - if (vht_cap & (1 << 2)) { - if (tx_map & 0x30) - m = get_max_rate_vht_160_ss3(mcs); - else if (tx_map & 0x0C) - m = get_max_rate_vht_160_ss2(mcs); - else - m = get_max_rate_vht_160_ss1(mcs); - } else { - if (tx_map & 0x30) - m = get_max_rate_vht_80_ss3(mcs); - else if (tx_map & 0x0C) - m = get_max_rate_vht_80_ss2(mcs); - else - m = get_max_rate_vht_80_ss1(mcs); - } - - *out_maxrate = m; - return TRUE; -} - -/* Management Frame Information Element IDs, ieee80211_eid */ -#define WLAN_EID_HT_CAPABILITY 45 -#define WLAN_EID_VHT_CAPABILITY 191 -#define WLAN_EID_VENDOR_SPECIFIC 221 - -void -nm_wifi_utils_parse_ies(const guint8 *bytes, - gsize len, - guint32 * out_max_rate, - gboolean * out_metered, - gboolean * out_owe_transition_mode) -{ - guint8 id, elem_len; - guint32 m; - - NM_SET_OUT(out_max_rate, 0); - NM_SET_OUT(out_metered, FALSE); - NM_SET_OUT(out_owe_transition_mode, FALSE); - - while (len) { - if (len < 2) - break; - - id = *bytes++; - elem_len = *bytes++; - len -= 2; - - if (elem_len > len) - break; - - switch (id) { - case WLAN_EID_HT_CAPABILITY: - if (out_max_rate) { - if (get_max_rate_ht(bytes, elem_len, &m)) - *out_max_rate = NM_MAX(*out_max_rate, m); - } - break; - case WLAN_EID_VHT_CAPABILITY: - if (out_max_rate) { - if (get_max_rate_vht(bytes, elem_len, &m)) - *out_max_rate = NM_MAX(*out_max_rate, m); - } - break; - case WLAN_EID_VENDOR_SPECIFIC: - if (len == 8 && bytes[0] == 0x00 /* OUI: Microsoft */ - && bytes[1] == 0x50 && bytes[2] == 0xf2 - && bytes[3] == 0x11) /* OUI type: Network cost */ - NM_SET_OUT(out_metered, (bytes[7] > 1)); /* Cost level > 1 */ - if (elem_len >= 10 && bytes[0] == 0x50 /* OUI: WiFi Alliance */ - && bytes[1] == 0x6f && bytes[2] == 0x9a - && bytes[3] == 0x1c) /* OUI type: OWE Transition Mode */ - NM_SET_OUT(out_owe_transition_mode, TRUE); - break; - } - - len -= elem_len; - bytes += elem_len; - } -} - -/*****************************************************************************/ - -guint8 -nm_wifi_utils_level_to_quality(int val) -{ - if (val < 0) { - /* Assume dBm already; rough conversion: best = -40, worst = -100 */ - val = abs(CLAMP(val, -100, -40) + 40); /* normalize to 0 */ - val = 100 - (int) ((100.0 * (double) val) / 60.0); - } else if (val > 110 && val < 256) { - /* assume old-style WEXT 8-bit unsigned signal level */ - val -= 256; /* subtract 256 to convert to dBm */ - val = abs(CLAMP(val, -100, -40) + 40); /* normalize to 0 */ - val = 100 - (int) ((100.0 * (double) val) / 60.0); - } else { - /* Assume signal is a "quality" percentage */ - } - - return CLAMP(val, 0, 100); -} - -/*****************************************************************************/ - -NM_UTILS_ENUM2STR_DEFINE(nm_icmpv6_router_pref_to_string, - NMIcmpv6RouterPref, - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_LOW, "low"), - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_MEDIUM, "medium"), - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_HIGH, "high"), - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_INVALID, "invalid"), ); - -NM_UTILS_LOOKUP_STR_DEFINE(nm_activation_type_to_string, - NMActivationType, - NM_UTILS_LOOKUP_DEFAULT_WARN("(unknown)"), - NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_MANAGED, "managed"), - NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_ASSUME, "assume"), - NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_EXTERNAL, "external"), ); |