diff --git a/man/systemd-networkd-wait-online.service.xml b/man/systemd-networkd-wait-online.service.xml
index 3fac8ab9d38..7c82f68fb32 100644
--- a/man/systemd-networkd-wait-online.service.xml
+++ b/man/systemd-networkd-wait-online.service.xml
@@ -35,8 +35,9 @@
configured. By default, it will wait for all links it is aware of
and which are managed by
systemd-networkd.service8
- to be fully configured or failed, and for at least one link to
- gain a carrier.
+ to be fully configured or failed, and for at least one link to be online. Here, online means that
+ the link's operational state is equal or higher than degraded. The threshold
+ can be configured by option.
@@ -49,13 +50,12 @@
INTERFACE:OPERSTATEINTERFACE:OPERSTATE
- Network interface to wait for before deciding
- if the system is online. This is useful when a system has
- several interfaces which will be configured, but a particular
- one is necessary to access some network resources. This option
- may be used more than once to wait for multiple network
- interfaces. When used, all other interfaces are ignored.
- Optinally, required minimum operational state can be
+ Network interface to wait for before deciding if the system is online. This
+ is useful when a system has several interfaces which will be configured, but a particular
+ one is necessary to access some network resources. When used, all other interfaces are ignored.
+ This option may be used more than once to wait for multiple network interfaces. When this
+ option is specified multiple times, then systemd-networkd-wait-online waits
+ for all specified interfaces to be online. Optinally, required minimum operational state can be
specified after a colon :. Please see
networkctl1
for possible operational states. If the operational state is not specified here, then
@@ -81,7 +81,18 @@
networkctl1
for possible operational states. If set, the specified value overrides
RequiredForOnline= settings in .network files.
- But this does not override operational states specified in option.
+ But this does not override operational states specified in option.
+
+
+
+
+
+
+ Even if several interfaces are in configuring state,
+ systemd-networkd-wait-online exits with success when at least one interface
+ becomes online. When this option is specified with , then
+ systemd-networkd-wait-online waits for one of the specified interfaces to be
+ online. This option is useful when some interfaces may not have carrier on boot.
diff --git a/src/network/wait-online/manager.c b/src/network/wait-online/manager.c
index eb551f116ae..e1f9a812fbb 100644
--- a/src/network/wait-online/manager.c
+++ b/src/network/wait-online/manager.c
@@ -59,7 +59,7 @@ static int manager_link_is_online(Manager *m, Link *l, LinkOperationalState s) {
return 1;
}
-bool manager_all_configured(Manager *m) {
+bool manager_configured(Manager *m) {
bool one_ready = false;
Iterator i;
const char *ifname;
@@ -67,24 +67,33 @@ bool manager_all_configured(Manager *m) {
Link *l;
int r;
- /* wait for all the links given on the command line to appear */
- HASHMAP_FOREACH_KEY(p, ifname, m->interfaces, i) {
- LinkOperationalState s = PTR_TO_INT(p);
+ if (!hashmap_isempty(m->interfaces)) {
+ /* wait for all the links given on the command line to appear */
+ HASHMAP_FOREACH_KEY(p, ifname, m->interfaces, i) {
+ LinkOperationalState s = PTR_TO_INT(p);
- l = hashmap_get(m->links_by_name, ifname);
- if (!l) {
- log_debug("still waiting for %s", ifname);
- return false;
+ l = hashmap_get(m->links_by_name, ifname);
+ if (!l) {
+ log_debug("still waiting for %s", ifname);
+ if (!m->any)
+ return false;
+ continue;
+ }
+
+ if (manager_link_is_online(m, l, s) <= 0) {
+ if (!m->any)
+ return false;
+ continue;
+ }
+
+ one_ready = true;
}
- if (manager_link_is_online(m, l, s) <= 0)
- return false;
+ /* all interfaces given by the command line are online, or
+ * one of the specified interfaces is online. */
+ return one_ready;
}
- if (!hashmap_isempty(m->interfaces))
- /* all interfaces given by the command line are online. */
- return true;
-
/* wait for all links networkd manages to be in admin state 'configured'
* and at least one link to gain a carrier */
HASHMAP_FOREACH(l, m->links, i) {
@@ -94,7 +103,7 @@ bool manager_all_configured(Manager *m) {
}
r = manager_link_is_online(m, l, _LINK_OPERSTATE_INVALID);
- if (r < 0)
+ if (r < 0 && !m->any)
return false;
if (r > 0)
/* we wait for at least one link to be ready,
@@ -180,7 +189,7 @@ static int on_rtnl_event(sd_netlink *rtnl, sd_netlink_message *mm, void *userdat
if (r < 0)
return r;
- if (manager_all_configured(m))
+ if (manager_configured(m))
sd_event_exit(m->event, 0);
return 1;
@@ -248,7 +257,7 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *
log_link_warning_errno(l, r, "Failed to update monitor information: %m");
}
- if (manager_all_configured(m))
+ if (manager_configured(m))
sd_event_exit(m->event, 0);
return 0;
@@ -280,7 +289,8 @@ static int manager_network_monitor_listen(Manager *m) {
}
int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
- LinkOperationalState required_operstate, usec_t timeout) {
+ LinkOperationalState required_operstate,
+ bool any, usec_t timeout) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
@@ -294,6 +304,7 @@ int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
.interfaces = interfaces,
.ignore = ignore,
.required_operstate = required_operstate,
+ .any = any,
};
r = sd_event_default(&m->event);
diff --git a/src/network/wait-online/manager.h b/src/network/wait-online/manager.h
index e559dd168de..dd7d847dd38 100644
--- a/src/network/wait-online/manager.h
+++ b/src/network/wait-online/manager.h
@@ -21,6 +21,7 @@ struct Manager {
char **ignore;
LinkOperationalState required_operstate;
+ bool any;
sd_netlink *rtnl;
sd_event_source *rtnl_event_source;
@@ -33,8 +34,9 @@ struct Manager {
void manager_free(Manager *m);
int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
- LinkOperationalState required_operstate, usec_t timeout);
+ LinkOperationalState required_operstate,
+ bool any, usec_t timeout);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
-bool manager_all_configured(Manager *m);
+bool manager_configured(Manager *m);
diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c
index fd7a48d81dc..4ce2ac31b2d 100644
--- a/src/network/wait-online/wait-online.c
+++ b/src/network/wait-online/wait-online.c
@@ -19,6 +19,7 @@ static usec_t arg_timeout = 120 * USEC_PER_SEC;
static Hashmap *arg_interfaces = NULL;
static char **arg_ignore = NULL;
static LinkOperationalState arg_required_operstate = _LINK_OPERSTATE_INVALID;
+static bool arg_any = false;
STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_free_free_keyp);
STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep);
@@ -41,6 +42,7 @@ static int help(void) {
" --ignore=INTERFACE Don't take these interfaces into account\n"
" -o --operational-state=OPERSTATE\n"
" Required operational state\n"
+ " --any Wait until at least one of the interfaces is online\n"
" --timeout=SECS Maximum time to wait for network connectivity\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
@@ -101,6 +103,7 @@ static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_IGNORE,
+ ARG_ANY,
ARG_TIMEOUT,
};
@@ -111,6 +114,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "interface", required_argument, NULL, 'i' },
{ "ignore", required_argument, NULL, ARG_IGNORE },
{ "operational-state", required_argument, NULL, 'o' },
+ { "any", no_argument, NULL, ARG_ANY },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
{}
};
@@ -158,6 +162,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_required_operstate = s;
break;
}
+ case ARG_ANY:
+ arg_any = true;
+ break;
+
case ARG_TIMEOUT:
r = parse_sec(optarg, &arg_timeout);
if (r < 0)
@@ -192,11 +200,11 @@ static int run(int argc, char *argv[]) {
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
- r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_timeout);
+ r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_any, arg_timeout);
if (r < 0)
return log_error_errno(r, "Could not create manager: %m");
- if (manager_all_configured(m))
+ if (manager_configured(m))
goto success;
notify_message = notify_start("READY=1\n"
diff --git a/test/test-network/conf/11-dummy.network b/test/test-network/conf/11-dummy.network
new file mode 100644
index 00000000000..b117028a848
--- /dev/null
+++ b/test/test-network/conf/11-dummy.network
@@ -0,0 +1,5 @@
+[Match]
+Name=test1
+
+[Network]
+IPv6AcceptRA=no
diff --git a/test/test-network/conf/25-bridge.network b/test/test-network/conf/25-bridge.network
new file mode 100644
index 00000000000..d2f346388d3
--- /dev/null
+++ b/test/test-network/conf/25-bridge.network
@@ -0,0 +1,5 @@
+[Match]
+Name=bridge99
+
+[Network]
+IPv6AcceptRA=no
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index a2463796448..ffc63d08383 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -71,7 +71,6 @@ def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable():
return f
def setUpModule():
-
os.makedirs(network_unit_file_path, exist_ok=True)
os.makedirs(networkd_ci_path, exist_ok=True)
@@ -186,8 +185,10 @@ class Utilities():
if sleep_sec > 0:
time.sleep(sleep_sec)
- def wait_online(self, links_with_operstate, timeout='20s'):
- args = [wait_online_bin, f' --timeout={timeout}'] + [f'--interface={link}' for link in links_with_operstate]
+ def wait_online(self, links_with_operstate, timeout='20s', bool_any=False):
+ args = [wait_online_bin, f'--timeout={timeout}'] + [f'--interface={link}' for link in links_with_operstate]
+ if bool_any:
+ args += ['--any']
subprocess.check_call(args)
class NetworkdNetDevTests(unittest.TestCase, Utilities):
@@ -248,6 +249,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
units = [
'10-dropin-test.netdev',
'11-dummy.netdev',
+ '11-dummy.network',
'12-dummy.netdev',
'15-name-conflict-test.netdev',
'21-macvlan.netdev',
@@ -259,6 +261,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'25-bond.netdev',
'25-bond-balanced-tlb.netdev',
'25-bridge.netdev',
+ '25-bridge.network',
'25-erspan-tunnel-local-any.netdev',
'25-erspan-tunnel.netdev',
'25-fou-gretap.netdev',
@@ -367,6 +370,22 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
else:
print('ethtool does not support driver field at least for dummy interfaces, skipping test for Driver field of networkctl.')
+ def test_wait_online_any(self):
+ self.copy_unit_to_networkd_unit_path('25-bridge.netdev', '25-bridge.network', '11-dummy.netdev', '11-dummy.network')
+ self.start_networkd(0)
+
+ self.wait_online(['bridge99', 'test1:degraded'], bool_any=True)
+ self.assertTrue(self.link_exits('bridge99'))
+ self.assertTrue(self.link_exits('test1'))
+
+ output = subprocess.check_output(['networkctl', 'status', 'bridge99']).rstrip().decode('utf-8')
+ print(output)
+ self.assertRegex(output, 'State: (?:off|no-carrier) \(configuring\)')
+
+ output = subprocess.check_output(['networkctl', 'status', 'test1']).rstrip().decode('utf-8')
+ print(output)
+ self.assertRegex(output, 'State: degraded \(configured\)')
+
def test_bridge(self):
self.copy_unit_to_networkd_unit_path('25-bridge.netdev')
self.start_networkd()