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()