From 28bb29cbb48072952bbc47bbf91f1d4d24538766 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Tue, 19 Dec 2023 15:38:32 +0800 Subject: [PATCH] networkctl: introduce verb mask and unmask Suggested in https://github.com/systemd/systemd/pull/29928#discussion_r1386626565 --- man/networkctl.xml | 42 +++++++++- src/network/networkctl-config-file.c | 108 ++++++++++++++++++++++++++ src/network/networkctl-config-file.h | 3 + src/network/networkctl.c | 4 + test/units/testsuite-74.networkctl.sh | 19 +++++ 5 files changed, 172 insertions(+), 4 deletions(-) diff --git a/man/networkctl.xml b/man/networkctl.xml index 3a2dc09eccb..1a03e9e11d4 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -461,6 +461,40 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) + + + + mask + FILE… + + Mask network configuration files, which include .network, + .netdev, and .link files. A symlink of the given name will + be created under /etc/ or /run/, depending on + whether is specified, that points to /dev/null. + If a non-empty config file with the specified name exists under the target directory or a directory + with higher priority (e.g. is used while an existing config resides + in /etc/), the operation is aborted. + + This command honors in the same way as edit. + + + + + + + + unmask + FILE… + + Unmask network configuration files, i.e. reverting the effect of mask. + Note that this command operates regardless of the scope of the directory, i.e. + is of no effect. + + This command honors in the same way as edit + and mask. + + + @@ -534,11 +568,11 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) - When used with edit, + When used with edit, mask, or unmask, systemd-networkd.service8 or systemd-udevd.service8 - will not be reloaded after the editing finishes. + will not be reloaded after the operation finishes. @@ -547,8 +581,8 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) - When used with edit, edit the file under /run/ - instead of /etc/. + When used with edit or mask, + operate on the file under /run/ instead of /etc/. diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c index b212358cfaa..670f1c2fd7b 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -13,6 +13,7 @@ #include "bus-wait-for-jobs.h" #include "conf-files.h" #include "edit-util.h" +#include "mkdir-label.h" #include "netlink-util.h" #include "networkctl.h" #include "networkctl-config-file.h" @@ -518,3 +519,110 @@ int verb_cat(int argc, char *argv[], void *userdata) { return ret; } + +int verb_mask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + r = mac_selinux_init(); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *config_path = NULL, *symlink_path = NULL; + ReloadFlags reload; + + /* We update the real 'flags' at last, since the operation can be skipped. */ + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload = RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload = RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ true, &config_path, /* ret_dropins = */ NULL); + if (r == -ENOENT) + log_warning("No existing network config '%s' found, proceeding anyway.", *name); + else if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + else if (!path_startswith(config_path, "/usr")) { + r = null_or_empty_path(config_path); + if (r < 0) + return log_error_errno(r, + "Failed to check if '%s' is masked: %m", config_path); + if (r > 0) { + log_debug("%s is already masked, skipping.", config_path); + continue; + } + + /* At this point, we have found a config under mutable dir (/run/ or /etc/), + * so masking through /run/ (--runtime) is not possible. If it's under /etc/, + * then it doesn't work without --runtime either. */ + if (arg_runtime || path_startswith(config_path, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot mask network config %s: %s exists", + *name, config_path); + } + + symlink_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name); + if (!symlink_path) + return log_oom(); + + (void) mkdir_parents_label(symlink_path, 0755); + + if (symlink("/dev/null", symlink_path) < 0) + return log_error_errno(errno, + "Failed to create symlink '%s' to /dev/null: %m", symlink_path); + + flags |= reload; + log_info("Successfully created symlink '%s' to /dev/null.", symlink_path); + } + + return reload_daemons(flags); +} + +int verb_unmask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *path = NULL; + ReloadFlags reload; + + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload = RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload = RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ true, &path, /* ret_dropins = */ NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Network configuration '%s' doesn't exist, skipping.", *name); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + + r = null_or_empty_path(path); + if (r < 0) + return log_error_errno(r, "Failed to check if '%s' is masked: %m", path); + if (r == 0) + continue; + + if (path_startswith(path, "/usr")) + return log_error_errno(r, "Cannot unmask network config under /usr/: %s", path); + + if (unlink(path) < 0) { + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to remove '%s': %m", path); + } + + flags |= reload; + log_info("Successfully removed masked network config '%s'.", path); + } + + return reload_daemons(flags); +} diff --git a/src/network/networkctl-config-file.h b/src/network/networkctl-config-file.h index 8d7069e3f31..38210a8093b 100644 --- a/src/network/networkctl-config-file.h +++ b/src/network/networkctl-config-file.h @@ -3,3 +3,6 @@ int verb_edit(int argc, char *argv[], void *userdata); int verb_cat(int argc, char *argv[], void *userdata); + +int verb_mask(int argc, char *argv[], void *userdata); +int verb_unmask(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index aaa2b3a4303..cf9d17c8b2f 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -2877,6 +2877,8 @@ static int help(void) { " reload Reload .network and .netdev files\n" " edit FILES|DEVICES... Edit network configuration files\n" " cat FILES|DEVICES... Show network configuration files\n" + " mask FILES... Mask network configuration files\n" + " unmask FILES... Unmask network configuration files\n" "\nOptions:\n" " -h --help Show this help\n" " --version Show package version\n" @@ -3033,6 +3035,8 @@ static int networkctl_main(int argc, char *argv[]) { { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, { "edit", 2, VERB_ANY, 0, verb_edit }, { "cat", 2, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, {} }; diff --git a/test/units/testsuite-74.networkctl.sh b/test/units/testsuite-74.networkctl.sh index b857abcf9a6..06a3c39e776 100755 --- a/test/units/testsuite-74.networkctl.sh +++ b/test/units/testsuite-74.networkctl.sh @@ -28,6 +28,16 @@ Name=test EOF # Test files + +networkctl mask --runtime "donotexist.network" +assert_eq "$(readlink /run/systemd/network/donotexist.network)" "/dev/null" +networkctl unmask "donotexist.network" # unmask should work even without --runtime +[[ ! -e /run/systemd/network/donotexist.network ]] + +touch /usr/lib/systemd/network/donotexist.network +(! networkctl unmask "donotexist.network") +rm /usr/lib/systemd/network/donotexist.network + networkctl cat "$NETWORK_NAME" | tail -n +2 | cmp - "/usr/lib/systemd/network/$NETWORK_NAME" cat >new <"+4" <