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:OPERSTATE INTERFACE: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()