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