cloud-setup,core: merge branch 'th/cloud-setup-preserve-external-ip'

This is a partial backport of "th/cloud-setup-preserve-external-ip"
branch from 1.41.6.

https://bugzilla.redhat.com/show_bug.cgi?id=2132754

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1476

(cherry picked from commit f0e8b6f0e2)
This commit is contained in:
Thomas Haller 2022-12-14 17:34:37 +01:00
commit caff311fd6
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
9 changed files with 276 additions and 55 deletions

View file

@ -182,6 +182,7 @@ EXTRA_DIST += \
examples/python/gi/add_connection.py \
examples/python/gi/checkpoint.py \
examples/python/gi/deactivate-all.py \
examples/python/gi/device-reapply.py \
examples/python/gi/device-state-ip4config.py \
examples/python/gi/dns.py \
examples/python/gi/firewall-zone.py \

View file

@ -0,0 +1,147 @@
#!/usr/bin/env python
# SPDX-License-Identifier: LGPL-2.1-or-later
import os
import sys
import gi
gi.require_version("NM", "1.0")
from gi.repository import NM, GLib, Gio, GObject
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def kf_from_data(data):
kf = GLib.KeyFile.new()
kf.load_from_data(data, 18446744073709551615, GLib.KeyFileFlags.NONE)
return kf
def kf_to_data(kf):
data, l = kf.to_data()
return data
def connection_to_kf(connection):
return kf_to_data(NM.keyfile_write(connection, NM.KeyfileHandlerFlags.NONE))
def connection_from_kf(data):
base_dir = os.getcwd()
return NM.keyfile_read(kf_from_data(data), base_dir, NM.KeyfileHandlerFlags.NONE)
def connection_from_stdin():
return connection_from_kf(sys.stdin.read())
def device_get_applied_connection(device):
mainloop = GLib.MainLoop()
r = []
def cb(device, result):
try:
connection, version_id = device.get_applied_connection_finish(result)
except Exception as e:
r.append(e)
else:
r.append(connection)
r.append(version_id)
mainloop.quit()
device.get_applied_connection_async(0, None, cb)
mainloop.run()
if len(r) == 1:
raise r[0]
connection, version_id = r
return connection, version_id
def device_reapply(device, connection, version_id, reapply_flags):
mainloop = GLib.MainLoop()
r = []
def cb(device, result):
try:
device.reapply_finish(result)
except Exception as e:
r.append(e)
mainloop.quit()
device.reapply_async(connection, version_id or 0, reapply_flags, None, cb)
mainloop.run()
if len(r) == 1:
raise r[0]
def parse_args():
import argparse
parser = argparse.ArgumentParser(
prog="device-reapply.py",
description="Example program to interact with the applied connection",
)
parser.add_argument("mode", choices=["get", "reapply", "modify"])
parser.add_argument("device")
parser.add_argument("-V", "--version-id", type=int)
parser.add_argument("-s", "--stdin", action="store_true")
parser.add_argument("-p", "--preserve-external-ip", action="store_true")
return parser.parse_args()
def main():
args = parse_args()
nmc = NM.Client.new()
device = [d for d in nmc.get_devices() if d.get_iface() == args.device]
if not device:
raise Exception(f'Device "{args.device}" not found')
if len(device) != 1:
raise Exception(f'Not unique device "{args.device}" found')
device = device[0]
assert not args.stdin or args.mode == "modify"
assert not args.preserve_external_ip or args.mode in ["modify", "reapply"]
if args.mode == "get":
connection, version_id = device_get_applied_connection(device)
version_id_matches = args.version_id is None or args.version_id == version_id
print(
f'# Applied connection on "{device.get_iface()}": "{connection.get_id()}" ({connection.get_uuid()}, {connection.get_connection_type()})'
)
s = "" if version_id_matches else f" (expected {args.version_id})"
print(f"# version-id={version_id}{s}")
print(f"#")
print(f"{connection_to_kf(connection)}")
if not version_id_matches:
eprint(
f"Applied version-id does not match (expects {args.version_id} but got {version_id})"
)
sys.exit(1)
sys.exit(0)
if args.mode == "reapply":
new_connection = None
elif args.stdin:
new_connection = connection_from_stdin()
else:
new_connection, _ = device_get_applied_connection(device)
reapply_flags = 0
if args.preserve_external_ip:
reapply_flags = 1 # NM.DeviceReapplyFlags.PRESERVE_EXTERNAL_IP
device_reapply(device, new_connection, args.version_id, reapply_flags)
if __name__ == "__main__":
main()

View file

@ -322,7 +322,7 @@
Reapply:
@connection: The optional connection settings that will be reapplied on the device. If empty, the currently active settings-connection will be used. The connection cannot arbitrarily differ from the current applied-connection otherwise the call will fail. Only certain changes are supported, like adding or removing IP addresses.
@version_id: If non-zero, the current version id of the applied-connection must match. The current version id can be retrieved via GetAppliedConnection. This optional argument allows to catch concurrent modifications between the GetAppliedConnection call and Reapply.
@flags: Flags which would modify the behavior of the Reapply call. There are no flags defined currently and the users should use the value of 0.
@flags: Flags which would modify the behavior of the Reapply call. Invalid flags are rejected.
Attempts to update the configuration of a device without deactivating it.
NetworkManager has the concept of connections, which are profiles that
@ -344,6 +344,9 @@
Reapply can make the applied-connection different from the
settings-connection, just like updating the settings-connection can make
them different.
Since 1.42, 1.40.10, "preserve-external-ip" flag (0x1) is supported to not
remove externally added IP addresses and routes on the device during reapply.
-->
<method name="Reapply">
<arg name="connection" type="a{sa{sv}}" direction="in"/>

View file

@ -12791,6 +12791,7 @@ reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
* the current settings connection
* @version_id: either zero, or the current version id for the applied
* connection.
* @reapply_flags: the #NMDeviceReapplyFlags.
* @audit_args: on return, a string representing the changes
* @error: the error if %FALSE is returned
*
@ -12800,11 +12801,12 @@ reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
* Return: %FALSE if the new configuration can not be reapplied.
*/
static gboolean
check_and_reapply_connection(NMDevice *self,
NMConnection *connection,
guint64 version_id,
char **audit_args,
GError **error)
check_and_reapply_connection(NMDevice *self,
NMConnection *connection,
guint64 version_id,
NMDeviceReapplyFlags reapply_flags,
char **audit_args,
GError **error)
{
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
@ -12972,7 +12974,12 @@ check_and_reapply_connection(NMDevice *self,
reactivate_proxy_config(self);
nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, FALSE);
nm_device_l3cfg_commit(
self,
NM_FLAGS_HAS(reapply_flags, NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP)
? NM_L3_CFG_COMMIT_TYPE_UPDATE
: NM_L3_CFG_COMMIT_TYPE_REAPPLY,
FALSE);
}
if (priv->state >= NM_DEVICE_STATE_IP_CHECK)
@ -12989,12 +12996,18 @@ nm_device_reapply(NMDevice *self, NMConnection *connection, GError **error)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
return check_and_reapply_connection(self, connection, 0, NULL, error);
return check_and_reapply_connection(self,
connection,
0,
NM_DEVICE_REAPPLY_FLAGS_NONE,
NULL,
error);
}
typedef struct {
NMConnection *connection;
guint64 version_id;
NMConnection *connection;
guint64 version_id;
NMDeviceReapplyFlags reapply_flags;
} ReapplyData;
static void
@ -13005,16 +13018,16 @@ reapply_cb(NMDevice *self,
gpointer user_data)
{
ReapplyData *reapply_data = user_data;
guint64 version_id = 0;
gs_unref_object NMConnection *connection = NULL;
GError *local = NULL;
gs_free char *audit_args = NULL;
guint64 version_id;
gs_unref_object NMConnection *connection = NULL;
NMDeviceReapplyFlags reapply_flags;
GError *local = NULL;
gs_free char *audit_args = NULL;
if (reapply_data) {
connection = reapply_data->connection;
version_id = reapply_data->version_id;
g_slice_free(ReapplyData, reapply_data);
}
connection = reapply_data->connection;
version_id = reapply_data->version_id;
reapply_flags = reapply_data->reapply_flags;
nm_g_slice_free(reapply_data);
if (error) {
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
@ -13034,6 +13047,7 @@ reapply_cb(NMDevice *self,
connection
?: nm_device_get_settings_connection_get_connection(self),
version_id,
reapply_flags,
&audit_args,
&local)) {
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
@ -13067,12 +13081,12 @@ impl_device_reapply(NMDBusObject *obj,
ReapplyData *reapply_data;
gs_unref_variant GVariant *settings = NULL;
guint64 version_id;
guint32 flags;
guint32 reapply_flags_u;
NMDeviceReapplyFlags reapply_flags;
g_variant_get(parameters, "(@a{sa{sv}}tu)", &settings, &version_id, &flags);
g_variant_get(parameters, "(@a{sa{sv}}tu)", &settings, &version_id, &reapply_flags_u);
/* No flags supported as of now. */
if (flags != 0) {
if (NM_FLAGS_ANY(reapply_flags_u, ~((guint32) NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP))) {
error =
g_error_new_literal(NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "Invalid flags specified");
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
@ -13085,6 +13099,9 @@ impl_device_reapply(NMDBusObject *obj,
return;
}
reapply_flags = reapply_flags_u;
nm_assert(reapply_flags_u == reapply_flags);
if (priv->state < NM_DEVICE_STATE_PREPARE || priv->state > NM_DEVICE_STATE_ACTIVATED) {
error = g_error_new_literal(NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ACTIVE,
@ -13122,12 +13139,12 @@ impl_device_reapply(NMDBusObject *obj,
nm_connection_clear_secrets(connection);
}
if (connection || version_id) {
reapply_data = g_slice_new(ReapplyData);
reapply_data->connection = connection;
reapply_data->version_id = version_id;
} else
reapply_data = NULL;
reapply_data = g_slice_new(ReapplyData);
*reapply_data = (ReapplyData){
.connection = connection,
.version_id = version_id,
.reapply_flags = reapply_flags,
};
nm_device_auth_request(self,
invocation,
@ -13163,7 +13180,7 @@ impl_device_get_applied_connection(NMDBusObject *obj,
if (flags != 0) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"Invalid flags specified");
return;
}

View file

@ -2496,7 +2496,7 @@ nm_device_reapply_finish(NMDevice *device, GAsyncResult *result, GError **error)
/**
* nm_device_get_applied_connection:
* @device: a #NMDevice
* @flags: the flags argument. Currently, this value must always be zero.
* @flags: the flags argument. See #NMDeviceReapplyFlags.
* @version_id: (out) (allow-none): returns the current version id of
* the applied connection
* @cancellable: a #GCancellable, or %NULL
@ -2559,7 +2559,7 @@ nm_device_get_applied_connection(NMDevice *device,
/**
* nm_device_get_applied_connection_async:
* @device: a #NMDevice
* @flags: the flags argument. Currently, this value must always be zero.
* @flags: the flags argument. See #NMDeviceReapplyFlags.
* @cancellable: a #GCancellable, or %NULL
* @callback: callback to be called when the reapply operation completes
* @user_data: caller-specific data passed to @callback

View file

@ -1144,6 +1144,24 @@ typedef enum /*< flags >*/ {
NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY = 0x40,
} NMSettingsUpdate2Flags;
/**
* NMDeviceReapplyFlags:
* @NM_DEVICE_REAPPLY_FLAGS_NONE: no flag set.
* @NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP: during reapply,
* preserve external IP addresses and routes.
*
* Flags for the Reapply() D-Bus call of a device and
* nm_device_reapply_async().
*
* Since: 1.42, 1.40.10
*
* On 1.40.10+, no GFlags type is created.
*/
typedef enum /*< skip >*/ {
NM_DEVICE_REAPPLY_FLAGS_NONE = 0,
NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP = 0x1,
} NMDeviceReapplyFlags;
/**
* NMTernary:
* @NM_TERNARY_DEFAULT: use the globally-configured default value.

View file

@ -251,24 +251,38 @@ _get_config(GCancellable *sigterm_cancellable, NMCSProvider *provider, NMClient
/*****************************************************************************/
static gboolean
_nmc_skip_connection(NMConnection *connection)
_nmc_skip_connection_by_user_data(NMConnection *connection)
{
NMSettingUser *s_user;
const char *v;
s_user = NM_SETTING_USER(nm_connection_get_setting(connection, NM_TYPE_SETTING_USER));
if (!s_user)
return FALSE;
#define USER_TAG_SKIP "org.freedesktop.nm-cloud-setup.skip"
nm_assert(nm_setting_user_check_key(USER_TAG_SKIP, NULL));
v = nm_setting_user_get_data(s_user, USER_TAG_SKIP);
return _nm_utils_ascii_str_to_bool(v, FALSE);
s_user = NM_SETTING_USER(nm_connection_get_setting(connection, NM_TYPE_SETTING_USER));
if (s_user) {
v = nm_setting_user_get_data(s_user, USER_TAG_SKIP);
if (_nm_utils_ascii_str_to_bool(v, FALSE))
return TRUE;
}
return FALSE;
}
static gboolean
_nmc_skip_connection_by_type(NMConnection *connection)
{
if (!nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_WIRED_SETTING_NAME))
return TRUE;
if (!nm_connection_get_setting_ip4_config(connection))
return TRUE;
return FALSE;
}
static void
_nmc_mangle_connection(NMDevice *device,
NMConnection *connection,
const NMCSProviderGetConfigResult *result,
@ -291,12 +305,8 @@ _nmc_mangle_connection(NMDevice *device,
NM_SET_OUT(out_skipped_single_addr, FALSE);
NM_SET_OUT(out_changed, FALSE);
if (!nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_WIRED_SETTING_NAME))
return FALSE;
s_ip = nm_connection_get_setting_ip4_config(connection);
if (!s_ip)
return FALSE;
nm_assert(NM_IS_SETTING_IP4_CONFIG(s_ip));
if ((ac = nm_device_get_active_connection(device))
&& (remote_connection = NM_CONNECTION(nm_active_connection_get_connection(ac))))
@ -429,7 +439,6 @@ _nmc_mangle_connection(NMDevice *device,
rules_new->len);
NM_SET_OUT(out_changed, addrs_changed || routes_changed || rules_changed);
return TRUE;
}
/*****************************************************************************/
@ -451,6 +460,7 @@ _config_one(GCancellable *sigterm_cancellable,
gboolean version_id_changed;
guint try_count;
gboolean any_changes = FALSE;
gboolean maybe_no_preserved_external_ip;
g_main_context_iteration(NULL, FALSE);
@ -484,6 +494,8 @@ _config_one(GCancellable *sigterm_cancellable,
try_count = 0;
try_again:
g_clear_object(&applied_connection);
g_clear_error(&error);
applied_connection = nmcs_device_get_applied_connection(device,
sigterm_cancellable,
@ -497,23 +509,25 @@ try_again:
return any_changes;
}
if (_nmc_skip_connection(applied_connection)) {
if (_nmc_skip_connection_by_user_data(applied_connection)) {
_LOGD("config device %s: skip applied connection due to user data %s",
hwaddr,
USER_TAG_SKIP);
return any_changes;
}
if (!_nmc_mangle_connection(device,
applied_connection,
result,
config_data,
&skipped_single_addr,
&changed)) {
if (_nmc_skip_connection_by_type(applied_connection)) {
_LOGD("config device %s: device has no suitable applied connection. Skip", hwaddr);
return any_changes;
}
_nmc_mangle_connection(device,
applied_connection,
result,
config_data,
&skipped_single_addr,
&changed);
if (!changed) {
if (skipped_single_addr) {
_LOGD("config device %s: device needs no update to applied connection \"%s\" (%s) "
@ -539,16 +553,22 @@ try_again:
/* we are about to call Reapply(). Even if that fails, it counts as if we changed something. */
any_changes = TRUE;
/* "preserve-external-ip" flag was only introduced in 1.41.6 and 1.40.9.
* We have no convenient way to check the daemon version (short of parsing the "Version"
* string). Hence, we don't know it. Take into account, that the daemon that we
* talk to might not support the flag yet. This is to support backward compatibility
* during package upgrade. */
maybe_no_preserved_external_ip = TRUE;
if (!nmcs_device_reapply(device,
sigterm_cancellable,
applied_connection,
applied_version_id,
maybe_no_preserved_external_ip,
&version_id_changed,
&error)) {
if (version_id_changed && try_count < 5) {
_LOGD("config device %s: applied connection changed in the meantime. Retry...", hwaddr);
g_clear_object(&applied_connection);
g_clear_error(&error);
try_count++;
goto try_again;
}

View file

@ -822,6 +822,7 @@ nmcs_device_reapply(NMDevice *device,
GCancellable *sigterm_cancellable,
NMConnection *connection,
guint64 version_id,
gboolean maybe_no_preserved_external_ip,
gboolean *out_version_id_changed,
GError **error)
{
@ -829,11 +830,13 @@ nmcs_device_reapply(NMDevice *device,
DeviceReapplyData data = {
.main_loop = main_loop,
};
NMDeviceReapplyFlags reapply_flags = NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP;
again:
nm_device_reapply_async(device,
connection,
version_id,
0,
reapply_flags,
sigterm_cancellable,
_nmcs_device_reapply_cb,
&data);
@ -841,6 +844,17 @@ nmcs_device_reapply(NMDevice *device,
g_main_loop_run(main_loop);
if (data.error) {
if (maybe_no_preserved_external_ip
&& reapply_flags == NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP
&& nm_g_error_matches(data.error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED)) {
/* Hm? Maybe we running against an older version of NetworkManager that
* doesn't support "preserve-external-ip" flags? Retry without the flag.
*
* Note that recent version would reject invalid flags with NM_DEVICE_ERROR_INVALID_ARGUMENT,
* but we want to detect old daemon versions here. */
reapply_flags = NM_DEVICE_REAPPLY_FLAGS_NONE;
goto again;
}
NM_SET_OUT(
out_version_id_changed,
g_error_matches(data.error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_VERSION_ID_MISMATCH));

View file

@ -136,6 +136,7 @@ gboolean nmcs_device_reapply(NMDevice *device,
GCancellable *sigterm_cancellable,
NMConnection *connection,
guint64 version_id,
gboolean maybe_no_preserved_external_ip,
gboolean *out_version_id_changed,
GError **error);