]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-link.c
network: make bridge master also follow operstates of slave interfaces
[thirdparty/systemd.git] / src / network / networkd-link.c
index 04fec95f7d4e82949eb2bbd0ce569a2ea1c52611..22b639bad6a882ef08d094a737348b74bc206e64 100644 (file)
@@ -14,6 +14,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "missing_network.h"
+#include "netdev/vrf.h"
 #include "netlink-util.h"
 #include "network-internal.h"
 #include "networkd-ipv6-proxy-ndp.h"
 #include "stdio-util.h"
 #include "string-table.h"
 #include "strv.h"
+#include "sysctl-util.h"
 #include "tmpfile-util.h"
 #include "util.h"
 #include "virt.h"
 
+uint32_t link_get_vrf_table(Link *link) {
+        return link->network->vrf ? VRF(link->network->vrf)->table : RT_TABLE_MAIN;
+}
+
+uint32_t link_get_dhcp_route_table(Link *link) {
+        /* When the interface is part of an VRF use the VRFs routing table, unless
+         * another table is explicitly specified. */
+        if (link->network->dhcp_route_table_set)
+                return link->network->dhcp_route_table;
+        return link_get_vrf_table(link);
+}
+
+uint32_t link_get_ipv6_accept_ra_route_table(Link *link) {
+        if (link->network->ipv6_accept_ra_route_table_set)
+                return link->network->ipv6_accept_ra_route_table;
+        return link_get_vrf_table(link);
+}
+
 DUID* link_get_duid(Link *link) {
         if (link->network->duid.type != _DUID_TYPE_INVALID)
                 return &link->network->duid;
@@ -51,6 +71,12 @@ static bool link_dhcp6_enabled(Link *link) {
         if (!link->network)
                 return false;
 
+        if (link->network->bond)
+                return false;
+
+        if (manager_sysctl_ipv6_enabled(link->manager) == 0)
+                return false;
+
         return link->network->dhcp & ADDRESS_FAMILY_IPV6;
 }
 
@@ -63,6 +89,9 @@ static bool link_dhcp4_enabled(Link *link) {
         if (!link->network)
                 return false;
 
+        if (link->network->bond)
+                return false;
+
         return link->network->dhcp & ADDRESS_FAMILY_IPV4;
 }
 
@@ -75,6 +104,9 @@ static bool link_dhcp4_server_enabled(Link *link) {
         if (!link->network)
                 return false;
 
+        if (link->network->bond)
+                return false;
+
         return link->network->dhcp_server;
 }
 
@@ -87,7 +119,10 @@ static bool link_ipv4ll_enabled(Link *link) {
         if (!link->network)
                 return false;
 
-        if (streq_ptr(link->kind, "wireguard"))
+        if (STRPTR_IN_SET(link->kind, "vrf", "wireguard"))
+                return false;
+
+        if (link->network->bond)
                 return false;
 
         return link->network->link_local & ADDRESS_FAMILY_IPV4;
@@ -105,7 +140,13 @@ static bool link_ipv6ll_enabled(Link *link) {
         if (!link->network)
                 return false;
 
-        if (streq_ptr(link->kind, "wireguard"))
+        if (STRPTR_IN_SET(link->kind, "vrf", "wireguard"))
+                return false;
+
+        if (link->network->bond)
+                return false;
+
+        if (manager_sysctl_ipv6_enabled(link->manager) == 0)
                 return false;
 
         return link->network->link_local & ADDRESS_FAMILY_IPV6;
@@ -117,7 +158,10 @@ static bool link_ipv6_enabled(Link *link) {
         if (!socket_ipv6_is_supported())
                 return false;
 
-        if (link->network->bridge)
+        if (link->network->bond)
+                return false;
+
+        if (manager_sysctl_ipv6_enabled(link->manager) == 0)
                 return false;
 
         /* DHCPv6 client will not be started if no IPv6 link-local address is configured. */
@@ -199,6 +243,9 @@ static bool link_ipv6_forward_enabled(Link *link) {
         if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
                 return false;
 
+        if (manager_sysctl_ipv6_enabled(link->manager) == 0)
+                return false;
+
         return link->network->ip_forward & ADDRESS_FAMILY_IPV6;
 }
 
@@ -263,7 +310,6 @@ static IPv6PrivacyExtensions link_ipv6_privacy_extensions(Link *link) {
 }
 
 static int link_enable_ipv6(Link *link) {
-        const char *p = NULL;
         bool disabled;
         int r;
 
@@ -272,9 +318,7 @@ static int link_enable_ipv6(Link *link) {
 
         disabled = !link_ipv6_enabled(link);
 
-        p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/disable_ipv6");
-
-        r = write_string_file(p, one_zero(disabled), WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "disable_ipv6", disabled);
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot %s IPv6 for interface %s: %m",
                                        enable_disable(!disabled), link->ifname);
@@ -284,8 +328,40 @@ static int link_enable_ipv6(Link *link) {
         return 0;
 }
 
-void link_update_operstate(Link *link) {
+static bool link_is_enslaved(Link *link) {
+        if (link->flags & IFF_SLAVE)
+                /* 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. */
+                return true;
+
+        return false;
+}
+
+static void link_update_master_operstate(Link *link, NetDev *netdev) {
+        Link *master;
+
+        if (!netdev)
+                return;
+
+        if (link_get(link->manager, netdev->ifindex, &master) < 0)
+                return;
+
+        link_update_operstate(master, true);
+}
+
+void link_update_operstate(Link *link, bool also_update_master) {
         LinkOperationalState operstate;
+        Iterator i;
+
         assert(link);
 
         if (link->kernel_operstate == IF_OPER_DORMANT)
@@ -293,7 +369,6 @@ void link_update_operstate(Link *link) {
         else if (link_has_carrier(link)) {
                 Address *address;
                 uint8_t scope = RT_SCOPE_NOWHERE;
-                Iterator i;
 
                 /* if we have carrier, check what addresses we have */
                 SET_FOREACH(address, link->addresses, i) {
@@ -327,11 +402,32 @@ void link_update_operstate(Link *link) {
         else
                 operstate = LINK_OPERSTATE_OFF;
 
+        if (IN_SET(operstate, LINK_OPERSTATE_DEGRADED, LINK_OPERSTATE_CARRIER) &&
+            link_is_enslaved(link))
+                operstate = LINK_OPERSTATE_ENSLAVED;
+
+        if (IN_SET(operstate, LINK_OPERSTATE_CARRIER, LINK_OPERSTATE_ENSLAVED, LINK_OPERSTATE_ROUTABLE)) {
+                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 (link->operstate != operstate) {
                 link->operstate = operstate;
                 link_send_changed(link, "OperationalState", NULL);
                 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);
+        }
 }
 
 #define FLAG_STRING(string, flag, old, new) \
@@ -406,7 +502,7 @@ static int link_update_flags(Link *link, sd_netlink_message *m) {
         link->flags = flags;
         link->kernel_operstate = operstate;
 
-        link_update_operstate(link);
+        link_update_operstate(link, true);
 
         return 0;
 }
@@ -520,8 +616,8 @@ static void link_detach_from_manager(Link *link) {
 }
 
 static Link *link_free(Link *link) {
+        Link *carrier, *master;
         Address *address;
-        Link *carrier;
         Route *route;
         Iterator i;
 
@@ -587,6 +683,18 @@ static Link *link_free(Link *link) {
                 hashmap_remove(link->bound_by_links, INT_TO_PTR(carrier->ifindex));
         hashmap_free(link->bound_by_links);
 
+        hashmap_free(link->slaves);
+
+        if (link->network) {
+                if (link->network->bond &&
+                    link_get(link->manager, link->network->bond->ifindex, &master) >= 0)
+                        (void) hashmap_remove(master->slaves, INT_TO_PTR(link->ifindex));
+
+                if (link->network->bridge &&
+                    link_get(link->manager, link->network->bridge->ifindex, &master) >= 0)
+                        (void) hashmap_remove(master->slaves, INT_TO_PTR(link->ifindex));
+        }
+
         return mfree(link);
 }
 
@@ -879,23 +987,20 @@ void link_check_ready(Link *link) {
                     !link->ipv4ll_route)
                         return;
 
-        if (!link->network->bridge) {
-
-                if (link_ipv6ll_enabled(link))
-                        if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) > 0)
-                                return;
+        if (link_ipv6ll_enabled(link) &&
+            in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address))
+                return;
 
-                if ((link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) &&
-                     !link->dhcp4_configured) ||
-                    (link_dhcp6_enabled(link) && !link_dhcp4_enabled(link) &&
-                     !link->dhcp6_configured) ||
-                    (link_dhcp4_enabled(link) && link_dhcp6_enabled(link) &&
-                     !link->dhcp4_configured && !link->dhcp6_configured))
-                        return;
+        if ((link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) &&
+             !link->dhcp4_configured) ||
+            (link_dhcp6_enabled(link) && !link_dhcp4_enabled(link) &&
+             !link->dhcp6_configured) ||
+            (link_dhcp4_enabled(link) && link_dhcp6_enabled(link) &&
+             !link->dhcp4_configured && !link->dhcp6_configured))
+                return;
 
-                if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured)
-                        return;
-        }
+        if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured)
+                return;
 
         if (link->state != LINK_STATE_CONFIGURED)
                 link_enter_configured(link);
@@ -994,7 +1099,7 @@ static int link_push_uplink_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) {
 
         if (link->network->dhcp_use_dns && link->dhcp_lease) {
                 const struct in_addr *da = NULL;
-                int n;
+                int j, n;
 
                 n = sd_dhcp_lease_get_dns(link->dhcp_lease, &da);
                 if (n > 0) {
@@ -1002,8 +1107,9 @@ static int link_push_uplink_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) {
                         if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + n))
                                 return log_oom();
 
-                        memcpy(addresses + n_addresses, da, n * sizeof(struct in_addr));
-                        n_addresses += n;
+                        for (j = 0; j < n; j++)
+                                if (in4_addr_is_non_local(&da[j]))
+                                        addresses[n_addresses++] = da[j];
                 }
         }
 
@@ -1042,7 +1148,7 @@ static int link_push_uplink_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) {
 
         if (link->network->dhcp_use_ntp && link->dhcp_lease) {
                 const struct in_addr *da = NULL;
-                int n;
+                int j, n;
 
                 n = sd_dhcp_lease_get_ntp(link->dhcp_lease, &da);
                 if (n > 0) {
@@ -1050,8 +1156,9 @@ static int link_push_uplink_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) {
                         if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + n))
                                 return log_oom();
 
-                        memcpy(addresses + n_addresses, da, n * sizeof(struct in_addr));
-                        n_addresses += n;
+                        for (j = 0; j < n; j++)
+                                if (in4_addr_is_non_local(&da[j]))
+                                        addresses[n_addresses++] = da[j];
                 }
         }
 
@@ -1264,15 +1371,12 @@ static int link_set_bridge_vlan(Link *link) {
 }
 
 static int link_set_proxy_arp(Link *link) {
-        const char *p = NULL;
         int r;
 
         if (!link_proxy_arp_enabled(link))
                 return 0;
 
-        p = strjoina("/proc/sys/net/ipv4/conf/", link->ifname, "/proxy_arp");
-
-        r = write_string_file(p, one_zero(link->network->proxy_arp), WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp", link->network->proxy_arp > 0);
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot configure proxy ARP for interface: %m");
 
@@ -1341,7 +1445,7 @@ int link_set_mtu(Link *link, uint32_t mtu) {
                 return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
 
         /* If IPv6 not configured (no static IPv6 address and IPv6LL autoconfiguration is disabled)
-         * for this interface, or if it is a bridge slave, then disable IPv6 else enable it. */
+         * for this interface, then disable IPv6 else enable it. */
         (void) link_enable_ipv6(link);
 
         /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes
@@ -1520,7 +1624,26 @@ static int link_set_bridge(Link *link) {
         return r;
 }
 
-static int link_bond_set(Link *link) {
+static int link_set_bond_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+        int r;
+
+        assert(m);
+        assert(link);
+        assert(link->ifname);
+
+        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+                return 1;
+
+        r = sd_netlink_message_get_errno(m);
+        if (r < 0) {
+                log_link_warning_errno(link, r, "Could not set bonding interface: %m");
+                return 1;
+        }
+
+        return 1;
+}
+
+static int link_set_bond(Link *link) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
         int r;
 
@@ -1563,7 +1686,7 @@ static int link_bond_set(Link *link) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m");
 
-        r = netlink_call_async(link->manager->rtnl, NULL, req, set_flags_handler,
+        r = netlink_call_async(link->manager->rtnl, NULL, req, link_set_bond_handler,
                                link_netlink_destroy_callback, link);
         if (r < 0)
                 return log_link_error_errno(link, r,  "Could not send rtnetlink message: %m");
@@ -1573,6 +1696,28 @@ static int link_bond_set(Link *link) {
         return r;
 }
 
+static int link_append_to_master(Link *link, NetDev *netdev) {
+        Link *master;
+        int r;
+
+        assert(link);
+        assert(netdev);
+
+        r = link_get(link->manager, netdev->ifindex, &master);
+        if (r < 0)
+                return r;
+
+        r = hashmap_ensure_allocated(&master->slaves, NULL);
+        if (r < 0)
+                return r;
+
+        r = hashmap_put(master->slaves, INT_TO_PTR(link->ifindex), link);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int link_lldp_save(Link *link) {
         _cleanup_free_ char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
@@ -1734,7 +1879,7 @@ static int link_acquire_conf(Link *link) {
         if (r < 0)
                 return r;
 
-        if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) == 0) {
+        if (!in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address)) {
                 r = link_acquire_ipv6_conf(link);
                 if (r < 0)
                         return r;
@@ -1804,20 +1949,12 @@ static int link_configure_addrgen_mode(Link *link) {
 
         if (!link_ipv6ll_enabled(link))
                 ipv6ll_mode = IN6_ADDR_GEN_MODE_NONE;
-        else {
-                const char *p = NULL;
-                _cleanup_free_ char *stable_secret = NULL;
-
-                p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/stable_secret");
-
+        else if (sysctl_read_ip_property(AF_INET6, link->ifname, "stable_secret", NULL) < 0)
                 /* The file may not exist. And event if it exists, when stable_secret is unset,
-                 * then reading the file fails and EIO is returned. */
-                r = read_one_line_file(p, &stable_secret);
-                if (r < 0)
-                        ipv6ll_mode = IN6_ADDR_GEN_MODE_EUI64;
-                else
-                        ipv6ll_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
-        }
+                 * reading the file fails with EIO. */
+                ipv6ll_mode = IN6_ADDR_GEN_MODE_EUI64;
+        else
+                ipv6ll_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
 
         r = sd_netlink_message_append_u8(req, IFLA_INET6_ADDR_GEN_MODE, ipv6ll_mode);
         if (r < 0)
@@ -2371,12 +2508,20 @@ static int link_joined(Link *link) {
                 r = link_set_bridge(link);
                 if (r < 0)
                         log_link_error_errno(link, r, "Could not set bridge message: %m");
+
+                r = link_append_to_master(link, link->network->bridge);
+                if (r < 0)
+                        log_link_error_errno(link, r, "Failed to add to bridge master's slave list: %m");
         }
 
         if (link->network->bond) {
-                r = link_bond_set(link);
+                r = link_set_bond(link);
                 if (r < 0)
                         log_link_error_errno(link, r, "Could not set bond message: %m");
+
+                r = link_append_to_master(link, link->network->bond);
+                if (r < 0)
+                        log_link_error_errno(link, r, "Failed to add to bond master's slave list: %m");
         }
 
         if (link->network->use_br_vlan &&
@@ -2400,6 +2545,8 @@ static int netdev_join_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li
 
         assert(link);
         assert(link->network);
+        assert(link->enslaving > 0);
+        assert(!link->enslaved_raw);
 
         link->enslaving--;
 
@@ -2414,8 +2561,10 @@ static int netdev_join_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li
         } else
                 log_link_debug(link, "Joined netdev");
 
-        if (link->enslaving <= 0)
+        if (link->enslaving == 0) {
+                link->enslaved_raw = true;
                 link_joined(link);
+        }
 
         return 1;
 }
@@ -2432,12 +2581,8 @@ static int link_enter_join_netdev(Link *link) {
         link_set_state(link, LINK_STATE_CONFIGURING);
 
         link_dirty(link);
-
-        if (!link->network->bridge &&
-            !link->network->bond &&
-            !link->network->vrf &&
-            hashmap_isempty(link->network->stacked_netdevs))
-                return link_joined(link);
+        link->enslaving = 0;
+        link->enslaved_raw = false;
 
         if (link->network->bond) {
                 if (link->network->bond->state == NETDEV_STATE_READY &&
@@ -2449,6 +2594,8 @@ static int link_enter_join_netdev(Link *link) {
                            LOG_NETDEV_INTERFACE(link->network->bond),
                            LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bond->ifname));
 
+                link->enslaving++;
+
                 r = netdev_join(link->network->bond, link, netdev_join_handler);
                 if (r < 0) {
                         log_struct_errno(LOG_WARNING, r,
@@ -2458,8 +2605,6 @@ static int link_enter_join_netdev(Link *link) {
                         link_enter_failed(link);
                         return r;
                 }
-
-                link->enslaving++;
         }
 
         if (link->network->bridge) {
@@ -2468,6 +2613,8 @@ static int link_enter_join_netdev(Link *link) {
                            LOG_NETDEV_INTERFACE(link->network->bridge),
                            LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bridge->ifname));
 
+                link->enslaving++;
+
                 r = netdev_join(link->network->bridge, link, netdev_join_handler);
                 if (r < 0) {
                         log_struct_errno(LOG_WARNING, r,
@@ -2477,8 +2624,6 @@ static int link_enter_join_netdev(Link *link) {
                         link_enter_failed(link);
                         return r;
                 }
-
-                link->enslaving++;
         }
 
         if (link->network->vrf) {
@@ -2487,6 +2632,8 @@ static int link_enter_join_netdev(Link *link) {
                            LOG_NETDEV_INTERFACE(link->network->vrf),
                            LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->vrf->ifname));
 
+                link->enslaving++;
+
                 r = netdev_join(link->network->vrf, link, netdev_join_handler);
                 if (r < 0) {
                         log_struct_errno(LOG_WARNING, r,
@@ -2496,8 +2643,6 @@ static int link_enter_join_netdev(Link *link) {
                         link_enter_failed(link);
                         return r;
                 }
-
-                link->enslaving++;
         }
 
         HASHMAP_FOREACH(netdev, link->network->stacked_netdevs, i) {
@@ -2512,6 +2657,8 @@ static int link_enter_join_netdev(Link *link) {
                            LOG_NETDEV_INTERFACE(netdev),
                            LOG_LINK_MESSAGE(link, "Enslaving by '%s'", netdev->ifname));
 
+                link->enslaving++;
+
                 r = netdev_join(netdev, link, netdev_join_handler);
                 if (r < 0) {
                         log_struct_errno(LOG_WARNING, r,
@@ -2521,10 +2668,11 @@ static int link_enter_join_netdev(Link *link) {
                         link_enter_failed(link);
                         return r;
                 }
-
-                link->enslaving++;
         }
 
+        if (link->enslaving == 0)
+                return link_joined(link);
+
         return 0;
 }
 
@@ -2541,7 +2689,7 @@ static int link_set_ipv4_forward(Link *link) {
          * primarily to keep IPv4 and IPv6 packet forwarding behaviour
          * somewhat in sync (see below). */
 
-        r = write_string_file("/proc/sys/net/ipv4/ip_forward", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property(AF_INET, NULL, "ip_forward", "1");
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m");
 
@@ -2563,7 +2711,7 @@ static int link_set_ipv6_forward(Link *link) {
          * same behaviour there and also propagate the setting from
          * one to all, to keep things simple (see above). */
 
-        r = write_string_file("/proc/sys/net/ipv6/conf/all/forwarding", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property(AF_INET6, "all", "forwarding", "1");
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m");
 
@@ -2571,19 +2719,14 @@ static int link_set_ipv6_forward(Link *link) {
 }
 
 static int link_set_ipv6_privacy_extensions(Link *link) {
-        char buf[DECIMAL_STR_MAX(unsigned) + 1];
         IPv6PrivacyExtensions s;
-        const char *p = NULL;
         int r;
 
         s = link_ipv6_privacy_extensions(link);
         if (s < 0)
                 return 0;
 
-        p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/use_tempaddr");
-        xsprintf(buf, "%u", (unsigned) link->network->ipv6_privacy_extensions);
-
-        r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property_int(AF_INET6, link->ifname, "use_tempaddr", (int) link->network->ipv6_privacy_extensions);
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot configure IPv6 privacy extension for interface: %m");
 
@@ -2591,7 +2734,6 @@ static int link_set_ipv6_privacy_extensions(Link *link) {
 }
 
 static int link_set_ipv6_accept_ra(Link *link) {
-        const char *p = NULL;
         int r;
 
         /* Make this a NOP if IPv6 is not available */
@@ -2604,10 +2746,7 @@ static int link_set_ipv6_accept_ra(Link *link) {
         if (!link->network)
                 return 0;
 
-        p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/accept_ra");
-
-        /* We handle router advertisements ourselves, tell the kernel to GTFO */
-        r = write_string_file(p, "0", WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property(AF_INET6, link->ifname, "accept_ra", "0");
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot disable kernel IPv6 accept_ra for interface: %m");
 
@@ -2615,8 +2754,6 @@ static int link_set_ipv6_accept_ra(Link *link) {
 }
 
 static int link_set_ipv6_dad_transmits(Link *link) {
-        char buf[DECIMAL_STR_MAX(int) + 1];
-        const char *p = NULL;
         int r;
 
         /* Make this a NOP if IPv6 is not available */
@@ -2632,10 +2769,7 @@ static int link_set_ipv6_dad_transmits(Link *link) {
         if (link->network->ipv6_dad_transmits < 0)
                 return 0;
 
-        p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/dad_transmits");
-        xsprintf(buf, "%i", link->network->ipv6_dad_transmits);
-
-        r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property_int(AF_INET6, link->ifname, "dad_transmits", link->network->ipv6_dad_transmits);
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot set IPv6 dad transmits for interface: %m");
 
@@ -2643,8 +2777,6 @@ static int link_set_ipv6_dad_transmits(Link *link) {
 }
 
 static int link_set_ipv6_hop_limit(Link *link) {
-        char buf[DECIMAL_STR_MAX(int) + 1];
-        const char *p = NULL;
         int r;
 
         /* Make this a NOP if IPv6 is not available */
@@ -2660,10 +2792,7 @@ static int link_set_ipv6_hop_limit(Link *link) {
         if (link->network->ipv6_hop_limit < 0)
                 return 0;
 
-        p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/hop_limit");
-        xsprintf(buf, "%i", link->network->ipv6_hop_limit);
-
-        r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit);
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface: %m");
 
@@ -2671,8 +2800,6 @@ static int link_set_ipv6_hop_limit(Link *link) {
 }
 
 static int link_set_ipv6_mtu(Link *link) {
-        char buf[DECIMAL_STR_MAX(unsigned) + 1];
-        const char *p = NULL;
         int r;
 
         /* Make this a NOP if IPv6 is not available */
@@ -2685,11 +2812,7 @@ static int link_set_ipv6_mtu(Link *link) {
         if (link->network->ipv6_mtu == 0)
                 return 0;
 
-        p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/mtu");
-
-        xsprintf(buf, "%" PRIu32, link->network->ipv6_mtu);
-
-        r = write_string_file(p, buf, WRITE_STRING_FILE_DISABLE_BUFFER);
+        r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", link->network->ipv6_mtu);
         if (r < 0)
                 log_link_warning_errno(link, r, "Cannot set IPv6 MTU for interface: %m");
 
@@ -3565,7 +3688,7 @@ static int link_carrier_lost(Link *link) {
 
         assert(link);
 
-        if (link->network->ignore_carrier_loss)
+        if (link->network && link->network->ignore_carrier_loss)
                 return 0;
 
         /* Some devices reset itself while setting the MTU. This causes the DHCP client fall into a loop.
@@ -3894,12 +4017,9 @@ int link_save(Link *link) {
                         const struct in_addr *addresses;
 
                         r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
-                        if (r > 0) {
-                                if (space)
-                                        fputc(' ', f);
-                                serialize_in_addrs(f, addresses, r);
-                                space = true;
-                        }
+                        if (r > 0)
+                                if (serialize_in_addrs(f, addresses, r, space, in4_addr_is_non_local) > 0)
+                                        space = true;
                 }
 
                 if (link->network->dhcp_use_dns && dhcp6_lease) {
@@ -3940,12 +4060,9 @@ int link_save(Link *link) {
                         const struct in_addr *addresses;
 
                         r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
-                        if (r > 0) {
-                                if (space)
-                                        fputc(' ', f);
-                                serialize_in_addrs(f, addresses, r);
-                                space = true;
-                        }
+                        if (r > 0)
+                                if (serialize_in_addrs(f, addresses, r, space, in4_addr_is_non_local) > 0)
+                                        space = true;
                 }
 
                 if (link->network->dhcp_use_ntp && dhcp6_lease) {
@@ -3977,9 +4094,7 @@ int link_save(Link *link) {
                                 (void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains);
                 }
 
-                fputs("DOMAINS=", f);
-                space = false;
-                fputstrv(f, link->network->search_domains, NULL, &space);
+                ordered_set_print(f, "DOMAINS=", link->network->search_domains);
 
                 if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
                         NDiscDNSSL *dd;
@@ -3997,9 +4112,7 @@ int link_save(Link *link) {
 
                 fputc('\n', f);
 
-                fputs("ROUTE_DOMAINS=", f);
-                space = false;
-                fputstrv(f, link->network->route_domains, NULL, &space);
+                ordered_set_print(f, "ROUTE_DOMAINS=", link->network->route_domains);
 
                 if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
                         NDiscDNSSL *dd;
@@ -4090,7 +4203,7 @@ int link_save(Link *link) {
                 r = sd_dhcp_lease_get_address(link->dhcp_lease, &address);
                 if (r >= 0) {
                         fputs("DHCP4_ADDRESS=", f);
-                        serialize_in_addrs(f, &address, 1);
+                        serialize_in_addrs(f, &address, 1, false, NULL);
                         fputc('\n', f);
                 }
 
@@ -4110,7 +4223,7 @@ int link_save(Link *link) {
                 r = sd_ipv4ll_get_address(link->ipv4ll, &address);
                 if (r >= 0) {
                         fputs("IPV4LL_ADDRESS=", f);
-                        serialize_in_addrs(f, &address, 1);
+                        serialize_in_addrs(f, &address, 1, false, NULL);
                         fputc('\n', f);
                 }
         }
@@ -4181,6 +4294,7 @@ static const char* const link_operstate_table[_LINK_OPERSTATE_MAX] = {
         [LINK_OPERSTATE_DORMANT] = "dormant",
         [LINK_OPERSTATE_CARRIER] = "carrier",
         [LINK_OPERSTATE_DEGRADED] = "degraded",
+        [LINK_OPERSTATE_ENSLAVED] = "enslaved",
         [LINK_OPERSTATE_ROUTABLE] = "routable",
 };