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