diff --git a/man/networkctl.xml b/man/networkctl.xml
index 1fc94d7eda..c513d9542a 100644
--- a/man/networkctl.xml
+++ b/man/networkctl.xml
@@ -115,6 +115,13 @@
the device has a carrier, but is not yet ready for normal traffic
+
+ degraded-carrier
+
+ for bond or bridge master, one of the bonding or bridge slave network interfaces is
+ in off, no-carrier, or dormant state
+
+
carrier
@@ -125,9 +132,7 @@
degraded
- the link has carrier and addresses valid on the local link configured, or for bond
- or bridge master, one of the bonding or bridge slave network interfaces is in off,
- no-carrier, or dormant
+ the link has carrier and addresses valid on the local link configured
diff --git a/man/systemd-networkd-wait-online.service.xml b/man/systemd-networkd-wait-online.service.xml
index 95abf5b873..1b1bcf96fa 100644
--- a/man/systemd-networkd-wait-online.service.xml
+++ b/man/systemd-networkd-wait-online.service.xml
@@ -6,7 +6,8 @@
SPDX-License-Identifier: LGPL-2.1+
-->
-
+
systemd-networkd-wait-online.service
@@ -48,8 +49,8 @@
-
-
+ INTERFACE:OPERSTATE
+ INTERFACE:OPERSTATE
Network interface to wait for before deciding
if the system is online. This is useful when a system has
@@ -57,21 +58,53 @@
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
+ specified after a colon :. Please see
+ networkctl1
+ for possible operational states. If the operational state is not specified here, then
+ the value from RequiredForOnline= in the corresponding
+ .network file is used if present, and degraded otherwise.
+
-
+ INTERFACE
+
Network interfaces to be ignored when deciding
if the system is online. By default, only the loopback
interface is ignored. This option may be used more than once
to ignore multiple network interfaces.
+
-
+ OPERSTATE
+ OPERSTATE
+
+ Takes an operational state. Please see
+ 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.
+
+
+
+
+ SECS
+
Fail the service if the network is not online
by the time the timeout elapses. A timeout of 0 disables the
timeout. Defaults to 120 seconds.
+
+
+
+
+
+ Suppress log messages.
+
+
+
+
@@ -80,7 +113,8 @@
systemd1,
systemd.service5,
- systemd-networkd.service8
+ systemd-networkd.service8,
+ networkctl1
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 1a3d24aa76..0dd553adc1 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -255,15 +255,19 @@
RequiredForOnline=
- Takes a boolean. When yes, the network is deemed
- required when determining whether the system is online when running
- systemd-networkd-wait-online.
- When no, the network is ignored when checking for
- online state. Defaults to yes.
+ Takes a boolean or operational state. Please see
+ networkctl1
+ for possible operational states. When yes, the network is deemed required when
+ determining whether the system is online when running
+ systemd-networkd-wait-online. When no, the network is ignored
+ when checking for online state. When an operational state is set, yes is implied,
+ and this controls the operational state required for the network interface to be considered online.
+ Defaults to yes.
+
The network will be brought up normally in all cases, but in
the event that there is no address being assigned by DHCP or the
cable is not plugged in, the link will simply remain offline and be
- skipped automatically by systemd-networkd-wait-online
+ skipped automatically by systemd-networkd-wait-online
if RequiredForOnline=no.
diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h
index e16a9f9e30..41c8adb16b 100644
--- a/src/basic/hashmap.h
+++ b/src/basic/hashmap.h
@@ -412,9 +412,11 @@ static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) {
DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_key);
DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_key);
DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
#define _cleanup_hashmap_free_ _cleanup_(hashmap_freep)
diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c
index df5ce86842..f46a3ff788 100644
--- a/src/libsystemd/sd-network/network-util.c
+++ b/src/libsystemd/sd-network/network-util.c
@@ -3,6 +3,7 @@
#include "alloc-util.h"
#include "fd-util.h"
#include "network-util.h"
+#include "string-table.h"
#include "strv.h"
bool network_is_online(void) {
@@ -18,3 +19,16 @@ bool network_is_online(void) {
return false;
}
+
+static const char* const link_operstate_table[_LINK_OPERSTATE_MAX] = {
+ [LINK_OPERSTATE_OFF] = "off",
+ [LINK_OPERSTATE_NO_CARRIER] = "no-carrier",
+ [LINK_OPERSTATE_DORMANT] = "dormant",
+ [LINK_OPERSTATE_DEGRADED_CARRIER] = "degraded-carrier",
+ [LINK_OPERSTATE_CARRIER] = "carrier",
+ [LINK_OPERSTATE_DEGRADED] = "degraded",
+ [LINK_OPERSTATE_ENSLAVED] = "enslaved",
+ [LINK_OPERSTATE_ROUTABLE] = "routable",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(link_operstate, LinkOperationalState);
diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h
index e868bc2e27..6936fd536b 100644
--- a/src/libsystemd/sd-network/network-util.h
+++ b/src/libsystemd/sd-network/network-util.h
@@ -3,4 +3,22 @@
#include "sd-network.h"
+#include "macro.h"
+
bool network_is_online(void);
+
+typedef enum LinkOperationalState {
+ LINK_OPERSTATE_OFF,
+ LINK_OPERSTATE_NO_CARRIER,
+ LINK_OPERSTATE_DORMANT,
+ LINK_OPERSTATE_DEGRADED_CARRIER,
+ LINK_OPERSTATE_CARRIER,
+ LINK_OPERSTATE_DEGRADED,
+ LINK_OPERSTATE_ENSLAVED,
+ LINK_OPERSTATE_ROUTABLE,
+ _LINK_OPERSTATE_MAX,
+ _LINK_OPERSTATE_INVALID = -1
+} LinkOperationalState;
+
+const char* link_operstate_to_string(LinkOperationalState s) _const_;
+LinkOperationalState link_operstate_from_string(const char *s) _pure_;
diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c
index 812826fe3d..2d715043e1 100644
--- a/src/libsystemd/sd-network/sd-network.c
+++ b/src/libsystemd/sd-network/sd-network.c
@@ -164,6 +164,27 @@ _public_ int sd_network_link_get_required_for_online(int ifindex) {
return parse_boolean(s);
}
+_public_ int sd_network_link_get_required_operstate_for_online(int ifindex, char **state) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert_return(state, -EINVAL);
+
+ r = network_link_get_string(ifindex, "REQUIRED_OPER_STATE_FOR_ONLINE", &s);
+ if (r < 0) {
+ if (r != -ENODATA)
+ return r;
+
+ /* For compatibility, assuming degraded. */
+ s = strdup("degraded");
+ if (!s)
+ return -ENOMEM;
+ }
+
+ *state = TAKE_PTR(s);
+ return 0;
+}
+
_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) {
return network_link_get_string(ifindex, "LLMNR", llmnr);
}
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
index 2803f5210b..9452dabcde 100644
--- a/src/network/networkctl.c
+++ b/src/network/networkctl.c
@@ -213,7 +213,7 @@ static int list_links(int argc, char *argv[], void *userdata) {
(void) pager_open(arg_pager_flags);
if (arg_legend)
- printf("%3s %-16s %-18s %-11s %-10s\n",
+ printf("%3s %-16s %-18s %-16s %-10s\n",
"IDX",
"LINK",
"TYPE",
@@ -241,7 +241,7 @@ static int list_links(int argc, char *argv[], void *userdata) {
t = link_get_type_string(links[i].iftype, d);
- printf("%3i %-16s %-18s %s%-11s%s %s%-10s%s\n",
+ printf("%3i %-16s %-18s %s%-16s%s %s%-10s%s\n",
links[i].ifindex, links[i].name, strna(t),
on_color_operational, strna(operational_state), off_color_operational,
on_color_setup, strna(setup_state), off_color_setup);
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index de112a2ef8..bc8c180665 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -407,15 +407,14 @@ void link_update_operstate(Link *link, bool also_update_master) {
link_is_enslaved(link))
operstate = LINK_OPERSTATE_ENSLAVED;
- if (IN_SET(operstate, LINK_OPERSTATE_CARRIER, LINK_OPERSTATE_ENSLAVED, LINK_OPERSTATE_ROUTABLE)) {
+ if (operstate >= LINK_OPERSTATE_CARRIER) {
Link *slave;
HASHMAP_FOREACH(slave, link->slaves, i) {
link_update_operstate(slave, false);
- if (IN_SET(slave->operstate,
- LINK_OPERSTATE_OFF, LINK_OPERSTATE_NO_CARRIER, LINK_OPERSTATE_DORMANT))
- operstate = LINK_OPERSTATE_DEGRADED;
+ if (slave->operstate < LINK_OPERSTATE_CARRIER)
+ operstate = LINK_OPERSTATE_DEGRADED_CARRIER;
}
}
@@ -4004,6 +4003,9 @@ int link_save(Link *link) {
fprintf(f, "REQUIRED_FOR_ONLINE=%s\n",
yes_no(link->network->required_for_online));
+ fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s\n",
+ strempty(link_operstate_to_string(link->network->required_operstate_for_online)));
+
if (link->dhcp6_client) {
r = sd_dhcp6_client_get_lease(link->dhcp6_client, &dhcp6_lease);
if (r < 0 && r != -ENOMSG)
@@ -4306,15 +4308,3 @@ static const char* const link_state_table[_LINK_STATE_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(link_state, LinkState);
-
-static const char* const link_operstate_table[_LINK_OPERSTATE_MAX] = {
- [LINK_OPERSTATE_OFF] = "off",
- [LINK_OPERSTATE_NO_CARRIER] = "no-carrier",
- [LINK_OPERSTATE_DORMANT] = "dormant",
- [LINK_OPERSTATE_CARRIER] = "carrier",
- [LINK_OPERSTATE_DEGRADED] = "degraded",
- [LINK_OPERSTATE_ENSLAVED] = "enslaved",
- [LINK_OPERSTATE_ROUTABLE] = "routable",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(link_operstate, LinkOperationalState);
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index b6b7c92b4f..e65246c87d 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -15,6 +15,8 @@
#include "sd-netlink.h"
#include "list.h"
+#include "log-link.h"
+#include "network-util.h"
#include "set.h"
typedef enum LinkState {
@@ -28,18 +30,6 @@ typedef enum LinkState {
_LINK_STATE_INVALID = -1
} LinkState;
-typedef enum LinkOperationalState {
- LINK_OPERSTATE_OFF,
- LINK_OPERSTATE_NO_CARRIER,
- LINK_OPERSTATE_DORMANT,
- LINK_OPERSTATE_CARRIER,
- LINK_OPERSTATE_DEGRADED,
- LINK_OPERSTATE_ENSLAVED,
- LINK_OPERSTATE_ROUTABLE,
- _LINK_OPERSTATE_MAX,
- _LINK_OPERSTATE_INVALID = -1
-} LinkOperationalState;
-
typedef struct Manager Manager;
typedef struct Network Network;
typedef struct Address Address;
@@ -180,9 +170,6 @@ int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link);
const char* link_state_to_string(LinkState s) _const_;
LinkState link_state_from_string(const char *s) _pure_;
-const char* link_operstate_to_string(LinkOperationalState s) _const_;
-LinkOperationalState link_operstate_from_string(const char *s) _pure_;
-
extern const sd_bus_vtable link_vtable[];
int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
@@ -193,30 +180,6 @@ uint32_t link_get_vrf_table(Link *link);
uint32_t link_get_dhcp_route_table(Link *link);
uint32_t link_get_ipv6_accept_ra_route_table(Link *link);
-/* Macros which append INTERFACE= to the message */
-
-#define log_link_full(link, level, error, ...) \
- ({ \
- const Link *_l = (link); \
- _l ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _l->ifname, NULL, NULL, ##__VA_ARGS__) : \
- log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
- }) \
-
-#define log_link_debug(link, ...) log_link_full(link, LOG_DEBUG, 0, ##__VA_ARGS__)
-#define log_link_info(link, ...) log_link_full(link, LOG_INFO, 0, ##__VA_ARGS__)
-#define log_link_notice(link, ...) log_link_full(link, LOG_NOTICE, 0, ##__VA_ARGS__)
-#define log_link_warning(link, ...) log_link_full(link, LOG_WARNING, 0, ##__VA_ARGS__)
-#define log_link_error(link, ...) log_link_full(link, LOG_ERR, 0, ##__VA_ARGS__)
-
-#define log_link_debug_errno(link, error, ...) log_link_full(link, LOG_DEBUG, error, ##__VA_ARGS__)
-#define log_link_info_errno(link, error, ...) log_link_full(link, LOG_INFO, error, ##__VA_ARGS__)
-#define log_link_notice_errno(link, error, ...) log_link_full(link, LOG_NOTICE, error, ##__VA_ARGS__)
-#define log_link_warning_errno(link, error, ...) log_link_full(link, LOG_WARNING, error, ##__VA_ARGS__)
-#define log_link_error_errno(link, error, ...) log_link_full(link, LOG_ERR, error, ##__VA_ARGS__)
-
-#define LOG_LINK_MESSAGE(link, fmt, ...) "MESSAGE=%s: " fmt, (link)->ifname, ##__VA_ARGS__
-#define LOG_LINK_INTERFACE(link) "INTERFACE=%s", (link)->ifname
-
#define ADDRESS_FMT_VAL(address) \
be32toh((address).s_addr) >> 24, \
(be32toh((address).s_addr) >> 16) & 0xFFu, \
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 51123d12ef..73a7fcdf08 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -36,7 +36,7 @@ Link.ARP, config_parse_tristate,
Link.Multicast, config_parse_tristate, 0, offsetof(Network, multicast)
Link.AllMulticast, config_parse_tristate, 0, offsetof(Network, allmulticast)
Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged)
-Link.RequiredForOnline, config_parse_bool, 0, offsetof(Network, required_for_online)
+Link.RequiredForOnline, config_parse_required_for_online, 0, 0
Network.Description, config_parse_string, 0, offsetof(Network, description)
Network.Bridge, config_parse_ifname, 0, offsetof(Network, bridge_name)
Network.Bond, config_parse_ifname, 0, offsetof(Network, bond_name)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 5f12df907a..fe26720103 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -366,6 +366,7 @@ int network_load_one(Manager *manager, const char *filename) {
.name = TAKE_PTR(name),
.required_for_online = true,
+ .required_operstate_for_online = LINK_OPERSTATE_DEGRADED,
.dhcp = ADDRESS_FAMILY_NO,
.dhcp_use_ntp = true,
.dhcp_use_dns = true,
@@ -1671,3 +1672,46 @@ int config_parse_iaid(const char *unit,
return 0;
}
+
+int config_parse_required_for_online(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = data;
+ LinkOperationalState s;
+ bool required = true;
+ int r;
+
+ if (isempty(rvalue)) {
+ network->required_for_online = true;
+ network->required_operstate_for_online = LINK_OPERSTATE_DEGRADED;
+ return 0;
+ }
+
+ s = link_operstate_from_string(rvalue);
+ if (s < 0) {
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse %s= setting, ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ required = r;
+ s = LINK_OPERSTATE_DEGRADED;
+ }
+
+ network->required_for_online = required;
+ network->required_operstate_for_online = s;
+
+ return 0;
+}
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 2f7b6133fd..2561bba1bc 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -241,6 +241,7 @@ struct Network {
bool iaid_set;
bool required_for_online; /* Is this network required to be considered online? */
+ LinkOperationalState required_operstate_for_online;
LLDPMode lldp_mode; /* LLDP reception */
LLDPEmit lldp_emit; /* LLDP transmission */
@@ -324,6 +325,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_section_route_table);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class);
CONFIG_PARSER_PROTOTYPE(config_parse_ntp);
CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
+CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online);
/* Legacy IPv4LL support */
CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll);
diff --git a/src/network/wait-online/link.c b/src/network/wait-online/link.c
index 87d54fe654..a13373f7d7 100644
--- a/src/network/wait-online/link.c
+++ b/src/network/wait-online/link.c
@@ -10,6 +10,7 @@
int link_new(Manager *m, Link **ret, int ifindex, const char *ifname) {
_cleanup_(link_freep) Link *l = NULL;
+ _cleanup_free_ char *n = NULL;
int r;
assert(m);
@@ -23,30 +24,33 @@ int link_new(Manager *m, Link **ret, int ifindex, const char *ifname) {
if (r < 0)
return r;
- l = new0(Link, 1);
+ n = strdup(ifname);
+ if (!n)
+ return -ENOMEM;
+
+ l = new(Link, 1);
if (!l)
return -ENOMEM;
- l->manager = m;
-
- l->ifname = strdup(ifname);
- if (!l->ifname)
- return -ENOMEM;
+ *l = (Link) {
+ .manager = m,
+ .ifname = TAKE_PTR(n),
+ .ifindex = ifindex,
+ .required_operstate = LINK_OPERSTATE_DEGRADED,
+ };
r = hashmap_put(m->links_by_name, l->ifname, l);
if (r < 0)
return r;
- l->ifindex = ifindex;
-
r = hashmap_put(m->links, INT_TO_PTR(ifindex), l);
if (r < 0)
return r;
if (ret)
*ret = l;
- l = NULL;
+ TAKE_PTR(l);
return 0;
}
@@ -60,6 +64,7 @@ Link *link_free(Link *l) {
hashmap_remove(l->manager->links_by_name, l->ifname);
}
+ free(l->state);
free(l->ifname);
return mfree(l);
}
@@ -87,9 +92,8 @@ int link_update_rtnl(Link *l, sd_netlink_message *m) {
if (!new_ifname)
return -ENOMEM;
- hashmap_remove(l->manager->links_by_name, l->ifname);
- free(l->ifname);
- l->ifname = new_ifname;
+ assert_se(hashmap_remove(l->manager->links_by_name, l->ifname) == l);
+ free_and_replace(l->ifname, new_ifname);
r = hashmap_put(l->manager->links_by_name, l->ifname, l);
if (r < 0)
@@ -100,17 +104,49 @@ int link_update_rtnl(Link *l, sd_netlink_message *m) {
}
int link_update_monitor(Link *l) {
+ _cleanup_free_ char *operstate = NULL, *required_operstate = NULL, *state = NULL;
+ LinkOperationalState s;
+ int r, ret = 0;
+
assert(l);
+ assert(l->ifname);
- l->required_for_online = sd_network_link_get_required_for_online(l->ifindex) != 0;
+ r = sd_network_link_get_required_for_online(l->ifindex);
+ if (r < 0)
+ ret = log_link_debug_errno(l, r, "Failed to determine whether the link is required for online or not, "
+ "ignoring: %m");
+ else
+ l->required_for_online = r > 0;
- l->operational_state = mfree(l->operational_state);
+ r = sd_network_link_get_required_operstate_for_online(l->ifindex, &required_operstate);
+ if (r < 0)
+ ret = log_link_debug_errno(l, r, "Failed to get required operational state, ignoring: %m");
+ else {
+ s = link_operstate_from_string(required_operstate);
+ if (s < 0)
+ ret = log_link_debug_errno(l, SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse required operational state, ignoring: %m");
+ else
+ l->required_operstate = s;
+ }
- sd_network_link_get_operational_state(l->ifindex, &l->operational_state);
+ r = sd_network_link_get_operational_state(l->ifindex, &operstate);
+ if (r < 0)
+ ret = log_link_debug_errno(l, r, "Failed to get operational state, ignoring: %m");
+ else {
+ s = link_operstate_from_string(operstate);
+ if (s < 0)
+ ret = log_link_debug_errno(l, SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse operational state, ignoring: %m");
+ else
+ l->operational_state = s;
+ }
- l->state = mfree(l->state);
+ r = sd_network_link_get_setup_state(l->ifindex, &state);
+ if (r < 0)
+ ret = log_link_debug_errno(l, r, "Failed to get setup state, ignoring: %m");
+ else
+ free_and_replace(l->state, state);
- sd_network_link_get_setup_state(l->ifindex, &l->state);
-
- return 0;
+ return ret;
}
diff --git a/src/network/wait-online/link.h b/src/network/wait-online/link.h
index 57e8f12e03..d58129dfe8 100644
--- a/src/network/wait-online/link.h
+++ b/src/network/wait-online/link.h
@@ -3,6 +3,9 @@
#include "sd-netlink.h"
+#include "log-link.h"
+#include "network-util.h"
+
typedef struct Link Link;
typedef struct Manager Manager;
@@ -14,7 +17,8 @@ struct Link {
unsigned flags;
bool required_for_online;
- char *operational_state;
+ LinkOperationalState required_operstate;
+ LinkOperationalState operational_state;
char *state;
};
diff --git a/src/network/wait-online/manager.c b/src/network/wait-online/manager.c
index 67218b6db3..eb551f116a 100644
--- a/src/network/wait-online/manager.c
+++ b/src/network/wait-online/manager.c
@@ -13,7 +13,7 @@
#include "time-util.h"
#include "util.h"
-bool manager_ignore_link(Manager *m, Link *link) {
+static bool manager_ignore_link(Manager *m, Link *link) {
assert(m);
assert(link);
@@ -22,7 +22,7 @@ bool manager_ignore_link(Manager *m, Link *link) {
return true;
/* if interfaces are given on the command line, ignore all others */
- if (m->interfaces && !strv_contains(m->interfaces, link->ifname))
+ if (m->interfaces && !hashmap_contains(m->interfaces, link->ifname))
return true;
if (!link->required_for_online)
@@ -32,45 +32,73 @@ bool manager_ignore_link(Manager *m, Link *link) {
return strv_fnmatch(m->ignore, link->ifname, 0);
}
-bool manager_all_configured(Manager *m) {
- Iterator i;
- Link *l;
- char **ifname;
- bool one_ready = false;
+static int manager_link_is_online(Manager *m, Link *l, LinkOperationalState s) {
+ /* This returns the following:
+ * -EAGAIN: not processed by udev or networkd
+ * 0: operstate is not enough
+ * 1: online */
- /* wait for all the links given on the command line to appear */
- STRV_FOREACH(ifname, m->interfaces) {
- l = hashmap_get(m->links_by_name, *ifname);
- if (!l) {
- log_debug("still waiting for %s", *ifname);
- return false;
- }
+ if (!l->state)
+ return log_link_debug_errno(l, SYNTHETIC_ERRNO(EAGAIN),
+ "link has not yet been processed by udev");
+
+ if (STR_IN_SET(l->state, "configuring", "pending"))
+ return log_link_debug_errno(l, SYNTHETIC_ERRNO(EAGAIN),
+ "link is being processed by networkd");
+
+ if (s < 0)
+ s = m->required_operstate >= 0 ? m->required_operstate : l->required_operstate;
+
+ if (l->operational_state < s) {
+ log_link_debug(l, "Operational state '%s' is below '%s'",
+ link_operstate_to_string(l->operational_state),
+ link_operstate_to_string(s));
+ return 0;
}
+ return 1;
+}
+
+bool manager_all_configured(Manager *m) {
+ bool one_ready = false;
+ Iterator i;
+ const char *ifname;
+ void *p;
+ 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);
+
+ l = hashmap_get(m->links_by_name, ifname);
+ if (!l) {
+ log_debug("still waiting for %s", ifname);
+ return false;
+ }
+
+ if (manager_link_is_online(m, l, s) <= 0)
+ return false;
+ }
+
+ 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 */
+ * and at least one link to gain a carrier */
HASHMAP_FOREACH(l, m->links, i) {
if (manager_ignore_link(m, l)) {
- log_info("ignoring: %s", l->ifname);
+ log_link_info(l, "link is ignored");
continue;
}
- if (!l->state) {
- log_debug("link %s has not yet been processed by udev",
- l->ifname);
+ r = manager_link_is_online(m, l, _LINK_OPERSTATE_INVALID);
+ if (r < 0)
return false;
- }
-
- if (STR_IN_SET(l->state, "configuring", "pending")) {
- log_debug("link %s is being processed by networkd",
- l->ifname);
- return false;
- }
-
- if (l->operational_state &&
- STR_IN_SET(l->operational_state, "degraded", "routable"))
+ if (r > 0)
/* we wait for at least one link to be ready,
- regardless of who manages it */
+ * regardless of who manages it */
one_ready = true;
}
@@ -120,21 +148,21 @@ static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *
r = link_new(m, &l, ifindex, ifname);
if (r < 0)
return log_error_errno(r, "Failed to create link object: %m");
-
- r = link_update_monitor(l);
- if (r < 0)
- return log_error_errno(r, "Failed to initialize link object: %m");
}
r = link_update_rtnl(l, mm);
if (r < 0)
- return log_warning_errno(r, "Failed to process RTNL link message: %m");;
+ log_link_warning_errno(l, r, "Failed to process RTNL link message, ignoring: %m");
+
+ r = link_update_monitor(l);
+ if (r < 0)
+ log_link_warning_errno(l, r, "Failed to update link state, ignoring: %m");
break;
case RTM_DELLINK:
if (l) {
- log_debug("Removing link %i", l->ifindex);
+ log_link_debug(l, "Removing link");
link_free(l);
}
@@ -217,7 +245,7 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *
HASHMAP_FOREACH(l, m->links, i) {
r = link_update_monitor(l);
if (r < 0)
- log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex);
+ log_link_warning_errno(l, r, "Failed to update monitor information: %m");
}
if (manager_all_configured(m))
@@ -251,18 +279,22 @@ static int manager_network_monitor_listen(Manager *m) {
return 0;
}
-int manager_new(Manager **ret, char **interfaces, char **ignore, usec_t timeout) {
+int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
+ LinkOperationalState required_operstate, usec_t timeout) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
assert(ret);
- m = new0(Manager, 1);
+ m = new(Manager, 1);
if (!m)
return -ENOMEM;
- m->interfaces = interfaces;
- m->ignore = ignore;
+ *m = (Manager) {
+ .interfaces = interfaces,
+ .ignore = ignore,
+ .required_operstate = required_operstate,
+ };
r = sd_event_default(&m->event);
if (r < 0)
diff --git a/src/network/wait-online/manager.h b/src/network/wait-online/manager.h
index 0fde272ddb..eb81e106e1 100644
--- a/src/network/wait-online/manager.h
+++ b/src/network/wait-online/manager.h
@@ -6,6 +6,7 @@
#include "sd-network.h"
#include "hashmap.h"
+#include "network-util.h"
typedef struct Manager Manager;
typedef struct Link Link;
@@ -14,9 +15,12 @@ struct Manager {
Hashmap *links;
Hashmap *links_by_name;
- char **interfaces;
+ /* Do not free the two members below. */
+ Hashmap *interfaces;
char **ignore;
+ LinkOperationalState required_operstate;
+
sd_netlink *rtnl;
sd_event_source *rtnl_event_source;
@@ -27,9 +31,9 @@ struct Manager {
};
void manager_free(Manager *m);
-int manager_new(Manager **ret, char **interfaces, char **ignore, usec_t timeout);
+int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
+ LinkOperationalState required_operstate, usec_t timeout);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
bool manager_all_configured(Manager *m);
-bool manager_ignore_link(Manager *m, Link *link);
diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c
index 71b6cf6b87..a5eddf3ca2 100644
--- a/src/network/wait-online/wait-online.c
+++ b/src/network/wait-online/wait-online.c
@@ -9,14 +9,16 @@
#include "manager.h"
#include "pretty-print.h"
#include "signal-util.h"
+#include "socket-util.h"
#include "strv.h"
static bool arg_quiet = false;
static usec_t arg_timeout = 120 * USEC_PER_SEC;
-static char **arg_interfaces = NULL;
+static Hashmap *arg_interfaces = NULL;
static char **arg_ignore = NULL;
+static LinkOperationalState arg_required_operstate = _LINK_OPERSTATE_INVALID;
-STATIC_DESTRUCTOR_REGISTER(arg_interfaces, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_free_free_keyp);
STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep);
static int help(void) {
@@ -32,8 +34,11 @@ static int help(void) {
" -h --help Show this help\n"
" --version Print version string\n"
" -q --quiet Do not show status information\n"
- " -i --interface=INTERFACE Block until at least these interfaces have appeared\n"
+ " -i --interface=INTERFACE[:OPERSTATE]\n"
+ " Block until at least these interfaces have appeared\n"
" --ignore=INTERFACE Don't take these interfaces into account\n"
+ " -o --operational-state=OPERSTATE\n"
+ " Required operational state\n"
" --timeout=SECS Maximum time to wait for network connectivity\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
@@ -43,6 +48,52 @@ static int help(void) {
return 0;
}
+static int parse_interface_with_operstate(const char *str) {
+ _cleanup_free_ char *ifname = NULL;
+ LinkOperationalState s;
+ const char *p;
+ int r;
+
+ assert(str);
+
+ p = strchr(str, ':');
+ if (p) {
+ if (isempty(p + 1))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Operational state is empty.");
+
+ s = link_operstate_from_string(p + 1);
+ if (s < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid operational state '%s'", p + 1);
+
+ ifname = strndup(optarg, p - optarg);
+ } else {
+ s = _LINK_OPERSTATE_INVALID;
+ ifname = strdup(str);
+ }
+ if (!ifname)
+ return log_oom();
+
+ if (!ifname_valid(ifname))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid interface name '%s'", ifname);
+
+ r = hashmap_ensure_allocated(&arg_interfaces, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = hashmap_put(arg_interfaces, ifname, INT_TO_PTR(s));
+ if (r < 0)
+ return log_error_errno(r, "Failed to store interface name: %m");
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Interface name %s is already specified", ifname);
+
+ TAKE_PTR(ifname);
+ return 0;
+}
+
static int parse_argv(int argc, char *argv[]) {
enum {
@@ -52,12 +103,13 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "quiet", no_argument, NULL, 'q' },
- { "interface", required_argument, NULL, 'i' },
- { "ignore", required_argument, NULL, ARG_IGNORE },
- { "timeout", required_argument, NULL, ARG_TIMEOUT },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "quiet", no_argument, NULL, 'q' },
+ { "interface", required_argument, NULL, 'i' },
+ { "ignore", required_argument, NULL, ARG_IGNORE },
+ { "operational-state", required_argument, NULL, 'o' },
+ { "timeout", required_argument, NULL, ARG_TIMEOUT },
{}
};
@@ -66,7 +118,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "+hi:q", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hi:qo:", options, NULL)) >= 0)
switch (c) {
@@ -82,9 +134,9 @@ static int parse_argv(int argc, char *argv[]) {
return version();
case 'i':
- if (strv_extend(&arg_interfaces, optarg) < 0)
- return log_oom();
-
+ r = parse_interface_with_operstate(optarg);
+ if (r < 0)
+ return r;
break;
case ARG_IGNORE:
@@ -93,11 +145,21 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case 'o': {
+ LinkOperationalState s;
+
+ s = link_operstate_from_string(optarg);
+ if (s < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid operational state '%s'", optarg);
+
+ arg_required_operstate = s;
+ break;
+ }
case ARG_TIMEOUT:
r = parse_sec(optarg, &arg_timeout);
if (r < 0)
return r;
-
break;
case '?':
@@ -124,11 +186,11 @@ static int run(int argc, char *argv[]) {
return r;
if (arg_quiet)
- log_set_max_level(LOG_WARNING);
+ log_set_max_level(LOG_ERR);
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
- r = manager_new(&m, arg_interfaces, arg_ignore, arg_timeout);
+ r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_timeout);
if (r < 0)
return log_error_errno(r, "Could not create manager: %m");
diff --git a/src/shared/log-link.h b/src/shared/log-link.h
new file mode 100644
index 0000000000..99efc0cbab
--- /dev/null
+++ b/src/shared/log-link.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "log.h"
+
+/*
+ * The following macros append INTERFACE= to the message.
+ * The macros require a struct named 'Link' which contains 'char *ifname':
+ *
+ * typedef struct Link {
+ * char *ifname;
+ * } Link;
+ *
+ * See, network/networkd-link.h for example.
+ */
+
+#define log_link_full(link, level, error, ...) \
+ ({ \
+ const Link *_l = (link); \
+ (_l && _l->ifname) ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _l->ifname, NULL, NULL, ##__VA_ARGS__) : \
+ log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
+ }) \
+
+#define log_link_debug(link, ...) log_link_full(link, LOG_DEBUG, 0, ##__VA_ARGS__)
+#define log_link_info(link, ...) log_link_full(link, LOG_INFO, 0, ##__VA_ARGS__)
+#define log_link_notice(link, ...) log_link_full(link, LOG_NOTICE, 0, ##__VA_ARGS__)
+#define log_link_warning(link, ...) log_link_full(link, LOG_WARNING, 0, ##__VA_ARGS__)
+#define log_link_error(link, ...) log_link_full(link, LOG_ERR, 0, ##__VA_ARGS__)
+
+#define log_link_debug_errno(link, error, ...) log_link_full(link, LOG_DEBUG, error, ##__VA_ARGS__)
+#define log_link_info_errno(link, error, ...) log_link_full(link, LOG_INFO, error, ##__VA_ARGS__)
+#define log_link_notice_errno(link, error, ...) log_link_full(link, LOG_NOTICE, error, ##__VA_ARGS__)
+#define log_link_warning_errno(link, error, ...) log_link_full(link, LOG_WARNING, error, ##__VA_ARGS__)
+#define log_link_error_errno(link, error, ...) log_link_full(link, LOG_ERR, error, ##__VA_ARGS__)
+
+#define LOG_LINK_MESSAGE(link, fmt, ...) "MESSAGE=%s: " fmt, (link)->ifname, ##__VA_ARGS__
+#define LOG_LINK_INTERFACE(link) "INTERFACE=%s", (link)->ifname
diff --git a/src/shared/meson.build b/src/shared/meson.build
index 1a5ba8c0f4..83a9df24cb 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -93,6 +93,7 @@ shared_sources = files('''
json.h
lockfile-util.c
lockfile-util.h
+ log-link.h
logs-show.c
logs-show.h
loop-util.c
diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h
index cc6bca9f5e..166c30185a 100644
--- a/src/systemd/sd-network.h
+++ b/src/systemd/sd-network.h
@@ -89,6 +89,7 @@ int sd_network_link_get_setup_state(int ifindex, char **state);
* -ENODATA: networkd is not aware of the link
*/
int sd_network_link_get_operational_state(int ifindex, char **state);
+int sd_network_link_get_required_operstate_for_online(int ifindex, char **state);
/* Indicates whether the network is relevant to being online.
* Possible return codes:
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 856960ad65..1614af29b0 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -1200,7 +1200,7 @@ class NetworkdNetWorkBondTests(unittest.TestCase, Utilities):
output = subprocess.check_output(['networkctl', 'status', 'bond99']).rstrip().decode('utf-8')
print(output)
- self.assertRegex(output, 'State: degraded \(configured\)')
+ self.assertRegex(output, 'State: degraded-carrier \(configured\)')
self.assertEqual(subprocess.call(['ip', 'link', 'set', 'dummy98', 'up']), 0)
time.sleep(2)
@@ -1219,7 +1219,7 @@ class NetworkdNetWorkBondTests(unittest.TestCase, Utilities):
self.assertEqual(subprocess.call(['ip', 'link', 'set', 'dummy98', 'down']), 0)
self.assertEqual(subprocess.call(['ip', 'link', 'set', 'test1', 'down']), 0)
- time.sleep(2)
+ time.sleep(5)
output = subprocess.check_output(['networkctl', 'status', 'dummy98']).rstrip().decode('utf-8')
print(output)
@@ -1231,7 +1231,7 @@ class NetworkdNetWorkBondTests(unittest.TestCase, Utilities):
output = subprocess.check_output(['networkctl', 'status', 'bond99']).rstrip().decode('utf-8')
print(output)
- self.assertRegex(output, 'State: degraded \(configured\)')
+ self.assertRegex(output, 'State: no-carrier \(configured\)')
class NetworkdNetWorkBridgeTests(unittest.TestCase, Utilities):
links = [
@@ -1313,7 +1313,7 @@ class NetworkdNetWorkBridgeTests(unittest.TestCase, Utilities):
time.sleep(3)
output = subprocess.check_output(['networkctl', 'status', 'bridge99']).rstrip().decode('utf-8')
- self.assertRegex(output, 'State: degraded \(configured\)')
+ self.assertRegex(output, 'State: degraded-carrier \(configured\)')
self.assertEqual(subprocess.call(['ip', 'link', 'del', 'dummy98']), 0)
time.sleep(3)