NetworkManager/src/nm-checkpoint.c
Beniamino Galvani ddeef464af checkpoint: introduce new flags to better restore previous state
When a global checkpoint is created (one with empty device list) we
save the status of all devices to restore it later. After the
checkpoint new interfaces and connections may appear and they can
significantly influence the overall networking status, but we don't
consider them at the moment.

Introduce a new flag DELETE_NEW_CONNECTIONS to delete any connection
added after the checkpoint and similarly a DISCONNECT_NEW_DEVICES to
ensure that the connection active on newly appeared devices doesn't
disrupt network connectivity.

https://bugzilla.redhat.com/show_bug.cgi?id=1378393
2016-10-24 09:57:18 +02:00

487 lines
16 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2016 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-checkpoint.h"
#include <string.h>
#include "nm-auth-subject.h"
#include "nm-core-utils.h"
#include "nm-dbus-interface.h"
#include "nm-device.h"
#include "nm-manager.h"
#include "nm-settings.h"
#include "nm-settings-connection.h"
#include "nm-simple-connection.h"
#include "nm-utils.h"
#include "nmdbus-checkpoint.h"
/*****************************************************************************/
typedef struct {
char *original_dev_path;
NMDevice *device;
NMConnection *applied_connection;
NMConnection *settings_connection;
NMDeviceState state;
bool realized:1;
bool unmanaged_explicit:1;
} DeviceCheckpoint;
NM_GOBJECT_PROPERTIES_DEFINE_BASE (
PROP_DEVICES,
PROP_CREATED,
PROP_ROLLBACK_TIMEOUT,
);
typedef struct {
/* properties */
GHashTable *devices;
gint64 created;
guint32 rollback_timeout;
/* private members */
NMManager *manager;
gint64 rollback_ts;
NMCheckpointCreateFlags flags;
GHashTable *connection_uuids;
} NMCheckpointPrivate;
struct _NMCheckpoint {
NMExportedObject parent;
NMCheckpointPrivate _priv;
};
struct _NMCheckpointClass {
NMExportedObjectClass parent;
};
G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_EXPORTED_OBJECT)
#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMCheckpoint, NM_IS_CHECKPOINT)
/*****************************************************************************/
#define _NMLOG_PREFIX_NAME "checkpoint"
#define _NMLOG_DOMAIN LOGD_CORE
#define _NMLOG(level, ...) \
G_STMT_START { \
if (nm_logging_enabled (level, _NMLOG_DOMAIN)) { \
char __prefix[32]; \
\
if (self) \
g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", ""_NMLOG_PREFIX_NAME"", (self)); \
else \
g_strlcpy (__prefix, _NMLOG_PREFIX_NAME, sizeof (__prefix)); \
_nm_log ((level), (_NMLOG_DOMAIN), 0, \
"%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
__prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} G_STMT_END
/*****************************************************************************/
guint64
nm_checkpoint_get_rollback_ts (NMCheckpoint *self)
{
g_return_val_if_fail (NM_IS_CHECKPOINT (self), 0);
return NM_CHECKPOINT_GET_PRIVATE (self)->rollback_ts;
}
gboolean
nm_checkpoint_includes_device (NMCheckpoint *self, NMDevice *device)
{
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
return g_hash_table_contains (priv->devices, device);
}
GVariant *
nm_checkpoint_rollback (NMCheckpoint *self)
{
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
DeviceCheckpoint *dev_checkpoint;
GHashTableIter iter;
NMSettingsConnection *connection;
NMDevice *device;
GError *local_error = NULL;
GVariantBuilder builder;
_LOGI ("rollback of %s", nm_exported_object_get_path ((NMExportedObject *) self));
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{su}"));
/* Start rolling-back each device */
g_hash_table_iter_init (&iter, priv->devices);
while (g_hash_table_iter_next (&iter, (gpointer *) &device, (gpointer *) &dev_checkpoint)) {
gs_unref_object NMAuthSubject *subject = NULL;
guint32 result = NM_ROLLBACK_RESULT_OK;
const char *con_uuid;
_LOGD ("rollback: restoring device %s (state %d, realized %d, explicitly unmanaged %d)",
nm_device_get_iface (device),
(int) dev_checkpoint->state,
dev_checkpoint->realized,
dev_checkpoint->unmanaged_explicit);
if (nm_device_is_real (device)) {
if (!dev_checkpoint->realized) {
_LOGD ("rollback: device was not realized, unmanage it");
nm_device_set_unmanaged_by_flags_queue (device,
NM_UNMANAGED_USER_EXPLICIT,
TRUE,
NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
goto next_dev;
}
} else {
if (dev_checkpoint->realized) {
if (nm_device_is_software (device)) {
/* try to recreate software device */
_LOGD ("rollback: software device not realized, will re-activate");
goto activate;
} else {
_LOGD ("rollback: device is not realized");
result = NM_ROLLBACK_RESULT_ERR_FAILED;
}
}
goto next_dev;
}
activate:
if (dev_checkpoint->state == NM_DEVICE_STATE_UNMANAGED) {
if ( nm_device_get_state (device) != NM_DEVICE_STATE_UNMANAGED
|| dev_checkpoint->unmanaged_explicit) {
_LOGD ("rollback: explicitly unmanage device");
nm_device_set_unmanaged_by_flags_queue (device,
NM_UNMANAGED_USER_EXPLICIT,
TRUE,
NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
}
goto next_dev;
}
if (dev_checkpoint->applied_connection) {
/* The device had an active connection, check if the
* connection still exists
* */
con_uuid = nm_connection_get_uuid (dev_checkpoint->settings_connection);
connection = nm_settings_get_connection_by_uuid (nm_settings_get (), con_uuid);
if (connection) {
/* If the connection is still there, restore its content
* and save it
* */
_LOGD ("rollback: connection %s still exists", con_uuid);
nm_connection_replace_settings_from_connection (NM_CONNECTION (connection),
dev_checkpoint->settings_connection);
nm_settings_connection_commit_changes (connection,
NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
NULL,
NULL);
} else {
/* The connection was deleted, recreate it */
_LOGD ("rollback: adding connection %s again", con_uuid);
connection = nm_settings_add_connection (nm_settings_get (),
dev_checkpoint->settings_connection,
TRUE,
&local_error);
if (!connection) {
_LOGD ("rollback: connection add failure: %s", local_error->message);
g_clear_error (&local_error);
result = NM_ROLLBACK_RESULT_ERR_FAILED;
goto next_dev;
}
}
/* Now re-activate the connection */
subject = nm_auth_subject_new_internal ();
if (!nm_manager_activate_connection (priv->manager,
connection,
dev_checkpoint->applied_connection,
NULL,
device,
subject,
&local_error)) {
_LOGW ("rollback: reactivation of connection %s/%s failed: %s",
nm_connection_get_id ((NMConnection *) connection),
nm_connection_get_uuid ((NMConnection * ) connection),
local_error->message);
g_clear_error (&local_error);
result = NM_ROLLBACK_RESULT_ERR_FAILED;
goto next_dev;
}
} else {
/* The device was initially disconnected, deactivate any existing connection */
_LOGD ("rollback: disconnecting device");
if ( nm_device_get_state (device) > NM_DEVICE_STATE_DISCONNECTED
&& nm_device_get_state (device) < NM_DEVICE_STATE_DEACTIVATING) {
nm_device_state_changed (device,
NM_DEVICE_STATE_DEACTIVATING,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
}
}
next_dev:
g_variant_builder_add (&builder, "{su}", dev_checkpoint->original_dev_path, result);
}
if (NM_FLAGS_HAS (priv->flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
NMSettingsConnection *con;
gs_free_slist GSList *list = NULL;
GSList *item;
g_return_val_if_fail (priv->connection_uuids, NULL);
list = nm_settings_get_connections_sorted (nm_settings_get ());
for (item = list; item; item = g_slist_next (item)) {
con = item->data;
if (!g_hash_table_contains (priv->connection_uuids,
nm_settings_connection_get_uuid (con))) {
_LOGD ("rollback: deleting new connection %s (%s)",
nm_settings_connection_get_uuid (con),
nm_settings_connection_get_id (con));
nm_settings_connection_delete (con, NULL, NULL);
}
}
}
if (NM_FLAGS_HAS (priv->flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) {
const GSList *list = nm_manager_get_devices (priv->manager);
NMDeviceState state;
NMDevice *dev;
for (list = nm_manager_get_devices (priv->manager); list ; list = g_slist_next (list)) {
dev = list->data;
if (!g_hash_table_contains (priv->devices, dev)) {
state = nm_device_get_state (dev);
if ( state > NM_DEVICE_STATE_DISCONNECTED
&& state < NM_DEVICE_STATE_DEACTIVATING) {
_LOGD ("rollback: disconnecting new device %s", nm_device_get_iface (dev));
nm_device_state_changed (dev,
NM_DEVICE_STATE_DEACTIVATING,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
}
}
}
}
return g_variant_new ("(a{su})", &builder);
}
static DeviceCheckpoint *
device_checkpoint_create (NMDevice *device,
GError **error)
{
DeviceCheckpoint *dev_checkpoint;
NMConnection *applied_connection;
NMSettingsConnection *settings_connection;
const char *path;
gboolean unmanaged_explicit;
path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (device));
unmanaged_explicit = !!nm_device_get_unmanaged_flags (device,
NM_UNMANAGED_USER_EXPLICIT);
dev_checkpoint = g_slice_new0 (DeviceCheckpoint);
dev_checkpoint->device = g_object_ref (device);
dev_checkpoint->original_dev_path = g_strdup (path);
dev_checkpoint->state = nm_device_get_state (device);
dev_checkpoint->realized = nm_device_is_real (device);
dev_checkpoint->unmanaged_explicit = unmanaged_explicit;
applied_connection = nm_device_get_applied_connection (device);
if (applied_connection) {
dev_checkpoint->applied_connection =
nm_simple_connection_new_clone (applied_connection);
settings_connection = nm_device_get_settings_connection (device);
g_return_val_if_fail (settings_connection, NULL);
dev_checkpoint->settings_connection =
nm_simple_connection_new_clone (NM_CONNECTION (settings_connection));
}
return dev_checkpoint;
}
static void
device_checkpoint_destroy (gpointer data)
{
DeviceCheckpoint *dev_checkpoint = data;
g_clear_object (&dev_checkpoint->applied_connection);
g_clear_object (&dev_checkpoint->settings_connection);
g_clear_object (&dev_checkpoint->device);
g_free (dev_checkpoint->original_dev_path);
g_slice_free (DeviceCheckpoint, dev_checkpoint);
}
/*****************************************************************************/
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMCheckpoint *self = NM_CHECKPOINT (object);
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
gs_free_slist GSList *devices = NULL;
GHashTableIter iter;
NMDevice *device;
switch (prop_id) {
case PROP_DEVICES:
g_hash_table_iter_init (&iter, priv->devices);
while (g_hash_table_iter_next (&iter, (gpointer *) &device, NULL))
devices = g_slist_append (devices, device);
nm_utils_g_value_set_object_path_array (value, devices, NULL, NULL);
break;
case PROP_CREATED:
g_value_set_int64 (value, priv->created);
break;
case PROP_ROLLBACK_TIMEOUT:
g_value_set_uint (value, priv->rollback_timeout);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_checkpoint_init (NMCheckpoint *self)
{
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, device_checkpoint_destroy);
}
NMCheckpoint *
nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout,
NMCheckpointCreateFlags flags, GError **error)
{
NMCheckpoint *self;
NMCheckpointPrivate *priv;
NMSettingsConnection *const *con;
DeviceCheckpoint *dev_checkpoint;
NMDevice *device;
guint i;
g_return_val_if_fail (manager, NULL);
g_return_val_if_fail (devices, NULL);
g_return_val_if_fail (!error || !*error, NULL);
if (!devices->len) {
g_set_error_literal (error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_INVALID_ARGUMENTS,
"no device available");
return NULL;
}
self = g_object_new (NM_TYPE_CHECKPOINT, NULL);
priv = NM_CHECKPOINT_GET_PRIVATE (self);
priv->manager = manager;
priv->created = nm_utils_monotonic_timestamp_as_boottime (nm_utils_get_monotonic_timestamp_ms (),
NM_UTILS_NS_PER_MSEC);
priv->rollback_timeout = rollback_timeout;
priv->rollback_ts = rollback_timeout ?
(nm_utils_get_monotonic_timestamp_ms () + ((gint64) rollback_timeout * 1000)) :
0;
priv->flags = flags;
if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
priv->connection_uuids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
for (con = nm_settings_get_connections (nm_settings_get (), NULL); *con; con++) {
g_hash_table_add (priv->connection_uuids,
g_strdup (nm_settings_connection_get_uuid (*con)));
}
}
for (i = 0; i < devices->len; i++) {
device = (NMDevice *) devices->pdata[i];
dev_checkpoint = device_checkpoint_create (device, error);
if (!dev_checkpoint) {
g_object_unref (self);
return NULL;
}
g_hash_table_insert (priv->devices, device, dev_checkpoint);
}
return self;
}
static void
dispose (GObject *object)
{
NMCheckpoint *self = NM_CHECKPOINT (object);
NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
g_clear_pointer (&priv->devices, g_hash_table_unref);
g_clear_pointer (&priv->connection_uuids, g_hash_table_unref);
G_OBJECT_CLASS (nm_checkpoint_parent_class)->dispose (object);
}
static void
nm_checkpoint_class_init (NMCheckpointClass *checkpoint_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (checkpoint_class);
NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (checkpoint_class);
exported_object_class->export_path = NM_DBUS_PATH "/Checkpoint/%u";
exported_object_class->export_on_construction = FALSE;
object_class->dispose = dispose;
object_class->get_property = get_property;
obj_properties[PROP_DEVICES] =
g_param_spec_boxed (NM_CHECKPOINT_DEVICES, "", "",
G_TYPE_STRV,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_CREATED] =
g_param_spec_int64 (NM_CHECKPOINT_CREATED, "", "",
G_MININT64, G_MAXINT64, 0,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ROLLBACK_TIMEOUT] =
g_param_spec_uint (NM_CHECKPOINT_ROLLBACK_TIMEOUT, "", "",
0, G_MAXUINT32, 0,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (checkpoint_class),
NMDBUS_TYPE_CHECKPOINT_SKELETON,
NULL);
}