NetworkManager/src/core/nm-core-utils.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

5324 lines
174 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2004 - 2018 Red Hat, Inc.
* Copyright (C) 2005 - 2008 Novell, Inc.
*/
#include "src/core/nm-default-daemon.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 "libnm-glib-aux/nm-uuid.h"
#include "libnm-platform/nmp-base.h"
#include "libnm-std-aux/unaligned.h"
#include "libnm-glib-aux/nm-random-utils.h"
#include "libnm-glib-aux/nm-io-utils.h"
#include "libnm-glib-aux/nm-secret-utils.h"
#include "libnm-glib-aux/nm-time-utils.h"
#include "libnm-glib-aux/nm-str-buf.h"
#include "nm-utils.h"
#include "libnm-core-intern/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(void)
{
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(void)
{
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);
}
/*****************************************************************************/
gboolean
nm_ether_addr_is_valid(const NMEtherAddr *addr)
{
static const guint8 invalid_addr[][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 */
};
int i;
if (!addr)
return FALSE;
/* Check for multicast address */
if (addr->ether_addr_octet[0] & 0x01u)
return FALSE;
for (i = 0; i < (int) G_N_ELEMENTS(invalid_addr); i++) {
if (memcmp(addr, invalid_addr[i], ETH_ALEN) == 0)
return FALSE;
}
return TRUE;
}
gboolean
nm_ether_addr_is_valid_str(const char *str)
{
NMEtherAddr addr_bin;
if (!str)
return FALSE;
if (!nm_utils_hwaddr_aton(str, &addr_bin, ETH_ALEN))
return FALSE;
return nm_ether_addr_is_valid(&addr_bin);
}
/*****************************************************************************/
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);
}
/*****************************************************************************/
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;
nm_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: (nullable): 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 = ((guint64) sleep_duration_msec) * 1000UL;
nm_assert(x < G_MAXULONG);
return x;
}
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) (optional): 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 *
dns/dnsmasq: refactor tracking of dnsmasq process Several points. - We spawn the dnsmasq process directly. That has several downsides: - The lifetime of the process is tied to NetworkManager's. When stopping NetworkManager, we usually also stop dnsmasq. Or we keep the process running, but later the process is no longer a child process of NetworkManager and we can only kill it using the pidfile. - We don't do special sandboxing of the dnsmasq process. - Note that we want to ensure that only one dnsmasq process is running at any time. We should track that in a singletone. Note that NMDnsDnsmasq is not a singleton. While there is only one instance active at any time, the DNS plugin can be swapped (e.g. during SIGHUP). Hence, don't track the process per-NMDnsDnsmasq instance, but in a global variable "gl_pid". - Usually, when NetworkManager quits, it also stops the dnsmasq process. Previously, we would always try to terminate the process based on the pidfile. That is wrong. Most of the time, NetworkManager spawned the process itself, as a child process. Hence, the PID is known and NetworkManager will get a signal when dnsmasq exits. The only moment when NetworkManager should use the pidfile, is the first time when checking to kill the previous instance. That is: only once at the beginning, to kill instances that were intentionally or unintentionally (crash) left running earlier. This is now done by _gl_pid_kill_external(). - Previously, before starting a new dnsmasq instance we would kill a possibly already running one, and block while waiting for the process to disappear. We should never block. Especially, since we afterwards start the process also in non-blocking way, there is no reason to kill the existing process in a blocking way. For the most part, starting dnsmasq is already asynchronous and so should be the killing of the dnsmasq process. - Drop GDBusProxy and only use GDBusConnection. It fully suffices. - When we kill a dnsmasq instance, we actually don't have to wait at all. That can happen fully in background. The only pecularity is that when we restart a new instance before the previous instance is killed, then we must wait for the previous process to terminate first. Also, if we are about to exit while killing the dnsmasq instance, we must register nm_shutdown_wait_obj_*() to wait until the process is fully gone.
2019-08-31 09:50:54 +02:00
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;
device: add "dhcp-plugin" match spec for device The need for this is the following: "ipv4.dhcp-client-id" can be specified via global connection defaults. In absence of any configuration in NetworkManager, the default depends on the DHCP client plugin. In case of "dhclient", the default further depends on /etc/dhcp. For "internal" plugin, we may very well want to change the default client-id to "mac" by universally installing a configuration snippet [connection-use-mac-client-id] ipv4.dhcp-client-id=mac However, if we the user happens to enable "dhclient" plugin, this also forces the client-id and overrules configuration from /etc/dhcp. The real problem is, that dhclient can be configured via means outside of NetworkManager, so our defaults shall not overwrite defaults from /etc/dhcp. With the new device spec, we can avoid this issue: [connection-dhcp-client-id] match-device=except:dhcp-plugin:dhclient ipv4.dhcp-client-id=mac This will be part of the solution for rh#1640494. Note that merely dropping a configuration snippet is not yet enough. More fixes for DHCP will follow. Also, bug rh#1640494 may have alternative solutions as well. The nice part of this new feature is that it is generally useful for configuring connection defaults and not specifically for the client-id issue. Note that this match spec is per-device, although the plugin is selected globally. That makes some sense, because in the future we may or may not configure the DHCP plugin per-device or per address family. https://bugzilla.redhat.com/show_bug.cgi?id=1640494
2018-10-24 08:43:45 +02:00
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);
device: add "dhcp-plugin" match spec for device The need for this is the following: "ipv4.dhcp-client-id" can be specified via global connection defaults. In absence of any configuration in NetworkManager, the default depends on the DHCP client plugin. In case of "dhclient", the default further depends on /etc/dhcp. For "internal" plugin, we may very well want to change the default client-id to "mac" by universally installing a configuration snippet [connection-use-mac-client-id] ipv4.dhcp-client-id=mac However, if we the user happens to enable "dhclient" plugin, this also forces the client-id and overrules configuration from /etc/dhcp. The real problem is, that dhclient can be configured via means outside of NetworkManager, so our defaults shall not overwrite defaults from /etc/dhcp. With the new device spec, we can avoid this issue: [connection-dhcp-client-id] match-device=except:dhcp-plugin:dhclient ipv4.dhcp-client-id=mac This will be part of the solution for rh#1640494. Note that merely dropping a configuration snippet is not yet enough. More fixes for DHCP will follow. Also, bug rh#1640494 may have alternative solutions as well. The nice part of this new feature is that it is generally useful for configuring connection defaults and not specifically for the client-id issue. Note that this match spec is per-device, although the plugin is selected globally. That makes some sense, because in the future we may or may not configure the DHCP plugin per-device or per address family. https://bugzilla.redhat.com/show_bug.cgi?id=1640494
2018-10-24 08:43:45 +02:00
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),
device: add "dhcp-plugin" match spec for device The need for this is the following: "ipv4.dhcp-client-id" can be specified via global connection defaults. In absence of any configuration in NetworkManager, the default depends on the DHCP client plugin. In case of "dhclient", the default further depends on /etc/dhcp. For "internal" plugin, we may very well want to change the default client-id to "mac" by universally installing a configuration snippet [connection-use-mac-client-id] ipv4.dhcp-client-id=mac However, if we the user happens to enable "dhclient" plugin, this also forces the client-id and overrules configuration from /etc/dhcp. The real problem is, that dhclient can be configured via means outside of NetworkManager, so our defaults shall not overwrite defaults from /etc/dhcp. With the new device spec, we can avoid this issue: [connection-dhcp-client-id] match-device=except:dhcp-plugin:dhclient ipv4.dhcp-client-id=mac This will be part of the solution for rh#1640494. Note that merely dropping a configuration snippet is not yet enough. More fixes for DHCP will follow. Also, bug rh#1640494 may have alternative solutions as well. The nice part of this new feature is that it is generally useful for configuring connection defaults and not specifically for the client-id issue. Note that this match spec is per-device, although the plugin is selected globally. That makes some sense, because in the future we may or may not configure the DHCP plugin per-device or per address family. https://bugzilla.redhat.com/show_bug.cgi?id=1640494
2018-10-24 08:43:45 +02:00
.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);
}
typedef struct {
const char *uuid;
const char *id;
const char *origin;
} MatchConnectionData;
static gboolean
match_connection_eval(const char *spec_str, const MatchConnectionData *match_data)
{
if (spec_str[0] == '*' && spec_str[1] == '\0')
return TRUE;
if (_MATCH_CHECK(spec_str, "id:"))
return nm_streq0(spec_str, match_data->id);
if (_MATCH_CHECK(spec_str, "uuid:"))
return nm_streq0(spec_str, match_data->uuid);
if (_MATCH_CHECK(spec_str, "origin:"))
return nm_streq0(spec_str, match_data->origin);
return FALSE;
}
static NMMatchSpecMatchType
match_spec_connection(const GSList *specs, const char *id, const char *uuid, const char *origin)
{
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;
const MatchConnectionData match_data = {
.id = nm_str_not_empty(id),
.uuid = nm_str_not_empty(uuid),
.origin = nm_str_not_empty(origin),
};
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_connection_eval(spec_str, &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);
}
int
nm_utils_connection_match_spec_list(NMConnection *connection,
const GSList *specs,
int no_match_value)
{
NMMatchSpecMatchType m;
NMSettingUser *s_user;
const char *origin = NULL;
if (!specs)
return no_match_value;
s_user = _nm_connection_get_setting(connection, NM_TYPE_SETTING_USER);
if (s_user)
origin = nm_setting_user_get_data(s_user, NM_USER_TAG_ORIGIN);
m = match_spec_connection(specs,
nm_connection_get_id(connection),
nm_connection_get_uuid(connection),
origin);
switch (m) {
case NM_MATCH_SPEC_MATCH:
return TRUE;
case NM_MATCH_SPEC_NEG_MATCH:
return FALSE;
case NM_MATCH_SPEC_NO_MATCH:
return no_match_value;
}
nm_assert_not_reached();
return no_match_value;
}
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);
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
static void
_pattern_parse(const char *input,
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
const char **out_pattern,
gboolean *out_is_inverted,
gboolean *out_is_mandatory)
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
{
gboolean is_inverted = FALSE;
gboolean is_mandatory = FALSE;
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
if (input[0] == '&') {
input++;
is_mandatory = TRUE;
if (input[0] == '!') {
input++;
is_inverted = TRUE;
}
goto out;
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
if (input[0] == '|') {
input++;
if (input[0] == '!') {
input++;
is_inverted = TRUE;
}
goto out;
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
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)
{
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
gboolean has_optional = FALSE;
gboolean has_any_optional = FALSE;
guint i;
for (i = 0; i < num_patterns; i++) {
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
gboolean is_inverted;
gboolean is_mandatory;
gboolean match;
const char *p;
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
_pattern_parse(patterns[i], &p, &is_inverted, &is_mandatory);
core: fix crash in nm_wildcard_match_check() It's not entirely clear how to treat %NULL. Clearly "match.interface-name=eth0" should not match with an interface %NULL. But what about "match.interface-name=!eth0"? It's now implemented that negative matches still succeed against %NULL. What about "match.interface-name=*"? That probably should also match with %NULL. So we treat %NULL really like "". Against commit 11cd443448bc ('iwd: Don't call IWD methods when device unmanaged'), we got this backtrace: #0 0x00007f1c164069f1 in __strnlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:62 #1 0x00007f1c1637ac9e in __fnmatch (pattern=<optimized out>, string=<optimized out>, string@entry=0x0, flags=flags@entry=0) at fnmatch.c:379 p = 0x0 res = <optimized out> orig_pattern = <optimized out> n = <optimized out> wpattern = 0x7fff8d860730 L"pci-0000:03:00.0" ps = {__count = 0, __value = {__wch = 0, __wchb = "\000\000\000"}} wpattern_malloc = 0x0 wstring_malloc = 0x0 wstring = <optimized out> alloca_used = 80 __PRETTY_FUNCTION__ = "__fnmatch" #2 0x0000564484a978bf in nm_wildcard_match_check (str=0x0, patterns=<optimized out>, num_patterns=<optimized out>) at src/core/nm-core-utils.c:1959 is_inverted = 0 is_mandatory = 0 match = <optimized out> p = 0x564486c43fa0 "pci-0000:03:00.0" has_optional = 0 has_any_optional = 0 i = <optimized out> #3 0x0000564484bf4797 in check_connection_compatible (self=<optimized out>, connection=<optimized out>, error=0x0) at src/core/devices/nm-device.c:7499 patterns = <optimized out> device_driver = 0x564486c76bd0 "veth" num_patterns = 1 priv = 0x564486cbe0b0 __func__ = "check_connection_compatible" device_iface = <optimized out> local = 0x564486c99a60 conn_iface = 0x0 klass = <optimized out> s_match = 0x564486c63df0 [NMSettingMatch] #4 0x0000564484c38491 in check_connection_compatible (device=0x564486cbe590 [NMDeviceVeth], connection=0x564486c6b160, error=0x0) at src/core/devices/nm-device-ethernet.c:348 self = 0x564486cbe590 [NMDeviceVeth] s_wired = <optimized out> Fixes: 3ced486f4162 ('libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\'') https://bugzilla.redhat.com/show_bug.cgi?id=1942741
2021-03-24 21:05:19 +01:00
match = (fnmatch(p, str ?: "", 0) == 0);
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
if (is_inverted)
match = !match;
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
if (is_mandatory) {
if (!match)
return FALSE;
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
} else {
has_any_optional = TRUE;
if (match)
has_optional = TRUE;
}
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
return has_optional || !has_any_optional;
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
/*****************************************************************************/
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);
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
for (; proc_cmdline[0]; proc_cmdline++) {
const char *c = proc_cmdline[0];
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
if (has_equal) {
/* if pattern contains '=' compare full key=value */
if (nm_streq(c, pattern))
return TRUE;
continue;
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
/* 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)
{
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
gboolean has_optional = FALSE;
gboolean has_any_optional = FALSE;
guint i;
for (i = 0; i < num_patterns; i++) {
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
const char *element = patterns[i];
gboolean is_inverted = FALSE;
gboolean is_mandatory = FALSE;
gboolean match;
const char *p;
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
_pattern_parse(element, &p, &is_inverted, &is_mandatory);
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
match = _kernel_cmdline_match(proc_cmdline, p);
if (is_inverted)
match = !match;
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
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;
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
} else {
has_any_optional = TRUE;
if (match)
has_optional = TRUE;
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
}
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\' For simple matches like match.interface-name, match.driver, and match.path, arguably what we had was fine. There each element (like "eth*") is a wildcard for a single name (like "eth1"). However, for match.kernel-command-line, the elements match individual command line options, so we should have more flexibility of whether a parameter is optional or mandatory. Extend the syntax for that. - the elements can now be prefixed by either '|' or '&'. This makes optional or mandatory elements, respectively. The entire match evaluates to true if all mandatory elements match (if any) and at least one of the optional elements (if any). As before, if neither '|' nor '&' is specified, then the element is optional (that means, "foo" is the same as "|foo"). - the exclamation mark is still used to invert the match. If used alone (like "!foo") it is a shortcut for defining a mandatory match ("&!foo"). - the backslash can now be used to escape the special characters above. Basically, the special characters ('|', '&', '!') are stripped from the start of the element. If what is left afterwards is a backslash, it also gets stripped and the remainder is the pattern. For example, "\\&foo" has the pattern "&foo" where '&' is no longer treated specially. This special handling of the backslash is only done at the beginning of the element (after the optional special characters). The remaining string is part of the pattern, where backslashes might have their own meaning. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
2020-06-23 09:39:48 +02:00
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;
}
/*****************************************************************************/
/**
* 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 if (G_VALUE_HOLDS_VARIANT(&val)) {
s = g_variant_print(g_value_get_variant(&val), FALSE);
} 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 =
&nm_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 =
&nm_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);
}
/*****************************************************************************/
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
typedef struct {
NMUuid bin;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
/* 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->is_fake = is_fake;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
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);
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
} else {
G_STATIC_ASSERT_EXPR(sizeof(uuid_data->str) >= 37);
2021-05-02 10:01:32 +02:00
nm_uuid_unparse(uuid, uuid_data->str);
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
return uuid_data;
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
/*****************************************************************************/
static const UuidData *
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
_machine_id_get(gboolean allow_fake)
{
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
static const UuidData *volatile p_uuid_data;
const UuidData *d;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
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;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
gboolean is_fake = TRUE;
const char *fake_type = NULL;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
NMUuid uuid;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
/* 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)) {
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
g_strstrip(content);
if (nm_utils_hexstr2bin_full(content,
FALSE,
FALSE,
FALSE,
NULL,
16,
(guint8 *) &uuid,
sizeof(uuid),
NULL)) {
if (!nm_uuid_is_null(&uuid)) {
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
/* an all-zero machine-id is not valid. */
is_fake = FALSE;
}
}
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
if (is_fake) {
const guint8 *seed_bin;
const NMUuid *hash_seed;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
gsize seed_len;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
if (!allow_fake) {
/* we don't allow generating (and memorizing) a fake key.
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
* Signal that no valid machine-id exists. */
return NULL;
}
if (nm_utils_host_id_get(&seed_bin, &seed_len)) {
static const NMUuid u =
NM_UUID_INIT(ab, 08, 5f, 06, b6, 29, 46, d1, a5, 53, 84, ee, ba, 56, 83, b6);
/* 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. */
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
fake_type = "secret-key";
hash_seed = &u;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
} else {
static const NMUuid u =
NM_UUID_INIT(7f, f0, c8, f5, 53, 99, 49, 01, ab, 63, 61, bf, 59, 4a, be, 8b);
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
/* 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();
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
seed_len = sizeof(NMUuid);
fake_type = "boot-id";
hash_seed = &u;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
/* the fake machine-id is based on secret-key/boot-id, but we hash it
* again, so that they are not literally the same. */
2021-05-02 21:32:31 +02:00
nm_uuid_generate_from_string(&uuid,
(const char *) seed_bin,
seed_len,
NM_UUID_TYPE_VERSION5,
hash_seed);
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
if (!g_once_init_enter(&lock))
goto again;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
d = _uuid_data_init(&uuid_data, TRUE, is_fake, &uuid);
g_atomic_pointer_set(&p_uuid_data, d);
g_once_init_leave(&lock, 1);
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
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);
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
return d;
}
const char *
nm_utils_machine_id_str(void)
{
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
return _machine_id_get(TRUE)->str;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
}
const NMUuid *
nm_utils_machine_id_bin(void)
{
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
return &_machine_id_get(TRUE)->bin;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
}
gboolean
nm_utils_machine_id_is_fake(void)
{
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
return _machine_id_get(TRUE)->is_fake;
}
/*****************************************************************************/
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
/* 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 timestamp from the past two years, by using a generated timespan
* by hashing the host-id. Yes, this timestamp counts back from @now, and is
* thus 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;
}
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
static const guint8 *
_host_id_hash_v2(const guint8 *seed_arr,
gsize seed_len,
guint8 *out_digest /* 32 bytes (NM_UTILS_CHECKSUM_LENGTH_SHA256) */)
{
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
nm_auto_free_checksum GChecksum *sum = g_checksum_new(G_CHECKSUM_SHA256);
const UuidData *machine_id_data;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
char slen[100];
/* The following snippet generates the same (binary) host-id:
(
stat -c '%s' /var/lib/NetworkManager/secret_key | tr -d '\n';
echo -n ' ';
cat /var/lib/NetworkManager/secret_key;
cat /etc/machine-id | tr -d '\n' | sed -n 's/[a-f0-9-]/\0/pg'
) \
| sha256sum \
| awk '{print $1}' \
| xxd -r -p
*/
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
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)
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
{
#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;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
gsize secret_len;
GError *error = NULL;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
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);
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
} 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);
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
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;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
} 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;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
} 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);
}
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
/* 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;
if (nm_random_get_crypto_bytes(rnd_buf, sizeof(rnd_buf)) < 0)
nm_random_get_bytes_full(rnd_buf, sizeof(rnd_buf), &success);
else
success = TRUE;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
/* 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);
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256;
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
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,
NULL,
&error)) {
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
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);
core: combine secret-key with /etc/machine-id NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
2018-12-05 13:42:33 +01:00
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;
/* The timestamp (in nsec since the Epoch) returned by nm_utils_host_id_get_timestamp_nsec().
* It is associated with the host (and the host-id). We currently use this for the LLT DUID
* generation for IPv6. Instead of persisting the timestamp separately to disk, we re-use the
* file timestamp of the secret_key file. */
gint64 timestamp_nsec;
bool is_good : 1;
bool timestamp_is_good : 1;
} HostIdData;
static const HostIdData *volatile host_id_static;
static const HostIdData *
_host_id_get(void)
{
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_nsec);
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_nsec(void)
{
return _host_id_get()->timestamp_nsec;
}
static GArray *nmtst_host_id_stack = NULL;
static GMutex nmtst_host_id_lock;
const HostIdData *nmtst_host_id_static_0 = NULL;
void
nmtst_utils_host_id_push(const guint8 *host_id,
gssize host_id_len,
gboolean is_good,
const gint64 *p_timestamp_nsec)
{
NM_G_MUTEX_LOCKED(&nmtst_host_id_lock);
gs_free char *str1_to_free = NULL;
HostIdData *h;
g_assert(host_id_len >= -1);
if (host_id_len < 0)
host_id_len = host_id ? strlen((const char *) host_id) : 0;
nm_log_dbg(LOGD_CORE,
"nmtst: host-id push: \"%s\" (%zu), is-good=%d, timestamp=%" G_GINT64_FORMAT "%s",
nm_utils_buf_utf8safe_escape(host_id,
host_id_len,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
&str1_to_free),
(gsize) host_id_len,
!!is_good,
p_timestamp_nsec ? *p_timestamp_nsec : 0,
p_timestamp_nsec ? "" : " (not-good)");
if (!nmtst_host_id_stack) {
nmtst_host_id_stack = g_array_new(FALSE, FALSE, sizeof(HostIdData));
nmtst_host_id_static_0 = g_atomic_pointer_get(&host_id_static);
}
h = nm_g_array_append_new(nmtst_host_id_stack, HostIdData);
*h = (HostIdData){
.host_id = nm_memdup(host_id, host_id_len),
.host_id_len = host_id_len,
.timestamp_nsec = p_timestamp_nsec ? *p_timestamp_nsec : 0,
.is_good = is_good,
.timestamp_is_good = !!p_timestamp_nsec,
};
g_atomic_pointer_set(&host_id_static, h);
}
void
nmtst_utils_host_id_pop(void)
{
NM_G_MUTEX_LOCKED(&nmtst_host_id_lock);
HostIdData *h;
g_assert(nmtst_host_id_stack);
g_assert(nmtst_host_id_stack->len > 0);
nm_log_dbg(LOGD_CORE, "nmtst: host-id pop");
h = &nm_g_array_index(nmtst_host_id_stack, HostIdData, nmtst_host_id_stack->len - 1);
g_free((char *) h->host_id);
g_array_set_size(nmtst_host_id_stack, nmtst_host_id_stack->len - 1u);
if (!g_atomic_pointer_compare_and_exchange(
&host_id_static,
h,
nmtst_host_id_stack->len == 0u ? nmtst_host_id_static_0
: &nm_g_array_last(nmtst_host_id_stack, HostIdData)))
g_assert_not_reached();
}
/*****************************************************************************/
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
static const UuidData *
_boot_id_get(void)
{
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
static const UuidData *volatile p_boot_id;
const UuidData *d;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
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;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
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);
2021-05-02 13:45:53 +02:00
if (!contents || !nm_uuid_parse(nm_strstrip(contents), &uuid)) {
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
/* generate a random UUID instead. */
is_fake = TRUE;
nm_uuid_generate_random(&uuid);
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
if (!g_once_init_enter(&lock))
goto again;
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
d = _uuid_data_init(&boot_id, FALSE, is_fake, &uuid);
g_atomic_pointer_set(&p_boot_id, d);
g_once_init_leave(&lock, 1);
}
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
return d;
}
const char *
nm_utils_boot_id_str(void)
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
{
return _boot_id_get()->str;
}
const NMUuid *
nm_utils_boot_id_bin(void)
core: refactor loading machine-id and cache it Previously, whenever we needed /etc/machine-id we would re-load it from file. The are 3 downsides of that: - the smallest downside is the runtime overhead of repeatedly reading the file and parse it. - as we read it multiple times, it may change anytime. Most code in NetworkManager does not expect or handle a change of the machine-id. Generally, the admin should make sure that the machine-id is properly initialized before NetworkManager starts, and not change it. As such, a change of the machine-id should never happen in practice. But if it would change, we would get odd behaviors. Note for example how generate_duid_from_machine_id() already cached the generated DUID and only read it once. It's better to pick the machine-id once, and rely to use the same one for the remainder of the program. If the admin wants to change the machine-id, NetworkManager must be restarted as well (in case the admin cares). Also, as we now only load it once, it makes sense to log an error (once) when we fail to read the machine-id. - previously, loading the machine-id could fail each time. And we have to somehow handle that error. It seems, the best thing what we anyway can do, is to log an error once and continue with a fake machine-id. Here we add a fake machine-id based on the secret-key or the boot-id. Now obtaining a machine-id can no longer fail and error handling is no longer necessary. Also, ensure that a machine-id of all zeros is not valid. Technically, a machine-id is not an RFC 4122 UUID. But it's the same size, so we also use NMUuid data structure for it. While at it, also refactor caching of the boot-id and the secret key. In particular, fix the thread-safety of the double-checked locking implementations.
2018-10-30 14:07:11 +01:00
{
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;
/* /run/NetworkManager/proc-cmdline can be used to overrule /proc/cmdline. */
if (!g_file_get_contents(NMRUNDIR "/proc-cmdline", &str, NULL, 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.
*/
if (hwaddr_len == INFINIBAND_ALEN) {
memcpy(out_iid->id_u8, hwaddr + INFINIBAND_ALEN - 8, 8);
out_iid->id_u8[0] |= 0x02;
return TRUE;
}
break;
case NM_LINK_TYPE_GRE:
/* Hardware address is the network-endian IPv4 address */
if (hwaddr_len == 4) {
addr = unaligned_read_ne32(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;
}
break;
case NM_LINK_TYPE_6LOWPAN:
/* The hardware address is already 64-bit. This is the case for
* IEEE 802.15.4 networks. */
if (hwaddr_len == sizeof(out_iid->id_u8)) {
memcpy(out_iid->id_u8, hwaddr, sizeof(out_iid->id_u8));
return TRUE;
}
break;
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;
}
/*****************************************************************************/
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
char *
nm_utils_stable_id_random(void)
{
char buf[15];
nm_random_get_bytes(buf, sizeof(buf));
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
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;
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
/* for NM_UTILS_STABLE_TYPE_GENERATED we generate a possibly long string
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
* 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);
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
/* 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) {
/* Would have been nicer to append "=NIL;" to differentiate between
* empty and NULL.
*
* Can't do that now, as it would change behavior. */
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
substitution = "";
}
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
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,
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
const char *bootid,
const char *uuid,
char **out_generated)
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
{
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;
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
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
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
* 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;
}
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
#define CHECK_PREFIX(prefix) \
({ \
gboolean _match = FALSE; \
\
if (NM_STR_HAS_PREFIX(&stable_id[i], "" prefix "")) { \
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
_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);
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
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".
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
* 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.
device: support dynamic "connection.stable-id" in form of text-substitution Usecase: when connecting to a public Wi-Fi with MAC address randomization ("wifi.cloned-mac-address=random") you get on every re-connect a new IP address due to the changing MAC address. "wifi.cloned-mac-address=stable" is the solution for that. But that means, every time when reconnecting to this network, the same ID will be reused. We want an ID that is stable for a while, but at a later point a new ID should e generated when revisiting the Wi-Fi network. Extend the stable-id to become dynamic and support templates/substitutions. Currently supported is "${CONNECTION}", "${BOOT}" and "${RANDOM}". Any unrecognized pattern is treated verbaim/untranslated. "$$" is treated special to allow escaping the '$' character. This allows the user to still embed verbatim '$' characters with the guarantee that future versions of NetworkManager will still generate the same ID. Of course, a user could just avoid '$' in the stable-id unless using it for dynamic substitutions. Later we might want to add more recognized substitutions. For example, it could be useful to generate new IDs based on the current time. The ${} syntax is extendable to support arguments like "${PERIODIC:weekly}". Also allow "connection.stable-id" to be set as global default value. Previously that made no sense because the stable-id was static and is anyway strongly tied to the identity of the connection profile. Now, with dynamic stable-ids it gets much more useful to specify a global default. Note that pre-existing stable-ids don't change and still generate the same addresses -- unless they contain one of the new ${} patterns.
2016-12-18 13:54:26 +01:00
* 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;
}
/*****************************************************************************/
core: avoid generating reserved IPv6 interface identifiers https://tools.ietf.org/html/rfc7217 says: The resulting Interface Identifier SHOULD be compared against the reserved IPv6 Interface Identifiers [RFC5453] [IANA-RESERVED-IID] and against those Interface Identifiers already employed in an address of the same network interface and the same network prefix. In the event that an unacceptable identifier has been generated, this situation SHOULD be handled in the same way as the case of duplicate addresses (see Section 6). In case of conflict, this suggests to create a new address incrementing the DAD counter, etc. Don't do that. If we generate an address of the reserved region, just rehash it right away. Note that the actual address anyway appears random, so this re-hashing is just as good as incrementing the DAD counter and going through the entire process again. Note that now we no longer generate certain addresses like we did previously. But realize that we now merely reject (1 + 16777216 + 128) addresses out of 2^64. So, the likelyhood of of a user accidentally generating an address that is suddenly rejected is in the order of 10e-13 (1 / 1,099,503,173,697). Which is not astronomically, but still extreeeemely unlikely. Also, the whole process is anyway build on the idea that somebody else might generate conflicting addresses (DAD). It means, there was always the extremely tiny chance that the address you generated last time is suddenly taken by somebody else. So, this change appears to a user like these reserved addresses are now claimed by another (non existing) host and a different address gets generated -- business as usual, as far as SLAAC is concerned. (cherry picked from commit f15c4961ad02a1edf766f140dd94e2d5449f8d82)
2017-04-27 12:38:40 +02:00
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;
}
void
nm_utils_ipv6_addr_set_stable_privacy_with_host_id(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)
{
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);
core: avoid generating reserved IPv6 interface identifiers https://tools.ietf.org/html/rfc7217 says: The resulting Interface Identifier SHOULD be compared against the reserved IPv6 Interface Identifiers [RFC5453] [IANA-RESERVED-IID] and against those Interface Identifiers already employed in an address of the same network interface and the same network prefix. In the event that an unacceptable identifier has been generated, this situation SHOULD be handled in the same way as the case of duplicate addresses (see Section 6). In case of conflict, this suggests to create a new address incrementing the DAD counter, etc. Don't do that. If we generate an address of the reserved region, just rehash it right away. Note that the actual address anyway appears random, so this re-hashing is just as good as incrementing the DAD counter and going through the entire process again. Note that now we no longer generate certain addresses like we did previously. But realize that we now merely reject (1 + 16777216 + 128) addresses out of 2^64. So, the likelyhood of of a user accidentally generating an address that is suddenly rejected is in the order of 10e-13 (1 / 1,099,503,173,697). Which is not astronomically, but still extreeeemely unlikely. Also, the whole process is anyway build on the idea that somebody else might generate conflicting addresses (DAD). It means, there was always the extremely tiny chance that the address you generated last time is suddenly taken by somebody else. So, this change appears to a user like these reserved addresses are now claimed by another (non existing) host and a different address gets generated -- business as usual, as far as SLAAC is concerned. (cherry picked from commit f15c4961ad02a1edf766f140dd94e2d5449f8d82)
2017-04-27 12:38:40 +02:00
while (_is_reserved_ipv6_iid(digest)) {
g_checksum_reset(sum);
tmp[0] = htonl(++dad_counter);
g_checksum_update(sum, digest, sizeof(digest));
core: avoid generating reserved IPv6 interface identifiers https://tools.ietf.org/html/rfc7217 says: The resulting Interface Identifier SHOULD be compared against the reserved IPv6 Interface Identifiers [RFC5453] [IANA-RESERVED-IID] and against those Interface Identifiers already employed in an address of the same network interface and the same network prefix. In the event that an unacceptable identifier has been generated, this situation SHOULD be handled in the same way as the case of duplicate addresses (see Section 6). In case of conflict, this suggests to create a new address incrementing the DAD counter, etc. Don't do that. If we generate an address of the reserved region, just rehash it right away. Note that the actual address anyway appears random, so this re-hashing is just as good as incrementing the DAD counter and going through the entire process again. Note that now we no longer generate certain addresses like we did previously. But realize that we now merely reject (1 + 16777216 + 128) addresses out of 2^64. So, the likelyhood of of a user accidentally generating an address that is suddenly rejected is in the order of 10e-13 (1 / 1,099,503,173,697). Which is not astronomically, but still extreeeemely unlikely. Also, the whole process is anyway build on the idea that somebody else might generate conflicting addresses (DAD). It means, there was always the extremely tiny chance that the address you generated last time is suddenly taken by somebody else. So, this change appears to a user like these reserved addresses are now claimed by another (non existing) host and a different address gets generated -- business as usual, as far as SLAAC is concerned. (cherry picked from commit f15c4961ad02a1edf766f140dd94e2d5449f8d82)
2017-04-27 12:38:40 +02:00
g_checksum_update(sum, (const guchar *) &tmp[0], sizeof(tmp[0]));
nm_utils_checksum_get_digest(sum, digest);
core: avoid generating reserved IPv6 interface identifiers https://tools.ietf.org/html/rfc7217 says: The resulting Interface Identifier SHOULD be compared against the reserved IPv6 Interface Identifiers [RFC5453] [IANA-RESERVED-IID] and against those Interface Identifiers already employed in an address of the same network interface and the same network prefix. In the event that an unacceptable identifier has been generated, this situation SHOULD be handled in the same way as the case of duplicate addresses (see Section 6). In case of conflict, this suggests to create a new address incrementing the DAD counter, etc. Don't do that. If we generate an address of the reserved region, just rehash it right away. Note that the actual address anyway appears random, so this re-hashing is just as good as incrementing the DAD counter and going through the entire process again. Note that now we no longer generate certain addresses like we did previously. But realize that we now merely reject (1 + 16777216 + 128) addresses out of 2^64. So, the likelyhood of of a user accidentally generating an address that is suddenly rejected is in the order of 10e-13 (1 / 1,099,503,173,697). Which is not astronomically, but still extreeeemely unlikely. Also, the whole process is anyway build on the idea that somebody else might generate conflicting addresses (DAD). It means, there was always the extremely tiny chance that the address you generated last time is suddenly taken by somebody else. So, this change appears to a user like these reserved addresses are now claimed by another (non existing) host and a different address gets generated -- business as usual, as far as SLAAC is concerned. (cherry picked from commit f15c4961ad02a1edf766f140dd94e2d5449f8d82)
2017-04-27 12:38:40 +02:00
}
memcpy(addr->s6_addr + 8, &digest[0], 8);
}
void
nm_utils_ipv6_addr_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;
nm_utils_host_id_get(&host_id, &host_id_len);
nm_utils_ipv6_addr_set_stable_privacy_with_host_id(stable_type,
addr,
ifname,
network_id,
dad_counter,
host_id,
host_id_len);
}
/**
* 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_may_fail(NMUtilsStableType stable_type,
struct in6_addr *addr,
const char *ifname,
const char *network_id,
guint32 dad_counter,
GError **error)
{
g_return_val_if_fail(network_id, FALSE);
if (dad_counter >= NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES) {
g_set_error_literal(error,
NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"Too many DAD collisions");
return FALSE;
}
nm_utils_ipv6_addr_set_stable_privacy(stable_type, addr, ifname, network_id, dad_counter);
return TRUE;
}
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
/*****************************************************************************/
static void
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
_hw_addr_eth_complete(struct ether_addr *addr,
const char *current_mac_address,
const char *generate_mac_address_mask)
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
{
struct ether_addr mask;
struct ether_addr oui;
struct ether_addr *ouis;
gsize ouis_len;
guint i;
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
/* the second LSB of the first octet means
* "globally unique, OUI enforced, BIA (burned-in-address)"
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
* 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;
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
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;
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
nm_assert((ouis == NULL) ^ (ouis_len != 0));
if (ouis) {
oui = ouis[nm_random_u64_range(ouis_len)];
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
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;
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
}
char *
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
nm_utils_hw_addr_gen_random_eth(const char *current_mac_address,
const char *generate_mac_address_mask)
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
{
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
struct ether_addr bin_addr;
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
nm_random_get_bytes(&bin_addr, ETH_ALEN);
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
_hw_addr_eth_complete(&bin_addr, current_mac_address, generate_mac_address_mask);
return nm_utils_hwaddr_ntoa(&bin_addr, ETH_ALEN);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
}
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)
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
{
nm_auto_free_checksum GChecksum *sum = NULL;
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
guint32 tmp;
guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA256];
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
struct ether_addr bin_addr;
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
guint8 stable_type_uint8;
nm_assert(stable_id);
nm_assert(host_id);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
sum = g_checksum_new(G_CHECKSUM_SHA256);
host_id_len = MIN(host_id_len, G_MAXUINT32);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
nm_assert(stable_type < (NMUtilsStableType) 255);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
stable_type_uint8 = stable_type;
g_checksum_update(sum, (const guchar *) &stable_type_uint8, sizeof(stable_type_uint8));
tmp = htonl((guint32) host_id_len);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
g_checksum_update(sum, (const guchar *) &tmp, sizeof(tmp));
g_checksum_update(sum, (const guchar *) host_id, host_id_len);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
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);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
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);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
}
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);
}
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
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)
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
{
const guint8 *host_id;
gsize host_id_len;
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
g_return_val_if_fail(stable_id, NULL);
nm_utils_host_id_get(&host_id, &host_id_len);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
return _hw_addr_gen_stable_eth(stable_type,
stable_id,
host_id,
host_id_len,
all: make MAC address randomization algorithm configurable For the per-connection settings "ethernet.cloned-mac-address" and "wifi.cloned-mac-address", and for the per-device setting "wifi.scan-rand-mac-address", we may generate MAC addresses using either the "random" or "stable" algorithm. Add new properties "generate-mac-address-mask" that allow to configure which bits of the MAC address will be scrambled. By default, the "random" and "stable" algorithms scamble all bits of the MAC address, including the OUI part and generate a locally- administered, unicast address. By specifying a MAC address mask, we can now configure to perserve parts of the current MAC address of the device. For example, setting "FF:FF:FF:00:00:00" will preserve the first 3 octects of the current MAC address. One can also explicitly specify a MAC address to use instead of the current MAC address. For example, "FF:FF:FF:00:00:00 68:F7:28:00:00:00" sets the OUI part of the MAC address to "68:F7:28" while scrambling the last 3 octects. Similarly, "02:00:00:00:00:00 00:00:00:00:00:00" will scamble all bits of the MAC address, except clearing the second-least significant bit. Thus, creating a burned-in address, globally administered. One can also supply a list of MAC addresses like "FF:FF:FF:00:00:00 68:F7:28:00:00:00 00:0C:29:00:00:00 ..." in which case a MAC address is choosen randomly. To fully scamble the MAC address one can configure "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00". which also randomly creates either a locally or globally administered address. With this, the following macchanger options can be implemented: `macchanger --random` This is the default if no mask is configured. -> "" while is the same as: -> "00:00:00:00:00:00" -> "02:00:00:00:00:00 02:00:00:00:00:00" `macchanger --random --bia` -> "02:00:00:00:00:00 00:00:00:00:00:00" `macchanger --ending` This option cannot be fully implemented, because macchanger uses the current MAC address but also implies --bia. -> "FF:FF:FF:00:00:00" This would yields the same result only if the current MAC address is already a burned-in address too. Otherwise, it has not the same effect as --ending. -> "FF:FF:FF:00:00:00 <MAC_ADDR>" Alternatively, instead of using the current MAC address, spell the OUI part out. But again, that is not really the same as macchanger does because you explictly have to name the OUI part to use. `machanger --another` `machanger --another_any` -> "FF:FF:FF:00:00:00 <MAC_ADDR> <MAC_ADDR> ..." "$(printf "FF:FF:FF:00:00:00 %s\n" "$(sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) \([0-9a-fA-F][0-9a-fA-F]\) .*/\1:\2:\3:00:00:00/p' /usr/share/macchanger/wireless.list | xargs)")"
2016-06-22 20:31:39 +02:00
ifname,
current_mac_address,
generate_mac_address_mask);
device: extend MAC address handling including randomization for ethernet and wifi Extend the "ethernet.cloned-mac-address" and "wifi.cloned-mac-address" settings. Instead of specifying an explicit MAC address, the additional special values "permanent", "preserve", "random", "random-bia", "stable" and "stable-bia" are supported. "permanent" means to use the permanent hardware address. Previously that was the default if no explict cloned-mac-address was set. The default is thus still "permanent", but it can be overwritten by global configuration. "preserve" means not to configure the MAC address when activating the device. That was actually the default behavior before introducing MAC address handling with commit 1b49f941a69af910b0e68530be7339e8053068e5. "random" and "random-bia" use a randomized MAC address for each connection. "stable" and "stable-bia" use a generated, stable address based on some token. The "bia" suffix says to generate a burned-in address. The stable method by default uses as token the connection UUID, but the token can be explicitly choosen via "stable:<TOKEN>" and "stable-bia:<TOKEN>". On a D-Bus level, the "cloned-mac-address" is a bytestring and thus cannot express the new forms. It is replaced by the new "assigned-mac-address" field. For the GObject property, libnm's API, nmcli, keyfile, etc. the old name "cloned-mac-address" is still used. Deprecating the old field seems more complicated then just extending the use of the existing "cloned-mac-address" field, although the name doesn't match well with the extended meaning. There is some overlap with the "wifi.mac-address-randomization" setting. https://bugzilla.gnome.org/show_bug.cgi?id=705545 https://bugzilla.gnome.org/show_bug.cgi?id=708820 https://bugzilla.gnome.org/show_bug.cgi?id=758301
2016-05-24 15:57:16 +02:00
}
/*****************************************************************************/
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;
}
}
GBytes *
nm_utils_dhcp_client_id_duid(guint32 iaid, const guint8 *duid, gsize duid_len)
{
struct _nm_packed {
guint8 type;
guint32 iaid;
guint8 duid[];
} *client_id;
gsize total_size;
/* the @duid must include the 16 bit duid-type and the data (of max 128 bytes). */
g_return_val_if_fail(duid_len > 2 && duid_len < 128 + 2, NULL);
g_return_val_if_fail(duid, NULL);
total_size = sizeof(*client_id) + duid_len;
client_id = g_malloc(total_size);
client_id->type = 255;
unaligned_write_be32(&client_id->iaid, iaid);
memcpy(client_id->duid, duid, duid_len);
return g_bytes_new_take(client_id, total_size);
}
/**
* 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);
}
/*****************************************************************************/
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_ip_4:
* @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_ip_4(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_strbuf_append(&s, &len, "%u.", p[i - 1] & 0xff);
nm_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_ip_6:
* @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_ip_6(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));
glib-aux: rename IP address related helpers from "nm-inet-utils.h" - name things related to `in_addr_t`, `struct in6_addr`, `NMIPAddr` as `nm_ip4_addr_*()`, `nm_ip6_addr_*()`, `nm_ip_addr_*()`, respectively. - we have a wrapper `nm_inet_ntop()` for `inet_ntop()`. This name of our wrapper is chosen to be familiar with the libc underlying function. With this, also name functions that are about string representations of addresses `nm_inet_*()`, `nm_inet4_*()`, `nm_inet6_*()`. For example, `nm_inet_parse_str()`, `nm_inet_is_normalized()`. <<<< R() { git grep -l "$1" | xargs sed -i "s/\<$1\>/$2/g" } R NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX NM_CMP_DIRECT_IP4_ADDR_SAME_PREFIX R NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX NM_CMP_DIRECT_IP6_ADDR_SAME_PREFIX R NM_UTILS_INET_ADDRSTRLEN NM_INET_ADDRSTRLEN R _nm_utils_inet4_ntop nm_inet4_ntop R _nm_utils_inet6_ntop nm_inet6_ntop R _nm_utils_ip4_get_default_prefix nm_ip4_addr_get_default_prefix R _nm_utils_ip4_get_default_prefix0 nm_ip4_addr_get_default_prefix0 R _nm_utils_ip4_netmask_to_prefix nm_ip4_addr_netmask_to_prefix R _nm_utils_ip4_prefix_to_netmask nm_ip4_addr_netmask_from_prefix R nm_utils_inet4_ntop_dup nm_inet4_ntop_dup R nm_utils_inet6_ntop_dup nm_inet6_ntop_dup R nm_utils_inet_ntop nm_inet_ntop R nm_utils_inet_ntop_dup nm_inet_ntop_dup R nm_utils_ip4_address_clear_host_address nm_ip4_addr_clear_host_address R nm_utils_ip4_address_is_link_local nm_ip4_addr_is_link_local R nm_utils_ip4_address_is_loopback nm_ip4_addr_is_loopback R nm_utils_ip4_address_is_zeronet nm_ip4_addr_is_zeronet R nm_utils_ip4_address_same_prefix nm_ip4_addr_same_prefix R nm_utils_ip4_address_same_prefix_cmp nm_ip4_addr_same_prefix_cmp R nm_utils_ip6_address_clear_host_address nm_ip6_addr_clear_host_address R nm_utils_ip6_address_same_prefix nm_ip6_addr_same_prefix R nm_utils_ip6_address_same_prefix_cmp nm_ip6_addr_same_prefix_cmp R nm_utils_ip6_is_ula nm_ip6_addr_is_ula R nm_utils_ip_address_same_prefix nm_ip_addr_same_prefix R nm_utils_ip_address_same_prefix_cmp nm_ip_addr_same_prefix_cmp R nm_utils_ip_is_site_local nm_ip_addr_is_site_local R nm_utils_ipaddr_is_normalized nm_inet_is_normalized R nm_utils_ipaddr_is_valid nm_inet_is_valid R nm_utils_ipx_address_clear_host_address nm_ip_addr_clear_host_address R nm_utils_parse_inaddr nm_inet_parse_str R nm_utils_parse_inaddr_bin nm_inet_parse_bin R nm_utils_parse_inaddr_bin_full nm_inet_parse_bin_full R nm_utils_parse_inaddr_prefix nm_inet_parse_with_prefix_str R nm_utils_parse_inaddr_prefix_bin nm_inet_parse_with_prefix_bin R test_nm_utils_ip6_address_same_prefix test_nm_ip_addr_same_prefix ./contrib/scripts/nm-code-format.sh -F
2022-08-19 13:15:20 +02:00
nm_ip6_addr_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_strbuf_append(&s, &len, "%x.", (addr.s6_addr[j / 2] >> N_SHIFT(j)) & 0xf);
nm_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] = nm_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 */
{
/* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nct/ */
NM_SET_OUT(out_metered, (bytes[4] > 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_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"), );
/*****************************************************************************/
typedef struct {
GPid pid;
GTask *task;
gulong cancellable_id;
GSource *child_watch_source;
GSource *timeout_source;
int child_stdin;
int child_stdout;
int child_stderr;
GSource *input_source;
GSource *output_source;
GSource *error_source;
NMStrBuf in_buffer;
NMStrBuf out_buffer;
NMStrBuf err_buffer;
gsize out_buffer_offset;
} HelperInfo;
#define _NMLOG2_PREFIX_NAME "nm-daemon-helper"
#define _NMLOG2_DOMAIN LOGD_CORE
#define _NMLOG2(level, info, ...) \
G_STMT_START \
{ \
if (nm_logging_enabled((level), (_NMLOG2_DOMAIN))) { \
HelperInfo *_info = (info); \
\
_nm_log((level), \
(_NMLOG2_DOMAIN), \
0, \
NULL, \
NULL, \
_NMLOG2_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \
",%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
NM_HASH_OBFUSCATE_PTR(_info), \
_info->pid _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
static void
helper_info_free(gpointer data)
{
HelperInfo *info = data;
nm_clear_g_source_inst(&info->child_watch_source);
nm_clear_g_source_inst(&info->timeout_source);
g_object_unref(info->task);
nm_str_buf_destroy(&info->in_buffer);
nm_str_buf_destroy(&info->out_buffer);
nm_str_buf_destroy(&info->err_buffer);
nm_clear_g_source_inst(&info->input_source);
nm_clear_g_source_inst(&info->output_source);
nm_clear_g_source_inst(&info->error_source);
nm_clear_fd(&info->child_stdout);
nm_clear_fd(&info->child_stdin);
nm_clear_fd(&info->child_stderr);
if (info->pid != -1) {
nm_assert(info->pid > 1);
nm_utils_kill_child_async(info->pid, SIGKILL, LOGD_CORE, "nm-daemon-helper", 0, NULL, NULL);
}
g_free(info);
}
static void
helper_complete(HelperInfo *info, GError *error)
{
if (error) {
if (info->err_buffer.len > 0) {
_LOG2T(info, "stderr: %s", nm_str_buf_get_str(&info->err_buffer));
}
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task),
&info->cancellable_id);
g_task_return_error(info->task, error);
helper_info_free(info);
return;
}
if (info->input_source || info->output_source || info->pid != -1) {
/* Wait that pipes are closed and process has terminated */
return;
}
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id);
g_task_return_pointer(info->task, nm_str_buf_finalize(&info->in_buffer, NULL), g_free);
helper_info_free(info);
}
static gboolean
helper_can_write(int fd, GIOCondition condition, gpointer user_data)
{
HelperInfo *info = user_data;
gssize n_written;
int errsv;
if (NM_FLAGS_HAS(condition, G_IO_ERR)) {
errsv = EIO;
goto out_error;
} else if (NM_FLAGS_HAS(condition, G_IO_HUP)) {
errsv = EPIPE;
goto out_error;
}
n_written = write(info->child_stdin,
&((nm_str_buf_get_str_unsafe(&info->out_buffer))[info->out_buffer_offset]),
info->out_buffer.len - info->out_buffer_offset);
errsv = errno;
if (n_written < 0 && errsv != EAGAIN)
goto out_error;
if (n_written > 0) {
if ((gsize) n_written >= (info->out_buffer.len - info->out_buffer_offset)) {
nm_assert((gsize) n_written == (info->out_buffer.len - info->out_buffer_offset));
nm_clear_g_source_inst(&info->output_source);
nm_close(info->child_stdin);
info->child_stdin = -1;
return G_SOURCE_CONTINUE;
}
info->out_buffer_offset += (gsize) n_written;
}
return G_SOURCE_CONTINUE;
out_error:
nm_clear_g_source_inst(&info->output_source);
helper_complete(info,
g_error_new(NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"error writing to helper: %d (%s)",
errsv,
nm_strerror_native(errsv)));
return G_SOURCE_CONTINUE;
}
static gboolean
helper_have_data(int fd, GIOCondition condition, gpointer user_data)
{
HelperInfo *info = user_data;
gssize n_read;
GError *error = NULL;
n_read = nm_utils_fd_read(fd, &info->in_buffer);
_LOG2T(info, "read returns %ld", (long) n_read);
if (n_read > 0)
return G_SOURCE_CONTINUE;
nm_clear_g_source_inst(&info->input_source);
nm_clear_fd(&info->child_stdout);
_LOG2T(info, "stdout closed");
if (n_read < 0) {
error = g_error_new(NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"read from process returned %d (%s)",
(int) -n_read,
nm_strerror_native((int) -n_read));
}
helper_complete(info, error);
return G_SOURCE_CONTINUE;
}
static gboolean
helper_have_err_data(int fd, GIOCondition condition, gpointer user_data)
{
HelperInfo *info = user_data;
gssize n_read;
n_read = nm_utils_fd_read(fd, &info->err_buffer);
if (n_read > 0)
return G_SOURCE_CONTINUE;
nm_clear_g_source_inst(&info->error_source);
nm_clear_fd(&info->child_stderr);
return G_SOURCE_CONTINUE;
}
static void
helper_child_terminated(GPid pid, int status, gpointer user_data)
{
HelperInfo *info = user_data;
GError *error = NULL;
gs_free char *status_desc = NULL;
_LOG2D(info, "process %s", (status_desc = nm_utils_get_process_exit_status_desc(status)));
info->pid = -1;
nm_clear_g_source_inst(&info->child_watch_source);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
if (!status_desc)
status_desc = nm_utils_get_process_exit_status_desc(status);
error =
g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc);
}
helper_complete(info, error);
}
static gboolean
helper_timeout(gpointer user_data)
{
HelperInfo *info = user_data;
nm_clear_g_source_inst(&info->timeout_source);
helper_complete(info, g_error_new_literal(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "timed out"));
return G_SOURCE_CONTINUE;
}
static void
helper_cancelled(GObject *object, gpointer user_data)
{
HelperInfo *info = user_data;
GError *error = NULL;
nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id);
nm_utils_error_set_cancelled(&error, FALSE, NULL);
helper_complete(info, error);
}
void
nm_utils_spawn_helper(const char *const *args,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer cb_data)
{
gs_free_error GError *error = NULL;
gs_free char *commands = NULL;
HelperInfo *info;
int fd_flags;
const char *const *arg;
GMainContext *context;
nm_assert(args && args[0]);
info = g_new(HelperInfo, 1);
*info = (HelperInfo){
.task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data),
.child_stdin = -1,
.child_stdout = -1,
.pid = -1,
};
if (!g_spawn_async_with_pipes("/",
(char **) NM_MAKE_STRV(LIBEXECDIR "/nm-daemon-helper"),
(char **) NM_MAKE_STRV(),
G_SPAWN_DO_NOT_REAP_CHILD,
NULL,
NULL,
&info->pid,
&info->child_stdin,
&info->child_stdout,
&info->child_stderr,
&error)) {
info->child_stdin = -1;
info->child_stdout = -1;
info->child_stderr = -1;
info->pid = -1;
g_task_return_error(info->task,
g_error_new(NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"error spawning nm-helper: %s",
error->message));
helper_info_free(info);
return;
}
_LOG2D(info, "spawned process with args: %s", (commands = g_strjoinv(" ", (char **) args)));
context = g_task_get_context(info->task);
/* The async function makes a lukewarm attempt to honor the current thread default
* context. However, it later uses nm_utils_kill_child_async() which always uses
* g_main_context_default(). For now, the function really can only be used with the
* main context. */
nm_assert(context == g_main_context_default());
info->child_watch_source = g_child_watch_source_new(info->pid);
g_source_set_callback(info->child_watch_source,
G_SOURCE_FUNC(helper_child_terminated),
info,
NULL);
g_source_attach(info->child_watch_source, context);
info->timeout_source =
nm_g_timeout_source_new_seconds(20, G_PRIORITY_DEFAULT, helper_timeout, info, NULL);
g_source_attach(info->timeout_source, context);
/* Set file descriptors as non-blocking */
fd_flags = fcntl(info->child_stdin, F_GETFD, 0);
fcntl(info->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
fd_flags = fcntl(info->child_stdout, F_GETFD, 0);
fcntl(info->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
fd_flags = fcntl(info->child_stderr, F_GETFD, 0);
fcntl(info->child_stderr, F_SETFL, fd_flags | O_NONBLOCK);
/* Watch process stdin */
info->out_buffer = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, TRUE);
for (arg = args; *arg; arg++) {
nm_str_buf_append(&info->out_buffer, *arg);
nm_str_buf_append_c(&info->out_buffer, '\0');
}
info->output_source = nm_g_unix_fd_source_new(info->child_stdin,
G_IO_OUT | G_IO_ERR | G_IO_HUP,
G_PRIORITY_DEFAULT,
helper_can_write,
info,
NULL);
g_source_attach(info->output_source, context);
/* Watch process stdout */
info->in_buffer = NM_STR_BUF_INIT(0, FALSE);
info->input_source = nm_g_unix_fd_source_new(info->child_stdout,
G_IO_IN | G_IO_ERR | G_IO_HUP,
G_PRIORITY_DEFAULT,
helper_have_data,
info,
NULL);
g_source_attach(info->input_source, context);
/* Watch process stderr */
info->err_buffer = NM_STR_BUF_INIT(0, FALSE);
info->error_source = nm_g_unix_fd_source_new(info->child_stderr,
G_IO_IN | G_IO_ERR | G_IO_HUP,
G_PRIORITY_DEFAULT,
helper_have_err_data,
info,
NULL);
g_source_attach(info->error_source, context);
if (cancellable) {
gulong signal_id;
signal_id = g_cancellable_connect(cancellable, G_CALLBACK(helper_cancelled), info, NULL);
if (signal_id == 0) {
/* the request is already cancelled. Return. */
return;
}
info->cancellable_id = signal_id;
}
}
char *
nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error)
{
GTask *task = G_TASK(result);
nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_spawn_helper));
return g_task_propagate_pointer(task, error);
}
/*****************************************************************************/
/**
* nm_utils_get_nm_uid:
*
* Checks what EUID NetworkManager is running as.
* Saves the EUID so it can be reused without making many syscalls.
*/
uid_t
nm_utils_get_nm_uid(void)
{
static int u_static = -1;
int u;
again:
if ((u = g_atomic_int_get(&u_static)) == -1) {
uid_t u2;
u2 = geteuid();
u = u2;
nm_assert(u == u2);
nm_assert(u >= 0);
if (!g_atomic_int_compare_and_exchange(&u_static, -1, u))
goto again;
}
return u;
}
/**
* nm_utils_get_nm_gid:
*
* Checks what EGID NetworkManager is running as.
* Saves the EGID so it can be reused without making many syscalls.
*/
gid_t
nm_utils_get_nm_gid(void)
{
static int g_static = -1;
int g;
again:
if ((g = g_atomic_int_get(&g_static)) == -1) {
gid_t g2;
g2 = geteuid();
g = g2;
nm_assert(g == g2);
nm_assert(g >= 0);
if (!g_atomic_int_compare_and_exchange(&g_static, -1, g))
goto again;
}
return g;
}
/*****************************************************************************/
/**
* nm_utils_shorten_hostname:
* @hostname: the input hostname
* @shortened: (out) (transfer full): on return, the shortened hostname
*
* Checks whether the input hostname is valid. If not, tries to shorten it
* to HOST_NAME_MAX (64) or to the first dot, whatever comes earlier.
* The new hostname is returned in @shortened.
*
* Returns: %TRUE if the input hostname was already valid or if was shortened
* successfully; %FALSE otherwise
*/
gboolean
nm_utils_shorten_hostname(const char *hostname, char **shortened)
{
gs_free char *s = NULL;
const char *dot;
gsize l;
nm_assert(hostname);
nm_assert(shortened);
if (nm_hostname_is_valid(hostname, FALSE)) {
*shortened = NULL;
return TRUE;
}
dot = strchr(hostname, '.');
if (dot)
l = (dot - hostname);
else
l = strlen(hostname);
l = MIN(l, (gsize) NM_HOST_NAME_MAX);
s = g_strndup(hostname, l);
if (!nm_hostname_is_valid(s, FALSE)) {
*shortened = NULL;
return FALSE;
}
*shortened = g_steal_pointer(&s);
return TRUE;
}