From 13aec9fddffbf52e50c67ab7bc6575abb3a1588f Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Thu, 16 Jul 2026 16:08:27 +0000 Subject: [PATCH] networkctl,networkd: add --no-reconfigure flag to networkctl reload Add a new --no-reconfigure flag to 'networkctl reload' that reloads .network and .netdev files from disk without reconfiguring any network interfaces. This may be useful to avoid reconfiguring multiple interfaces simultaneously when multiple .network files are updated, or when an updated .network file is applied to multiple interfaces. On the networkd side, manager_reload() gains a reconfigure_links parameter that gates the per-link reconfiguration loop. A new io.systemd.Network.Reload varlink method is added that exposes this as an optional reconfigureLinks boolean (defaults to true). Both plain 'networkctl reload' and '--no-reconfigure' now unconditionally call this method first. If an older networkd returns MethodNotFound, plain reload falls back to io.systemd.service.Reload for backward compatibility; '--no-reconfigure' fails with a clear error in that case. --- man/networkctl.xml | 20 +++++++++++- shell-completion/bash/networkctl | 2 +- shell-completion/zsh/_networkctl | 1 + src/network/networkctl-config-file.c | 2 +- src/network/networkctl-misc.c | 2 +- src/network/networkctl-util.c | 36 ++++++++++++++++++--- src/network/networkctl-util.h | 2 +- src/network/networkctl.c | 6 ++++ src/network/networkctl.h | 1 + src/network/networkd-manager-bus.c | 2 +- src/network/networkd-manager-varlink.c | 23 +++++++++++-- src/network/networkd-manager.c | 19 +++++------ src/network/networkd-manager.h | 2 +- src/shared/varlink-io.systemd.Network.c | 8 +++++ test/test-network/systemd-networkd-tests.py | 29 +++++++++++++++++ 15 files changed, 133 insertions(+), 22 deletions(-) diff --git a/man/networkctl.xml b/man/networkctl.xml index b436797999..4905c78506 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -543,7 +543,10 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) If a new, modified, or removed .network file is found, then all - interfaces that matched the file are reconfigured. + interfaces that matched the file are reconfigured. This reconfiguration step can be skipped by + passing , in which case only config files are reloaded from disk + without reconfiguring any network interfaces. Use networkctl reconfigure afterwards + to selectively apply config changes per interface. @@ -729,6 +732,21 @@ s - Service VLAN, m - Two-port MAC Relay (TPMR) + + + + + When used with reload, only reload config files from disk without + reconfiguring any network interfaces. This may be useful to avoid reconfiguring multiple + interfaces simultaneously when multiple .network files are updated, + or when an updated .network file is applied to multiple interfaces. + Use networkctl reconfigure afterwards to apply changes selectively + per interface. + + + + + diff --git a/shell-completion/bash/networkctl b/shell-completion/bash/networkctl index fe186a49a2..ddb3b662d2 100644 --- a/shell-completion/bash/networkctl +++ b/shell-completion/bash/networkctl @@ -45,7 +45,7 @@ _networkctl() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword local -A OPTS=( [STANDALONE]='-a --all -h --help --version --no-pager --no-legend --no-ask-password - -s --stats -l --full --no-reload --runtime' + -s --stats -l --full --no-reload --no-reconfigure --runtime' [ARG]='-n --lines --json --drop-in' ) diff --git a/shell-completion/zsh/_networkctl b/shell-completion/zsh/_networkctl index c44b346949..4c54cde486 100644 --- a/shell-completion/zsh/_networkctl +++ b/shell-completion/zsh/_networkctl @@ -58,5 +58,6 @@ _arguments \ '(- *)--version[Show package version]' \ '--drop-in=[Use the given drop-in file name]:NAME' \ '--no-reload[Do not reload the network manager state when editing]' \ + '--no-reconfigure[Reload config files without reconfiguring network interfaces]' \ '--json=[Shows output formatted as JSON]:format:_networkctl_get_json' \ '*::networkctl commands:_networkctl_commands' diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c index f69c509023..1b5cf1718f 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -411,7 +411,7 @@ static int reload_daemons(ReloadFlags flags) { if (!networkd_is_running()) log_debug("systemd-networkd is not running, skipping reload."); else - RET_GATHER(ret, reload_networkd()); + RET_GATHER(ret, reload_networkd(/* reconfigure_links= */ true)); } return ret; diff --git a/src/network/networkctl-misc.c b/src/network/networkctl-misc.c index 0436346bc8..2115da16b6 100644 --- a/src/network/networkctl-misc.c +++ b/src/network/networkctl-misc.c @@ -123,7 +123,7 @@ int verb_link_varlink_simple_method(int argc, char *argv[], uintptr_t _data, voi } int verb_reload(int argc, char *argv[], uintptr_t _data, void *userdata) { - return reload_networkd(); + return reload_networkd(!arg_no_reconfigure); } int verb_persistent_storage(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/network/networkctl-util.c b/src/network/networkctl-util.c index 590c8a6abc..22710d7631 100644 --- a/src/network/networkctl-util.c +++ b/src/network/networkctl-util.c @@ -60,7 +60,7 @@ int varlink_connect_networkd(sd_varlink **ret_varlink) { return 0; } -int reload_networkd(void) { +int reload_networkd(bool reconfigure_links) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; int r; @@ -70,11 +70,39 @@ int reload_networkd(void) { (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - return varlink_callbo_and_log( + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( vl, - "io.systemd.service.Reload", - /* reply= */ NULL, + "io.systemd.Network.Reload", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_BOOLEAN("reconfigureLinks", reconfigure_links), SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.Network.Reload varlink method: %m"); + + if (error_id) { + if (streq(error_id, SD_VARLINK_ERROR_METHOD_NOT_FOUND)) { + if (!reconfigure_links) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Installed version of systemd-networkd does not support --no-reconfigure."); + + /* Older networkd without io.systemd.Network.Reload, fall back to io.systemd.service.Reload. */ + return varlink_callbo_and_log( + vl, + "io.systemd.service.Reload", + /* reply= */ NULL, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + } + + r = sd_varlink_error_to_errno(error_id, reply); + if (r != -EBADR) + return log_error_errno(r, "Failed to call io.systemd.Network.Reload varlink method: %m"); + return log_error_errno(r, "Failed to call io.systemd.Network.Reload varlink method: %s", error_id); + } + + return 0; } int reload_udevd(void) { diff --git a/src/network/networkctl-util.h b/src/network/networkctl-util.h index ae0f565007..cb5832b80f 100644 --- a/src/network/networkctl-util.h +++ b/src/network/networkctl-util.h @@ -4,7 +4,7 @@ #include "forward.h" int varlink_connect_networkd(sd_varlink **ret_varlink); -int reload_networkd(void); +int reload_networkd(bool reconfigure_links); int reload_udevd(void); bool networkd_is_running(void); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index f7a6e6eef7..73757cc8f2 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -28,6 +28,7 @@ PagerFlags arg_pager_flags = 0; bool arg_legend = true; bool arg_no_reload = false; +bool arg_no_reconfigure = false; bool arg_all = false; bool arg_stats = false; bool arg_full = false; @@ -167,6 +168,11 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_no_reload = true; break; + OPTION_LONG("no-reconfigure", NULL, + "Only reload config files, do not reconfigure network interfaces"): + arg_no_reconfigure = true; + break; + OPTION_LONG("drop-in", "NAME", "Edit specified drop-in instead of main config file"): if (isempty(opts.arg)) diff --git a/src/network/networkctl.h b/src/network/networkctl.h index 39c81e7947..2de93bd605 100644 --- a/src/network/networkctl.h +++ b/src/network/networkctl.h @@ -7,6 +7,7 @@ extern PagerFlags arg_pager_flags; extern bool arg_legend; extern bool arg_no_reload; +extern bool arg_no_reconfigure; extern bool arg_all; extern bool arg_stats; extern bool arg_full; diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c index c335d34ff1..1fad86dc4a 100644 --- a/src/network/networkd-manager-bus.c +++ b/src/network/networkd-manager-bus.c @@ -215,7 +215,7 @@ static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_err if (r == 0) return 1; /* Polkit will call us back */ - r = manager_reload(manager, message, /* varlink= */ NULL); + r = manager_reload(manager, message, /* varlink= */ NULL, /* reconfigure_links= */ true); if (r < 0) return r; diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index d12847b089..19f7b4813d 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -18,6 +18,7 @@ #include "networkd-manager.h" #include "networkd-manager-varlink.h" #include "stat-util.h" +#include "string-util.h" #include "varlink-io.systemd.Network.h" #include "varlink-io.systemd.Network.Link.h" #include "varlink-io.systemd.service.h" @@ -237,6 +238,12 @@ static int vl_method_set_persistent_storage(sd_varlink *vlink, sd_json_variant * } static int vl_method_reload(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "reconfigureLinks", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, 0, SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + Manager *m = ASSERT_PTR(userdata); int r; @@ -245,7 +252,18 @@ static int vl_method_reload(sd_varlink *vlink, sd_json_variant *parameters, sd_v if (m->reloading > 0) return sd_varlink_error(vlink, "io.systemd.Network.AlreadyReloading", NULL); - r = sd_varlink_dispatch(vlink, parameters, dispatch_table_polkit_only, /* userdata= */ NULL); + const char *method; + r = sd_varlink_get_current_method(vlink, &method); + if (r < 0) + return r; + + bool is_network_reload = streq(method, "io.systemd.Network.Reload"); + int reconfigure_links = -1; /* tristate: -1 = unset (defaults to true) */ + r = sd_varlink_dispatch( + vlink, + parameters, + is_network_reload ? dispatch_table : dispatch_table_polkit_only, + &reconfigure_links); if (r != 0) return r; @@ -258,7 +276,7 @@ static int vl_method_reload(sd_varlink *vlink, sd_json_variant *parameters, sd_v if (r <= 0) return r; - r = manager_reload(m, /* message= */ NULL, vlink); + r = manager_reload(m, /* message= */ NULL, vlink, /* reconfigure_links= */ reconfigure_links != 0); if (r < 0) return log_error_errno(r, "Failed to reload: %m"); @@ -302,6 +320,7 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.GetStates", vl_method_get_states, "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, + "io.systemd.Network.Reload", vl_method_reload, "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage, "io.systemd.Network.Link.Describe", vl_method_link_describe, "io.systemd.Network.Link.Up", vl_method_link_up, diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 63ba0c166f..64e022a674 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -535,7 +535,7 @@ static int signal_restart_callback(sd_event_source *s, const struct signalfd_sig static int signal_reload_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { Manager *m = ASSERT_PTR(userdata); - (void) manager_reload(m, /* message= */ NULL, /* varlink= */ NULL); + (void) manager_reload(m, /* message= */ NULL, /* varlink= */ NULL, /* reconfigure_links= */ true); return 0; } @@ -1266,7 +1266,7 @@ int manager_set_timezone(Manager *m, const char *tz) { return 0; } -int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink) { +int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink, bool reconfigure_links) { Link *link; int r; @@ -1288,13 +1288,14 @@ int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink) { goto finish; } - HASHMAP_FOREACH(link, m->links_by_index) - (void) link_reconfigure_full( - link, - /* flags= */ 0, - message, - varlink, - /* counter= */ (message || varlink) ? &m->reloading : NULL); + if (reconfigure_links) + HASHMAP_FOREACH(link, m->links_by_index) + (void) link_reconfigure_full( + link, + /* flags= */ 0, + message, + varlink, + /* counter= */ (message || varlink) ? &m->reloading : NULL); log_debug("Reloaded."); r = 0; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 2e1b643090..7913e972ec 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -159,7 +159,7 @@ int manager_enumerate(Manager *m); int manager_set_hostname(Manager *m, const char *hostname); int manager_set_timezone(Manager *m, const char *tz); -int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink); +int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink, bool reconfigure_links); static inline Hashmap** manager_get_sysctl_shadow(Manager *manager) { #if ENABLE_SYSCTL_BPF diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index e98d517cc7..2973328777 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "bus-polkit.h" #include "varlink-io.systemd.Network.h" /* Helper macro to define address fields with both binary and string representation */ @@ -544,6 +545,12 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Current transmit/receive bitrates from speed meter"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(BitRates, BitRates, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + Reload, + SD_VARLINK_FIELD_COMMENT("If true (default), reload config files and reconfigure all matching network interfaces. If false, only reload config files without reconfiguring interfaces; use 'networkctl reconfigure' afterwards to apply changes selectively."), + SD_VARLINK_DEFINE_INPUT(reconfigureLinks, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + static SD_VARLINK_DEFINE_METHOD( Describe, SD_VARLINK_FIELD_COMMENT("All network interfaces managed by systemd-networkd"), @@ -612,6 +619,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_GetStates, &vl_method_GetNamespaceId, &vl_method_GetLLDPNeighbors, + &vl_method_Reload, &vl_method_SetPersistentStorage, &vl_type_Address, &vl_type_BitRates, diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index ce0d5a8a15..e55a509367 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1699,6 +1699,35 @@ class NetworkctlTests(unittest.TestCase, Utilities): networkctl_reload() self.wait_operstate('test1', 'degraded') + def test_reload_no_reconfigure(self): + copy_network_unit('12-dummy.netdev', '25-address-static.network', copy_dropins=False) + start_networkd() + self.wait_online('dummy98:routable') + + output = check_output('ip -4 address show dev dummy98') + print(output) + self.assertIn('inet 10.1.2.3/16 brd 10.1.255.255 scope global dummy98', output) + + with open( + os.path.join(network_unit_dir, '25-address-static.network'), mode='w', encoding='utf-8' + ) as f: + f.write('[Match]\nName=dummy98\n\n[Network]\nIPv6AcceptRA=no\nAddress=10.99.0.1/24\n') + + networkctl('reload', '--no-reconfigure') + + output = check_output('ip -4 address show dev dummy98') + print(output) + self.assertIn('inet 10.1.2.3/16 brd 10.1.255.255 scope global dummy98', output) + self.assertNotIn('inet 10.99.0.1/24', output) + + networkctl_reconfigure('dummy98') + self.wait_online('dummy98:routable') + + output = check_output('ip -4 address show dev dummy98') + print(output) + self.assertIn('inet 10.99.0.1/24 brd 10.99.0.255 scope global dummy98', output) + self.assertNotIn('inet 10.1.2.3/16', output) + def test_glob(self): copy_network_unit('11-dummy.netdev', '11-dummy.network') start_networkd()