#include "missing_network.h"
#include "netdev/bond.h"
#include "netdev/bridge.h"
+#include "netdev/ipvlan.h"
#include "netdev/vrf.h"
#include "netlink-util.h"
#include "network-internal.h"
#include "networkd-can.h"
#include "networkd-ipv6-proxy-ndp.h"
+#include "networkd-link-bus.h"
+#include "networkd-link.h"
#include "networkd-lldp-tx.h"
#include "networkd-manager.h"
#include "networkd-ndisc.h"
return &link->manager->duid;
}
+int link_sysctl_ipv6_enabled(Link *link) {
+ _cleanup_free_ char *value = NULL;
+ int r;
+
+ r = sysctl_read_ip_property(AF_INET6, link->ifname, "disable_ipv6", &value);
+ if (r < 0)
+ return log_link_warning_errno(link, r,
+ "Failed to read net.ipv6.conf.%s.disable_ipv6 sysctl property: %m",
+ link->ifname);
+
+ link->sysctl_ipv6_enabled = value[0] == '0';
+ return link->sysctl_ipv6_enabled;
+}
+
static bool link_dhcp6_enabled(Link *link) {
assert(link);
if (link->network->bond)
return false;
- if (manager_sysctl_ipv6_enabled(link->manager) == 0)
+ if (STRPTR_IN_SET(link->kind, "can", "vcan", "vxcan"))
+ return false;
+
+ if (link_sysctl_ipv6_enabled(link) == 0)
return false;
return link->network->dhcp & ADDRESS_FAMILY_IPV6;
if (link->network->bond)
return false;
+ if (STRPTR_IN_SET(link->kind, "can", "vcan", "vxcan"))
+ return false;
+
return link->network->dhcp & ADDRESS_FAMILY_IPV4;
}
if (link->network->bond)
return false;
+ if (STRPTR_IN_SET(link->kind, "can", "vcan", "vxcan"))
+ return false;
+
return link->network->dhcp_server;
}
-bool link_ipv4ll_enabled(Link *link) {
+bool link_ipv4ll_enabled(Link *link, AddressFamilyBoolean mask) {
assert(link);
+ assert((mask & ~(ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_FALLBACK_IPV4)) == 0);
if (link->flags & IFF_LOOPBACK)
return false;
if (!link->network)
return false;
- if (STRPTR_IN_SET(link->kind, "vrf", "wireguard", "ipip", "gre", "ip6gre", "ip6tnl", "sit", "vti", "vti6"))
- return false;
-
- if (link->network->bond)
- return false;
-
- return link->network->link_local & ADDRESS_FAMILY_IPV4;
-}
-
-bool link_ipv4ll_fallback_enabled(Link *link) {
- assert(link);
-
- if (link->flags & IFF_LOOPBACK)
+ if (STRPTR_IN_SET(link->kind, "vrf", "wireguard", "ipip", "gre", "ip6gre", "ip6tnl", "sit", "vti", "vti6", "can", "vcan", "vxcan", "nlmon"))
return false;
- if (!link->network)
+ /* L3 or L3S mode do not support ARP. */
+ if (IN_SET(link_get_ipvlan_mode(link), NETDEV_IPVLAN_MODE_L3, NETDEV_IPVLAN_MODE_L3S))
return false;
- if (STRPTR_IN_SET(link->kind, "vrf", "wireguard", "ipip", "gre", "ip6gre", "ip6tnl", "sit", "vti", "vti6"))
+ if (link->network->bond)
return false;
if (link->network->bond)
return false;
- return link->network->link_local & ADDRESS_FAMILY_FALLBACK_IPV4;
+ return link->network->link_local & mask;
}
static bool link_ipv6ll_enabled(Link *link) {
if (!link->network)
return false;
- if (STRPTR_IN_SET(link->kind, "vrf", "wireguard", "ipip", "gre", "sit", "vti"))
+ if (STRPTR_IN_SET(link->kind, "vrf", "wireguard", "ipip", "gre", "sit", "vti", "can", "vcan", "vxcan", "nlmon"))
return false;
if (link->network->bond)
return false;
- if (manager_sysctl_ipv6_enabled(link->manager) == 0)
+ if (link_sysctl_ipv6_enabled(link) == 0)
return false;
return link->network->link_local & ADDRESS_FAMILY_IPV6;
if (link->network->bond)
return false;
- if (manager_sysctl_ipv6_enabled(link->manager) == 0)
+ if (link_sysctl_ipv6_enabled(link) == 0)
+ return false;
+
+ if (STRPTR_IN_SET(link->kind, "can", "vcan", "vxcan"))
return false;
/* DHCPv6 client will not be started if no IPv6 link-local address is configured. */
if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
return false;
- if (manager_sysctl_ipv6_enabled(link->manager) == 0)
+ if (link_sysctl_ipv6_enabled(link) == 0)
return false;
return link->network->ip_forward & ADDRESS_FAMILY_IPV6;
/* Even if the link is not managed by networkd, honor IFF_SLAVE flag. */
return true;
- if (!link->enslaved_raw)
- return false;
-
if (!link->network)
return false;
- if (link->network->bridge)
- /* TODO: support the case when link is not managed by networkd. */
+ if (link->master_ifindex > 0 && link->network->bridge)
return true;
+ /* TODO: add conditions for other netdevs. */
+
return false;
}
void link_update_operstate(Link *link, bool also_update_master) {
LinkOperationalState operstate;
+ LinkCarrierState carrier_state;
+ LinkAddressState address_state;
+ _cleanup_strv_free_ char **p = NULL;
+ uint8_t scope = RT_SCOPE_NOWHERE;
+ bool changed = false;
+ Address *address;
Iterator i;
assert(link);
if (link->kernel_operstate == IF_OPER_DORMANT)
- operstate = LINK_OPERSTATE_DORMANT;
+ carrier_state = LINK_CARRIER_STATE_DORMANT;
else if (link_has_carrier(link)) {
- Address *address;
- uint8_t scope = RT_SCOPE_NOWHERE;
+ if (link_is_enslaved(link))
+ carrier_state = LINK_CARRIER_STATE_ENSLAVED;
+ else
+ carrier_state = LINK_CARRIER_STATE_CARRIER;
+ } else if (link->flags & IFF_UP)
+ carrier_state = LINK_CARRIER_STATE_NO_CARRIER;
+ else
+ carrier_state = LINK_CARRIER_STATE_OFF;
- /* if we have carrier, check what addresses we have */
- SET_FOREACH(address, link->addresses, i) {
- if (!address_is_ready(address))
- continue;
+ if (carrier_state >= LINK_CARRIER_STATE_CARRIER) {
+ Link *slave;
+
+ SET_FOREACH(slave, link->slaves, i) {
+ link_update_operstate(slave, false);
- if (address->scope < scope)
- scope = address->scope;
+ if (slave->carrier_state < LINK_CARRIER_STATE_CARRIER)
+ carrier_state = LINK_CARRIER_STATE_DEGRADED_CARRIER;
}
+ }
- /* for operstate we also take foreign addresses into account */
- SET_FOREACH(address, link->addresses_foreign, i) {
- if (!address_is_ready(address))
- continue;
+ SET_FOREACH(address, link->addresses, i) {
+ if (!address_is_ready(address))
+ continue;
- if (address->scope < scope)
- scope = address->scope;
- }
+ if (address->scope < scope)
+ scope = address->scope;
+ }
- if (scope < RT_SCOPE_SITE)
- /* universally accessible addresses found */
- operstate = LINK_OPERSTATE_ROUTABLE;
- else if (scope < RT_SCOPE_HOST)
- /* only link or site local addresses found */
- operstate = LINK_OPERSTATE_DEGRADED;
- else
- /* no useful addresses found */
- operstate = LINK_OPERSTATE_CARRIER;
- } else if (link->flags & IFF_UP)
- operstate = LINK_OPERSTATE_NO_CARRIER;
+ /* for operstate we also take foreign addresses into account */
+ SET_FOREACH(address, link->addresses_foreign, i) {
+ if (!address_is_ready(address))
+ continue;
+
+ if (address->scope < scope)
+ 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
- operstate = LINK_OPERSTATE_OFF;
+ /* no useful addresses found */
+ address_state = LINK_ADDRESS_STATE_OFF;
+
+ /* Mapping of address and carrier state vs operational state
+ * carrier state
+ * | off | no-carrier | dormant | degraded-carrier | carrier | enslaved
+ * ------------------------------------------------------------------------------
+ * off | off | no-carrier | dormant | degraded-carrier | carrier | enslaved
+ * address_state degraded | off | no-carrier | dormant | degraded-carrier | degraded | enslaved
+ * routable | off | no-carrier | dormant | degraded-carrier | routable | routable
+ */
- if (IN_SET(operstate, LINK_OPERSTATE_DEGRADED, LINK_OPERSTATE_CARRIER) &&
- link_is_enslaved(link))
+ if (carrier_state < LINK_CARRIER_STATE_CARRIER || address_state == LINK_ADDRESS_STATE_OFF)
+ operstate = (LinkOperationalState) carrier_state;
+ else if (address_state == LINK_ADDRESS_STATE_ROUTABLE)
+ operstate = LINK_OPERSTATE_ROUTABLE;
+ else if (carrier_state == LINK_CARRIER_STATE_CARRIER)
+ operstate = LINK_OPERSTATE_DEGRADED;
+ else
operstate = LINK_OPERSTATE_ENSLAVED;
- if (operstate >= LINK_OPERSTATE_CARRIER) {
- Link *slave;
-
- SET_FOREACH(slave, link->slaves, i) {
- link_update_operstate(slave, false);
+ if (link->carrier_state != carrier_state) {
+ link->carrier_state = carrier_state;
+ changed = true;
+ if (strv_extend(&p, "CarrierState") < 0)
+ log_oom();
+ }
- if (slave->operstate < LINK_OPERSTATE_CARRIER)
- operstate = LINK_OPERSTATE_DEGRADED_CARRIER;
- }
+ if (link->address_state != address_state) {
+ link->address_state = address_state;
+ changed = true;
+ if (strv_extend(&p, "AddressState") < 0)
+ log_oom();
}
if (link->operstate != operstate) {
link->operstate = operstate;
- link_send_changed(link, "OperationalState", NULL);
- link_dirty(link);
+ changed = true;
+ if (strv_extend(&p, "OperationalState") < 0)
+ log_oom();
}
+ if (p)
+ link_send_changed_strv(link, p);
+ if (changed)
+ link_dirty(link);
+
if (also_update_master && link->network) {
link_update_master_operstate(link, link->network->bond);
link_update_master_operstate(link, link->network->bridge);
? ((old & flag) ? (" -" string) : (" +" string)) \
: "")
-static int link_update_flags(Link *link, sd_netlink_message *m) {
+static int link_update_flags(Link *link, sd_netlink_message *m, bool force_update_operstate) {
unsigned flags, unknown_flags_added, unknown_flags_removed, unknown_flags;
uint8_t operstate;
int r;
the state was unchanged */
operstate = link->kernel_operstate;
- if ((link->flags == flags) && (link->kernel_operstate == operstate))
+ if (!force_update_operstate && (link->flags == flags) && (link->kernel_operstate == operstate))
return 0;
if (link->flags != flags) {
.rtnl_extended_attrs = true,
.ifindex = ifindex,
.iftype = iftype,
+ .sysctl_ipv6_enabled = -1,
};
link->ifname = strdup(ifname);
if (r < 0)
return r;
- r = link_update_flags(link, message);
+ r = link_update_flags(link, message, false);
if (r < 0)
return r;
return 0;
}
-static void link_set_state(Link *link, LinkState state) {
+void link_set_state(Link *link, LinkState state) {
assert(link);
if (link->state == state)
static void link_enter_unmanaged(Link *link) {
assert(link);
- log_link_debug(link, "Unmanaged");
-
link_set_state(link, LINK_STATE_UNMANAGED);
link_dirty(link);
}
-int link_stop_clients(Link *link) {
+int link_stop_clients(Link *link, bool may_keep_dhcp) {
int r = 0, k;
assert(link);
assert(link->manager);
assert(link->manager->event);
- if (link->dhcp_client) {
+ dhcp4_release_old_lease(link);
+
+ if (link->dhcp_client && (!may_keep_dhcp || !link->network ||
+ !FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP))) {
k = sd_dhcp_client_stop(link->dhcp_client);
if (k < 0)
r = log_link_warning_errno(link, k, "Could not stop DHCPv4 client: %m");
link_set_state(link, LINK_STATE_FAILED);
- link_stop_clients(link);
+ link_stop_clients(link, false);
link_dirty(link);
}
link->routing_policy_rules_configured = false;
LIST_FOREACH(rules, rule, link->network->rules) {
- r = routing_policy_rule_get(link->manager, rule->family, &rule->from, rule->from_prefixlen, &rule->to,
- rule->to_prefixlen, rule->tos, rule->fwmark, rule->table, rule->iif, rule->oif,
- rule->protocol, &rule->sport, &rule->dport, &rrule);
- if (r == 0) {
- (void) routing_policy_rule_make_local(link->manager, rrule);
+ r = routing_policy_rule_get(link->manager, rule, &rrule);
+ if (r >= 0) {
+ if (r == 0)
+ (void) routing_policy_rule_make_local(link->manager, rrule);
continue;
}
- r = routing_policy_rule_configure(rule, link, NULL, false);
+ r = routing_policy_rule_configure(rule, link, NULL);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set routing policy rules: %m");
link_enter_failed(link);
return r;
}
-
- link->routing_policy_rule_messages++;
+ if (r > 0)
+ link->routing_policy_rule_messages++;
}
routing_policy_rule_purge(link->manager, link);
link_enter_failed(link);
return r;
}
-
- link->route_messages++;
+ if (r > 0)
+ link->route_messages++;
}
if (link->route_messages == 0) {
if (!link->routing_policy_rules_configured)
return;
- if (link_ipv4ll_enabled(link) && !(link->ipv4ll_address && link->ipv4ll_route))
- return;
+ if (link_has_carrier(link) || !link->network->configure_without_carrier) {
- if (link_ipv6ll_enabled(link) &&
- in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address))
- return;
+ if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4) && !(link->ipv4ll_address && link->ipv4ll_route))
+ return;
- if ((link_dhcp4_enabled(link) || link_dhcp6_enabled(link)) &&
- !(link->dhcp4_configured || link->dhcp6_configured) &&
- !(link_ipv4ll_fallback_enabled(link) && link->ipv4ll_address && link->ipv4ll_route))
- /* When DHCP is enabled, at least one protocol must provide an address, or
- * an IPv4ll fallback address must be configured. */
- return;
+ if (link_ipv6ll_enabled(link) &&
+ in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address))
+ return;
- if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured)
- return;
+ if ((link_dhcp4_enabled(link) || link_dhcp6_enabled(link)) &&
+ !link->dhcp4_configured &&
+ !link->dhcp6_configured &&
+ !(link_ipv4ll_enabled(link, ADDRESS_FAMILY_FALLBACK_IPV4) && link->ipv4ll_address && link->ipv4ll_route))
+ /* When DHCP is enabled, at least one protocol must provide an address, or
+ * an IPv4ll fallback address must be configured. */
+ return;
+
+ if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured)
+ return;
+ }
if (link->state != LINK_STATE_CONFIGURED)
link_enter_configured(link);
link_enter_failed(link);
return r;
}
-
- link->address_messages++;
+ if (r > 0)
+ link->address_messages++;
}
LIST_FOREACH(labels, label, link->network->address_labels) {
assert(link->manager);
assert(link->manager->event);
- if (link_ipv4ll_enabled(link)) {
+ if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4)) {
assert(link->ipv4ll);
log_link_debug(link, "Acquiring IPv4 link-local address");
if (link->kernel_operstate == IF_OPER_UNKNOWN)
/* operstate may not be implemented, so fall back to flags */
- if ((link->flags & IFF_LOWER_UP) && !(link->flags & IFF_DORMANT))
+ if (FLAGS_SET(link->flags, IFF_LOWER_UP | IFF_RUNNING) &&
+ !FLAGS_SET(link->flags, IFF_DORMANT))
return true;
return false;
assert(link);
assert(link->network);
assert(link->enslaving > 0);
- assert(!link->enslaved_raw);
link->enslaving--;
log_link_debug(link, "Joined netdev");
if (link->enslaving == 0) {
- link->enslaved_raw = true;
link_joined(link);
}
link_dirty(link);
link->enslaving = 0;
- link->enslaved_raw = false;
if (link->network->bond) {
if (link->network->bond->state == NETDEV_STATE_READY &&
return false;
}
+static bool link_address_is_dynamic(Link *link, Address *address) {
+ Route *route;
+ Iterator i;
+
+ assert(link);
+ assert(address);
+
+ if (address->cinfo.ifa_prefered != CACHE_INFO_INFINITY_LIFE_TIME)
+ return true;
+
+ /* Even when the address is leased from a DHCP server, networkd assign the address
+ * without lifetime when KeepConfiguration=dhcp. So, let's check that we have
+ * corresponding routes with RTPROT_DHCP. */
+ SET_FOREACH(route, link->routes_foreign, i) {
+ if (route->protocol != RTPROT_DHCP)
+ continue;
+
+ if (address->family != route->family)
+ continue;
+
+ if (in_addr_equal(address->family, &address->in_addr, &route->prefsrc))
+ return true;
+ }
+
+ return false;
+}
+
static int link_drop_foreign_config(Link *link) {
Address *address;
Route *route;
if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1)
continue;
+ if (link_address_is_dynamic(link, address)) {
+ if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP))
+ continue;
+ } else if (FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC))
+ continue;
+
if (link_is_static_address_configured(link, address)) {
r = address_add(link, address->family, &address->in_addr, address->prefixlen, NULL);
if (r < 0)
if (route->protocol == RTPROT_KERNEL)
continue;
+ if (route->protocol == RTPROT_STATIC &&
+ FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC))
+ continue;
+
+ if (route->protocol == RTPROT_DHCP &&
+ FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP))
+ continue;
+
if (link_is_static_route_configured(link, route)) {
r = route_add(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL);
if (r < 0)
assert(link->network);
assert(link->state == LINK_STATE_INITIALIZED);
- if (STRPTR_IN_SET(link->kind, "can", "vcan"))
+ if (STRPTR_IN_SET(link->kind, "can", "vcan", "vxcan"))
return link_configure_can(link);
/* Drop foreign config, but ignore loopback or critical devices.
* We do not want to remove loopback address or addresses used for root NFS. */
- if (!(link->flags & IFF_LOOPBACK) && !(link->network->dhcp_critical)) {
+ if (!(link->flags & IFF_LOOPBACK) &&
+ link->network->keep_configuration != KEEP_CONFIGURATION_YES) {
r = link_drop_foreign_config(link);
if (r < 0)
return r;
if (r < 0)
return r;
- if (link_ipv4ll_enabled(link) || link_ipv4ll_fallback_enabled(link)) {
+ if (link_ipv4ll_enabled(link, ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_FALLBACK_IPV4)) {
r = ipv4ll_configure(link);
if (r < 0)
return r;
if (link->setting_mtu)
return 0;
- r = link_stop_clients(link);
+ r = link_stop_clients(link, false);
if (r < 0) {
link_enter_failed(link);
return r;
const char *ifname;
uint32_t mtu;
bool had_carrier, carrier_gained, carrier_lost;
- int r;
+ int old_master, r;
assert(link);
assert(link->ifname);
}
}
+ old_master = link->master_ifindex;
+ (void) sd_netlink_message_read_u32(m, IFLA_MASTER, (uint32_t *) &link->master_ifindex);
+
had_carrier = link_has_carrier(link);
- r = link_update_flags(link, m);
+ r = link_update_flags(link, m, old_master != link->master_ifindex);
if (r < 0)
return r;
int link_save(Link *link) {
_cleanup_free_ char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
- const char *admin_state, *oper_state;
+ const char *admin_state, *oper_state, *carrier_state, *address_state;
Address *a;
Route *route;
Iterator i;
oper_state = link_operstate_to_string(link->operstate);
assert(oper_state);
+ carrier_state = link_carrier_state_to_string(link->carrier_state);
+ assert(carrier_state);
+
+ address_state = link_address_state_to_string(link->address_state);
+ assert(address_state);
+
r = fopen_temporary(link->state_file, &f, &temp_path);
if (r < 0)
goto fail;
fprintf(f,
"# This is private data. Do not parse.\n"
"ADMIN_STATE=%s\n"
- "OPER_STATE=%s\n",
- admin_state, oper_state);
+ "OPER_STATE=%s\n"
+ "CARRIER_STATE=%s\n"
+ "ADDRESS_STATE=%s\n",
+ admin_state, oper_state, carrier_state, address_state);
if (link->network) {
char **dhcp6_domains = NULL, **dhcp_domains = NULL;