diff --git a/man/systemd-networkd-wait-online.service.xml b/man/systemd-networkd-wait-online.service.xml
index 6d2c71d8c7..31aa02218a 100644
--- a/man/systemd-networkd-wait-online.service.xml
+++ b/man/systemd-networkd-wait-online.service.xml
@@ -85,6 +85,34 @@
+
+
+
+
+ Waiting for an IPv4 address of each network interface to be configured. If this
+ option is specified with , then
+ systemd-networkd-wait-online exits with success when at least one interface
+ becomes online and has an IPv4 address. The option is applied only for the operational state
+ degraded or above. If neither nor
+ is specified, then the value from
+ RequiredFamilyForOnline= in the corresponding .network
+ file is used if present.
+
+
+
+
+
+
+ Waiting for an IPv6 address of each network interface to be configured. If this
+ option is specified with , then
+ systemd-networkd-wait-online exits with success when at least one interface
+ becomes online and has an IPv6 address. The option is applied only for the operational state
+ degraded or above. If neither nor
+ is specified, then the value from
+ RequiredFamilyForOnline= in the corresponding .network
+ file is used if present.
+
+
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index a49e6011e3..5ab1e9f0a8 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -229,6 +229,18 @@
if RequiredForOnline=no.
+
+ RequiredFamilyForOnline=
+
+ Specifies an address family. When specified,
+ systemd-networkd-wait-online waits for at least one routable or link-local
+ IP address in the family should be configured on the link. Takes one of
+ ipv4, ipv6, both, or
+ any. Defaults to any. Note that this will be used only
+ when RequiredForOnline= is true, or its minimum operational state is
+ degraded or above. Otherwise, it will be ignored.
+
+
ActivationPolicy=
diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c
index acf7500970..c1d3a9f6a2 100644
--- a/src/libsystemd/sd-network/network-util.c
+++ b/src/libsystemd/sd-network/network-util.c
@@ -56,6 +56,15 @@ static const char* const link_carrier_state_table[_LINK_CARRIER_STATE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(link_carrier_state, LinkCarrierState);
+static const char* const link_required_address_family_table[_ADDRESS_FAMILY_MAX] = {
+ [ADDRESS_FAMILY_NO] = "any",
+ [ADDRESS_FAMILY_IPV4] = "ipv4",
+ [ADDRESS_FAMILY_IPV6] = "ipv6",
+ [ADDRESS_FAMILY_YES] = "both",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(link_required_address_family, AddressFamily);
+
static const char* const link_address_state_table[_LINK_ADDRESS_STATE_MAX] = {
[LINK_ADDRESS_STATE_OFF] = "off",
[LINK_ADDRESS_STATE_DEGRADED] = "degraded",
diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h
index dd6a7dbe88..3bd10c42de 100644
--- a/src/libsystemd/sd-network/network-util.h
+++ b/src/libsystemd/sd-network/network-util.h
@@ -11,6 +11,16 @@
bool network_is_online(void);
+typedef enum AddressFamily {
+ /* This is a bitmask, though it usually doesn't feel that way! */
+ ADDRESS_FAMILY_NO = 0,
+ ADDRESS_FAMILY_IPV4 = 1 << 0,
+ ADDRESS_FAMILY_IPV6 = 1 << 1,
+ ADDRESS_FAMILY_YES = ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_IPV6,
+ _ADDRESS_FAMILY_MAX,
+ _ADDRESS_FAMILY_INVALID = -EINVAL,
+} AddressFamily;
+
typedef enum LinkOperationalState {
LINK_OPERSTATE_MISSING,
LINK_OPERSTATE_OFF,
@@ -50,6 +60,9 @@ LinkOperationalState link_operstate_from_string(const char *s) _pure_;
const char* link_carrier_state_to_string(LinkCarrierState s) _const_;
LinkCarrierState link_carrier_state_from_string(const char *s) _pure_;
+const char* link_required_address_family_to_string(AddressFamily s) _const_;
+AddressFamily link_required_address_family_from_string(const char *s) _pure_;
+
const char* link_address_state_to_string(LinkAddressState s) _const_;
LinkAddressState link_address_state_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 ccdfde9137..b190e8f881 100644
--- a/src/libsystemd/sd-network/sd-network.c
+++ b/src/libsystemd/sd-network/sd-network.c
@@ -48,6 +48,14 @@ _public_ int sd_network_get_address_state(char **state) {
return network_get_string("ADDRESS_STATE", state);
}
+_public_ int sd_network_get_ipv4_address_state(char **state) {
+ return network_get_string("IPV4_ADDRESS_STATE", state);
+}
+
+_public_ int sd_network_get_ipv6_address_state(char **state) {
+ return network_get_string("IPV6_ADDRESS_STATE", state);
+}
+
static int network_get_strv(const char *key, char ***ret) {
_cleanup_strv_free_ char **a = NULL;
_cleanup_free_ char *s = NULL;
@@ -160,6 +168,26 @@ _public_ int sd_network_link_get_operational_state(int ifindex, char **state) {
return network_link_get_string(ifindex, "OPER_STATE", state);
}
+_public_ int sd_network_link_get_required_family_for_online(int ifindex, char **state) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert_return(state, -EINVAL);
+
+ r = network_link_get_string(ifindex, "REQUIRED_FAMILY_FOR_ONLINE", &s);
+ if (r < 0) {
+ if (r != -ENODATA)
+ return r;
+
+ s = strdup("any");
+ if (!s)
+ return -ENOMEM;
+ }
+
+ *state = TAKE_PTR(s);
+ return 0;
+}
+
_public_ int sd_network_link_get_carrier_state(int ifindex, char **state) {
return network_link_get_string(ifindex, "CARRIER_STATE", state);
}
@@ -168,6 +196,14 @@ _public_ int sd_network_link_get_address_state(int ifindex, char **state) {
return network_link_get_string(ifindex, "ADDRESS_STATE", state);
}
+_public_ int sd_network_link_get_ipv4_address_state(int ifindex, char **state) {
+ return network_link_get_string(ifindex, "IPV4_ADDRESS_STATE", state);
+}
+
+_public_ int sd_network_link_get_ipv6_address_state(int ifindex, char **state) {
+ return network_link_get_string(ifindex, "IPV6_ADDRESS_STATE", state);
+}
+
_public_ int sd_network_link_get_dhcp6_client_iaid_string(int ifindex, char **iaid) {
return network_link_get_string(ifindex, "DHCP6_CLIENT_IAID", iaid);
}
diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c
index 16058c6fcf..a999b05845 100644
--- a/src/network/networkd-link-bus.c
+++ b/src/network/networkd-link-bus.c
@@ -684,6 +684,8 @@ const sd_bus_vtable link_vtable[] = {
SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state, offsetof(Link, operstate), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("CarrierState", "s", property_get_carrier_state, offsetof(Link, carrier_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("AddressState", "s", property_get_address_state, offsetof(Link, address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IPv4AddressState", "s", property_get_address_state, offsetof(Link, ipv4_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IPv6AddressState", "s", property_get_address_state, offsetof(Link, ipv6_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("AdministrativeState", "s", property_get_administrative_state, offsetof(Link, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("BitRates", "(tt)", property_get_bit_rates, 0, 0),
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 6c08203c54..65a6410a5a 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include
#include "alloc-util.h"
@@ -163,12 +164,25 @@ static void link_update_master_operstate(Link *link, NetDev *netdev) {
link_update_operstate(master, true);
}
+static LinkAddressState address_state_from_scope(uint8_t scope) {
+ if (scope < RT_SCOPE_SITE)
+ /* universally accessible addresses found */
+ return LINK_ADDRESS_STATE_ROUTABLE;
+
+ if (scope < RT_SCOPE_HOST)
+ /* only link or site local addresses found */
+ return LINK_ADDRESS_STATE_DEGRADED;
+
+ /* no useful addresses found */
+ return LINK_ADDRESS_STATE_OFF;
+}
+
void link_update_operstate(Link *link, bool also_update_master) {
LinkOperationalState operstate;
LinkCarrierState carrier_state;
- LinkAddressState address_state;
+ LinkAddressState ipv4_address_state, ipv6_address_state, address_state;
_cleanup_strv_free_ char **p = NULL;
- uint8_t scope = RT_SCOPE_NOWHERE;
+ uint8_t ipv4_scope = RT_SCOPE_NOWHERE, ipv6_scope = RT_SCOPE_NOWHERE, scope;
bool changed = false;
Address *address;
@@ -201,8 +215,11 @@ void link_update_operstate(Link *link, bool also_update_master) {
if (!address_is_ready(address))
continue;
- if (address->scope < scope)
- scope = address->scope;
+ if (address->family == AF_INET && address->scope < ipv4_scope)
+ ipv4_scope = address->scope;
+
+ if (address->family == AF_INET6 && address->scope < ipv6_scope)
+ ipv6_scope = address->scope;
}
/* for operstate we also take foreign addresses into account */
@@ -210,19 +227,18 @@ void link_update_operstate(Link *link, bool also_update_master) {
if (!address_is_ready(address))
continue;
- if (address->scope < scope)
- scope = address->scope;
+ if (address->family == AF_INET && address->scope < ipv4_scope)
+ ipv4_scope = address->scope;
+
+ if (address->family == AF_INET6 && address->scope < ipv6_scope)
+ ipv6_scope = address->scope;
}
- if (scope < RT_SCOPE_SITE)
- /* universally accessible addresses found */
- address_state = LINK_ADDRESS_STATE_ROUTABLE;
- else if (scope < RT_SCOPE_HOST)
- /* only link or site local addresses found */
- address_state = LINK_ADDRESS_STATE_DEGRADED;
- else
- /* no useful addresses found */
- address_state = LINK_ADDRESS_STATE_OFF;
+ ipv4_address_state = address_state_from_scope(ipv4_scope);
+ ipv6_address_state = address_state_from_scope(ipv6_scope);
+
+ scope = MIN(ipv4_scope, ipv6_scope);
+ address_state = address_state_from_scope(scope);
/* Mapping of address and carrier state vs operational state
* carrier state
@@ -256,6 +272,20 @@ void link_update_operstate(Link *link, bool also_update_master) {
log_oom();
}
+ if (link->ipv4_address_state != ipv4_address_state) {
+ link->ipv4_address_state = ipv4_address_state;
+ changed = true;
+ if (strv_extend(&p, "IPv4AddressState") < 0)
+ log_oom();
+ }
+
+ if (link->ipv6_address_state != ipv6_address_state) {
+ link->ipv6_address_state = ipv6_address_state;
+ changed = true;
+ if (strv_extend(&p, "IPv6AddressState") < 0)
+ log_oom();
+ }
+
if (link->operstate != operstate) {
link->operstate = operstate;
changed = true;
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index d3353a1c4f..a8bdd971f5 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -75,6 +75,8 @@ typedef struct Link {
LinkOperationalState operstate;
LinkCarrierState carrier_state;
LinkAddressState address_state;
+ LinkAddressState ipv4_address_state;
+ LinkAddressState ipv6_address_state;
unsigned address_messages;
unsigned address_remove_messages;
diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c
index 138d765414..0acaeb2bed 100644
--- a/src/network/networkd-manager-bus.c
+++ b/src/network/networkd-manager-bus.c
@@ -235,6 +235,8 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state, offsetof(Manager, operational_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("CarrierState", "s", property_get_carrier_state, offsetof(Manager, carrier_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("AddressState", "s", property_get_address_state, offsetof(Manager, address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IPv4AddressState", "s", property_get_address_state, offsetof(Manager, ipv4_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IPv6AddressState", "s", property_get_address_state, offsetof(Manager, ipv6_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_METHOD_WITH_ARGS("ListLinks",
SD_BUS_NO_ARGS,
diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h
index 7f630fccc7..e78b57b936 100644
--- a/src/network/networkd-manager.h
+++ b/src/network/networkd-manager.h
@@ -39,6 +39,8 @@ struct Manager {
LinkOperationalState operational_state;
LinkCarrierState carrier_state;
LinkAddressState address_state;
+ LinkAddressState ipv4_address_state;
+ LinkAddressState ipv6_address_state;
Hashmap *links;
Hashmap *netdevs;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 8ebeec8640..6c37e32453 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -67,6 +67,7 @@ Link.Promiscuous, config_parse_tristate,
Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged)
Link.ActivationPolicy, config_parse_activation_policy, 0, offsetof(Network, activation_policy)
Link.RequiredForOnline, config_parse_required_for_online, 0, 0
+Link.RequiredFamilyForOnline, config_parse_required_family_for_online, 0, offsetof(Network, required_family_for_online)
SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, 0
SR-IOV.VLANId, config_parse_sr_iov_uint32, 0, 0
SR-IOV.QualityOfService, config_parse_sr_iov_uint32, 0, 0
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 8ae2315f1b..7f086ceae2 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -1196,6 +1196,9 @@ int config_parse_required_for_online(
return 0;
}
+DEFINE_CONFIG_PARSE_ENUM(config_parse_required_family_for_online, link_required_address_family, AddressFamily,
+ "Failed to parse RequiredFamilyForOnline= setting");
+
DEFINE_CONFIG_PARSE_ENUM(config_parse_keep_configuration, keep_configuration, KeepConfiguration,
"Failed to parse KeepConfiguration= setting");
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 44b1d0205f..df77b42619 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -104,6 +104,7 @@ struct Network {
bool unmanaged;
bool required_for_online; /* Is this network required to be considered online? */
LinkOperationalStateRange required_operstate_for_online;
+ AddressFamily required_family_for_online;
ActivationPolicy activation_policy;
/* misc settings */
@@ -348,6 +349,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors);
CONFIG_PARSER_PROTOTYPE(config_parse_ntp);
CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online);
+CONFIG_PARSER_PROTOTYPE(config_parse_required_family_for_online);
CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_link_local_address_gen_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_activation_policy);
diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c
index 96232018dd..34613697d6 100644
--- a/src/network/networkd-state-file.c
+++ b/src/network/networkd-state-file.c
@@ -105,10 +105,11 @@ static int ordered_set_put_in4_addrv(
int manager_save(Manager *m) {
_cleanup_ordered_set_free_ OrderedSet *dns = NULL, *ntp = NULL, *sip = NULL, *search_domains = NULL, *route_domains = NULL;
- const char *operstate_str, *carrier_state_str, *address_state_str;
+ const char *operstate_str, *carrier_state_str, *address_state_str, *ipv4_address_state_str, *ipv6_address_state_str;
LinkOperationalState operstate = LINK_OPERSTATE_OFF;
LinkCarrierState carrier_state = LINK_CARRIER_STATE_OFF;
- LinkAddressState address_state = LINK_ADDRESS_STATE_OFF;
+ LinkAddressState ipv4_address_state = LINK_ADDRESS_STATE_OFF, ipv6_address_state = LINK_ADDRESS_STATE_OFF,
+ address_state = LINK_ADDRESS_STATE_OFF;
_cleanup_(unlink_and_freep) char *temp_path = NULL;
_cleanup_strv_free_ char **p = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -133,6 +134,12 @@ int manager_save(Manager *m) {
if (link->address_state > address_state)
address_state = link->address_state;
+ if (link->ipv4_address_state > ipv4_address_state)
+ ipv4_address_state = link->ipv4_address_state;
+
+ if (link->ipv6_address_state > ipv6_address_state)
+ ipv6_address_state = link->ipv6_address_state;
+
if (!link->network)
continue;
@@ -226,6 +233,12 @@ int manager_save(Manager *m) {
address_state_str = link_address_state_to_string(address_state);
assert(address_state_str);
+ ipv4_address_state_str = link_address_state_to_string(ipv4_address_state);
+ assert(ipv4_address_state_str);
+
+ ipv6_address_state_str = link_address_state_to_string(ipv6_address_state);
+ assert(ipv6_address_state_str);
+
r = fopen_temporary(m->state_file, &f, &temp_path);
if (r < 0)
return r;
@@ -236,8 +249,10 @@ int manager_save(Manager *m) {
"# This is private data. Do not parse.\n"
"OPER_STATE=%s\n"
"CARRIER_STATE=%s\n"
- "ADDRESS_STATE=%s\n",
- operstate_str, carrier_state_str, address_state_str);
+ "ADDRESS_STATE=%s\n"
+ "IPV4_ADDRESS_STATE=%s\n"
+ "IPV6_ADDRESS_STATE=%s\n",
+ operstate_str, carrier_state_str, address_state_str, ipv4_address_state_str, ipv6_address_state_str);
ordered_set_print(f, "DNS=", dns);
ordered_set_print(f, "NTP=", ntp);
@@ -273,6 +288,18 @@ int manager_save(Manager *m) {
log_oom();
}
+ if (m->ipv4_address_state != ipv4_address_state) {
+ m->ipv4_address_state = ipv4_address_state;
+ if (strv_extend(&p, "IPv4AddressState") < 0)
+ log_oom();
+ }
+
+ if (m->ipv6_address_state != ipv6_address_state) {
+ m->ipv6_address_state = ipv6_address_state;
+ if (strv_extend(&p, "IPv6AddressState") < 0)
+ log_oom();
+ }
+
if (p) {
r = manager_send_changed_strv(m, p);
if (r < 0)
@@ -376,7 +403,7 @@ static void serialize_addresses(
}
int link_save(Link *link) {
- const char *admin_state, *oper_state, *carrier_state, *address_state;
+ const char *admin_state, *oper_state, *carrier_state, *address_state, *ipv4_address_state, *ipv6_address_state;
_cleanup_(unlink_and_freep) char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
@@ -403,6 +430,12 @@ int link_save(Link *link) {
address_state = link_address_state_to_string(link->address_state);
assert(address_state);
+ ipv4_address_state = link_address_state_to_string(link->ipv4_address_state);
+ assert(ipv4_address_state);
+
+ ipv6_address_state = link_address_state_to_string(link->ipv6_address_state);
+ assert(ipv6_address_state);
+
r = fopen_temporary(link->state_file, &f, &temp_path);
if (r < 0)
return r;
@@ -414,8 +447,10 @@ int link_save(Link *link) {
"ADMIN_STATE=%s\n"
"OPER_STATE=%s\n"
"CARRIER_STATE=%s\n"
- "ADDRESS_STATE=%s\n",
- admin_state, oper_state, carrier_state, address_state);
+ "ADDRESS_STATE=%s\n"
+ "IPV4_ADDRESS_STATE=%s\n"
+ "IPV6_ADDRESS_STATE=%s\n",
+ admin_state, oper_state, carrier_state, address_state, ipv4_address_state, ipv6_address_state);
if (link->network) {
char **dhcp6_domains = NULL, **dhcp_domains = NULL;
@@ -431,6 +466,9 @@ int link_save(Link *link) {
st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "",
st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : "");
+ fprintf(f, "REQUIRED_FAMILY_FOR_ONLINE=%s\n",
+ link_required_address_family_to_string(link->network->required_family_for_online));
+
fprintf(f, "ACTIVATION_POLICY=%s\n",
activation_policy_to_string(link->network->activation_policy));
diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h
index 01675e8b5c..4926fc5eb7 100644
--- a/src/network/networkd-util.h
+++ b/src/network/networkd-util.h
@@ -8,18 +8,9 @@
#include "hashmap.h"
#include "log.h"
#include "macro.h"
+#include "network-util.h"
#include "string-util.h"
-typedef enum AddressFamily {
- /* This is a bitmask, though it usually doesn't feel that way! */
- ADDRESS_FAMILY_NO = 0,
- ADDRESS_FAMILY_IPV4 = 1 << 0,
- ADDRESS_FAMILY_IPV6 = 1 << 1,
- ADDRESS_FAMILY_YES = ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_IPV6,
- _ADDRESS_FAMILY_MAX,
- _ADDRESS_FAMILY_INVALID = -EINVAL,
-} AddressFamily;
-
typedef struct NetworkConfigSection {
unsigned line;
bool invalid;
diff --git a/src/network/wait-online/link.c b/src/network/wait-online/link.c
index f2d556f099..5a33d563c2 100644
--- a/src/network/wait-online/link.c
+++ b/src/network/wait-online/link.c
@@ -97,7 +97,8 @@ 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;
+ _cleanup_free_ char *operstate = NULL, *required_operstate = NULL, *required_family = NULL,
+ *ipv4_address_state = NULL, *ipv6_address_state = NULL, *state = NULL;
int r, ret = 0;
assert(l);
@@ -135,6 +136,47 @@ int link_update_monitor(Link *l) {
l->operational_state = s;
}
+ r = sd_network_link_get_required_family_for_online(l->ifindex, &required_family);
+ if (r < 0)
+ ret = log_link_debug_errno(l, r, "Failed to get required address family, ignoring: %m");
+ else if (isempty(required_family))
+ l->required_family = ADDRESS_FAMILY_NO;
+ else {
+ AddressFamily f;
+
+ f = link_required_address_family_from_string(required_family);
+ if (f < 0)
+ ret = log_link_debug_errno(l, f, "Failed to parse required address family, ignoring: %m");
+ else
+ l->required_family = f;
+ }
+
+ r = sd_network_link_get_ipv4_address_state(l->ifindex, &ipv4_address_state);
+ if (r < 0)
+ ret = log_link_debug_errno(l, r, "Failed to get IPv4 address state, ignoring: %m");
+ else {
+ LinkAddressState s;
+
+ s = link_address_state_from_string(ipv4_address_state);
+ if (s < 0)
+ ret = log_link_debug_errno(l, s, "Failed to parse IPv4 address state, ignoring: %m");
+ else
+ l->ipv4_address_state = s;
+ }
+
+ r = sd_network_link_get_ipv6_address_state(l->ifindex, &ipv6_address_state);
+ if (r < 0)
+ ret = log_link_debug_errno(l, r, "Failed to get IPv6 address state, ignoring: %m");
+ else {
+ LinkAddressState s;
+
+ s = link_address_state_from_string(ipv6_address_state);
+ if (s < 0)
+ ret = log_link_debug_errno(l, s, "Failed to parse IPv6 address state, ignoring: %m");
+ else
+ l->ipv6_address_state = s;
+ }
+
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");
diff --git a/src/network/wait-online/link.h b/src/network/wait-online/link.h
index 3aa8357293..3072a91e56 100644
--- a/src/network/wait-online/link.h
+++ b/src/network/wait-online/link.h
@@ -19,6 +19,9 @@ struct Link {
bool required_for_online;
LinkOperationalStateRange required_operstate;
LinkOperationalState operational_state;
+ AddressFamily required_family;
+ LinkAddressState ipv4_address_state;
+ LinkAddressState ipv6_address_state;
char *state;
};
diff --git a/src/network/wait-online/manager.c b/src/network/wait-online/manager.c
index 1438b27445..8238b70464 100644
--- a/src/network/wait-online/manager.c
+++ b/src/network/wait-online/manager.c
@@ -32,6 +32,13 @@ static bool manager_ignore_link(Manager *m, Link *link) {
}
static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange s) {
+ AddressFamily required_family;
+ bool needs_ipv4;
+ bool needs_ipv6;
+
+ assert(m);
+ assert(l);
+
/* This returns the following:
* -EAGAIN: not processed by udev or networkd
* 0: operstate is not enough
@@ -60,7 +67,35 @@ static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange
return 0;
}
+ required_family = m->required_family > 0 ? m->required_family : l->required_family;
+ needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4;
+ needs_ipv6 = required_family & ADDRESS_FAMILY_IPV6;
+
+ if (s.min >= LINK_OPERSTATE_DEGRADED) {
+ if (needs_ipv4 && l->ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED)
+ goto ipv4_not_ready;
+
+ if (needs_ipv6 && l->ipv6_address_state < LINK_ADDRESS_STATE_DEGRADED)
+ goto ipv6_not_ready;
+ }
+
+ if (s.min >= LINK_OPERSTATE_ROUTABLE) {
+ if (needs_ipv4 && l->ipv4_address_state < LINK_ADDRESS_STATE_ROUTABLE)
+ goto ipv4_not_ready;
+
+ if (needs_ipv6 && l->ipv6_address_state < LINK_ADDRESS_STATE_ROUTABLE)
+ goto ipv6_not_ready;
+ }
+
return 1;
+
+ipv4_not_ready:
+ log_link_debug(l, "No routable or link-local IPv4 address is configured.");
+ return 0;
+
+ipv6_not_ready:
+ log_link_debug(l, "No routable or link-local IPv6 address is configured.");
+ return 0;
}
bool manager_configured(Manager *m) {
@@ -298,6 +333,7 @@ static int manager_network_monitor_listen(Manager *m) {
int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
LinkOperationalStateRange required_operstate,
+ AddressFamily required_family,
bool any, usec_t timeout) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
@@ -312,6 +348,7 @@ int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
.interfaces = interfaces,
.ignore = ignore,
.required_operstate = required_operstate,
+ .required_family = required_family,
.any = any,
};
diff --git a/src/network/wait-online/manager.h b/src/network/wait-online/manager.h
index 9892a43dc9..f2e091638c 100644
--- a/src/network/wait-online/manager.h
+++ b/src/network/wait-online/manager.h
@@ -21,6 +21,7 @@ struct Manager {
char **ignore;
LinkOperationalStateRange required_operstate;
+ AddressFamily required_family;
bool any;
sd_netlink *rtnl;
@@ -35,6 +36,7 @@ struct Manager {
Manager* manager_free(Manager *m);
int manager_new(Manager **ret, Hashmap *interfaces, char **ignore,
LinkOperationalStateRange required_operstate,
+ AddressFamily required_family,
bool any, usec_t timeout);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c
index ca0116e7f3..bbf51b6d3b 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 LinkOperationalStateRange arg_required_operstate = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID };
+static AddressFamily arg_required_family = ADDRESS_FAMILY_NO;
static bool arg_any = false;
STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_free_free_freep);
@@ -42,6 +43,8 @@ static int help(void) {
" --ignore=INTERFACE Don't take these interfaces into account\n"
" -o --operational-state=MIN_OPERSTATE[:MAX_OPERSTATE]\n"
" Required operational state\n"
+ " -4 --ipv4 Requires at least one IPv4 address\n"
+ " -6 --ipv6 Requires at least one IPv6 address\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",
@@ -111,6 +114,8 @@ 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' },
+ { "ipv4", no_argument, NULL, '4' },
+ { "ipv6", no_argument, NULL, '6' },
{ "any", no_argument, NULL, ARG_ANY },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
{}
@@ -121,7 +126,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hi:qo:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hi:qo:46", options, NULL)) >= 0)
switch (c) {
@@ -159,6 +164,15 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
+
+ case '4':
+ arg_required_family |= ADDRESS_FAMILY_IPV4;
+ break;
+
+ case '6':
+ arg_required_family |= ADDRESS_FAMILY_IPV6;
+ break;
+
case ARG_ANY:
arg_any = true;
break;
@@ -197,7 +211,7 @@ 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_any, arg_timeout);
+ r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_required_family, arg_any, arg_timeout);
if (r < 0)
return log_error_errno(r, "Could not create manager: %m");
diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h
index 884dba81b9..3f4ccf1908 100644
--- a/src/systemd/sd-network.h
+++ b/src/systemd/sd-network.h
@@ -51,6 +51,8 @@ _SD_BEGIN_DECLARATIONS;
int sd_network_get_operational_state(char **state);
int sd_network_get_carrier_state(char **state);
int sd_network_get_address_state(char **state);
+int sd_network_get_ipv4_address_state(char **state);
+int sd_network_get_ipv6_address_state(char **state);
/* Get DNS entries for all links. These are string representations of
* IP addresses */
@@ -92,8 +94,11 @@ int sd_network_link_get_setup_state(int ifindex, char **state);
*/
int sd_network_link_get_operational_state(int ifindex, char **state);
int sd_network_link_get_required_operstate_for_online(int ifindex, char **state);
+int sd_network_link_get_required_family_for_online(int ifindex, char **state);
int sd_network_link_get_carrier_state(int ifindex, char **state);
int sd_network_link_get_address_state(int ifindex, char **state);
+int sd_network_link_get_ipv4_address_state(int ifindex, char **state);
+int sd_network_link_get_ipv6_address_state(int ifindex, char **state);
/* Indicates whether the network is relevant to being online.
* Possible return codes:
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 399eeafa98..016be501ed 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -32,6 +32,7 @@ PermanentMACAddress=
[Link]
ActivationPolicy=
RequiredForOnline=
+RequiredFamilyForOnline=
ARP=
AllMulticast=
Unmanaged=
diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service
index 310dbd6add..a152bebd73 100644
--- a/test/fuzz/fuzz-unit-file/directives-all.service
+++ b/test/fuzz/fuzz-unit-file/directives-all.service
@@ -545,6 +545,7 @@ RemoteChecksumRx=
RemoteChecksumTx=
ReorderHeader=
RequestBroadcast=
+RequiredFamilyForOnline=
RequiredForOnline=
ResendIGMP=
RootDistanceMaxSec=
diff --git a/test/test-network/conf/dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network b/test/test-network/conf/dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network
new file mode 100644
index 0000000000..448cfb0e59
--- /dev/null
+++ b/test/test-network/conf/dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network
@@ -0,0 +1,9 @@
+[Match]
+Name=veth99
+
+[NetworkEmulator]
+DelaySec=9
+
+[Network]
+DHCP=ipv4
+IPv6AcceptRA=true
diff --git a/test/test-network/conf/dhcp-server-with-ipv6-prefix.network b/test/test-network/conf/dhcp-server-with-ipv6-prefix.network
new file mode 100644
index 0000000000..05ebe3e7e2
--- /dev/null
+++ b/test/test-network/conf/dhcp-server-with-ipv6-prefix.network
@@ -0,0 +1,11 @@
+[Match]
+Name=veth-peer
+
+[Network]
+Address=192.168.5.1/24
+IPv6AcceptRA=no
+DHCPServer=yes
+IPv6SendRA=yes
+
+[IPv6Prefix]
+Prefix=2002:da8:1:0::/64
diff --git a/test/test-network/conf/ipv6-prefix-with-delay.network b/test/test-network/conf/ipv6-prefix-with-delay.network
new file mode 100644
index 0000000000..4fe3c9a04b
--- /dev/null
+++ b/test/test-network/conf/ipv6-prefix-with-delay.network
@@ -0,0 +1,12 @@
+[Match]
+Name=veth-peer
+
+[NetworkEmulator]
+DelaySec=15
+
+[Network]
+IPv6AcceptRA=no
+IPv6SendRA=yes
+
+[IPv6Prefix]
+Prefix=2002:da8:1:0::/64
diff --git a/test/test-network/conf/ipv6ra-prefix-client-with-static-ipv4-address.network b/test/test-network/conf/ipv6ra-prefix-client-with-static-ipv4-address.network
new file mode 100644
index 0000000000..13fd0adeff
--- /dev/null
+++ b/test/test-network/conf/ipv6ra-prefix-client-with-static-ipv4-address.network
@@ -0,0 +1,6 @@
+[Match]
+Name=veth99
+
+[Network]
+IPv6AcceptRA=true
+Address=192.168.5.1/24
diff --git a/test/test-network/conf/state-file-tests.network b/test/test-network/conf/state-file-tests.network
index 1f7e7d16f0..d9db9a9ab9 100644
--- a/test/test-network/conf/state-file-tests.network
+++ b/test/test-network/conf/state-file-tests.network
@@ -3,6 +3,7 @@ Name=dummy98
[Link]
RequiredForOnline=routable
+RequiredFamilyForOnline=both
[Network]
IPv6AcceptRA=no
@@ -14,3 +15,4 @@ MulticastDNS=yes
DNSSEC=no
Address=192.168.10.10/24
Address=192.168.12.12/24
+Address=2002:da8:1:0:1034:56ff:fe78:9abc/64
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index fc88fefd0f..28ffe3d8ba 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -555,7 +555,7 @@ class Utilities():
self.fail(f'Timed out waiting for {link} to reach state {operstate}/{setup_state}')
return False
- def wait_online(self, links_with_operstate, timeout='20s', bool_any=False, setup_state='configured', setup_timeout=5):
+ def wait_online(self, links_with_operstate, timeout='20s', bool_any=False, ipv4=False, ipv6=False, setup_state='configured', setup_timeout=5):
"""Wait for the link(s) to reach the specified operstate and/or setup state.
This is similar to wait_operstate() but can be used for multiple links,
@@ -569,6 +569,9 @@ class Utilities():
Set 'bool_any' to True to wait for any (instead of all) of the given links.
If this is set, no setup_state checks are done.
+ Set 'ipv4' or 'ipv6' to True to wait for IPv4 address or IPv6 address, respectively, of each of the given links.
+ This is applied only for the operational state 'degraded' or above.
+
Note that this function waits for the link(s) to reach *or exceed* the given operstate.
However, the setup_state, if specified, must be matched *exactly*.
@@ -578,6 +581,10 @@ class Utilities():
args = wait_online_cmd + [f'--timeout={timeout}'] + [f'--interface={link}' for link in links_with_operstate]
if bool_any:
args += ['--any']
+ if ipv4:
+ args += ['--ipv4']
+ if ipv6:
+ args += ['--ipv6']
try:
check_output(*args, env=env)
except subprocess.CalledProcessError:
@@ -1781,6 +1788,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
'gretun97',
'ip6gretun97',
'test1',
+ 'veth-peer',
'veth99',
'vrf99',
]
@@ -1842,6 +1850,10 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
'25-vrf.netdev',
'25-vrf.network',
'26-link-local-addressing-ipv6.network',
+ 'dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network',
+ 'dhcp-server-with-ipv6-prefix.network',
+ 'ipv6ra-prefix-client-with-static-ipv4-address.network',
+ 'ipv6-prefix-with-delay.network',
'routing-policy-rule-dummy98.network',
'routing-policy-rule-test1.network',
'routing-policy-rule-reconfigure1.network',
@@ -3099,6 +3111,22 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
call('rmmod netdevsim', stderr=subprocess.DEVNULL)
+ def test_wait_online_ipv4(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-with-ipv6-prefix.network', 'dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network')
+ start_networkd()
+
+ self.wait_online(['veth99:routable'], ipv4=True)
+
+ self.wait_address('veth99', r'192.168.5.[0-9]+', ipv='-4', timeout_sec=1)
+
+ def test_wait_online_ipv6(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6-prefix-with-delay.network', 'ipv6ra-prefix-client-with-static-ipv4-address.network')
+ start_networkd()
+
+ self.wait_online(['veth99:routable'], ipv6=True)
+
+ self.wait_address('veth99', r'2002:da8:1:0:1034:56ff:fe78:9abc', ipv='-6', timeout_sec=1)
+
class NetworkdStateFileTests(unittest.TestCase, Utilities):
links = [
'dummy98',
@@ -3135,10 +3163,13 @@ class NetworkdStateFileTests(unittest.TestCase, Utilities):
with open(path) as f:
data = f.read()
+ self.assertRegex(data, r'IPV4_ADDRESS_STATE=routable')
+ self.assertRegex(data, r'IPV6_ADDRESS_STATE=routable')
self.assertRegex(data, r'ADMIN_STATE=configured')
self.assertRegex(data, r'OPER_STATE=routable')
self.assertRegex(data, r'REQUIRED_FOR_ONLINE=yes')
self.assertRegex(data, r'REQUIRED_OPER_STATE_FOR_ONLINE=routable')
+ self.assertRegex(data, r'REQUIRED_FAMILY_FOR_ONLINE=both')
self.assertRegex(data, r'ACTIVATION_POLICY=up')
self.assertRegex(data, r'NETWORK_FILE=/run/systemd/network/state-file-tests.network')
self.assertRegex(data, r'DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com \[1111:2222::3333\]:1234#ccc.com')