]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-link.c
Merge pull request #1979 from evverx/build-install-systemd-path-completion
[thirdparty/systemd.git] / src / network / networkd-link.c
index 13d2fc6d0d7eb97f6a1143272e4a48f1646d9487..4af895a6fba00761c9301c8f65b81a8aa2e096e3 100644 (file)
@@ -111,20 +111,56 @@ static bool link_ipv4_forward_enabled(Link *link) {
         if (!link->network)
                 return false;
 
+        if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
+                return false;
+
         return link->network->ip_forward & ADDRESS_FAMILY_IPV4;
 }
 
 static bool link_ipv6_forward_enabled(Link *link) {
+
+        if (!socket_ipv6_is_supported())
+                return false;
+
         if (link->flags & IFF_LOOPBACK)
                 return false;
 
         if (!link->network)
                 return false;
 
+        if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
+                return false;
+
         return link->network->ip_forward & ADDRESS_FAMILY_IPV6;
 }
 
+bool link_ipv6_accept_ra_enabled(Link *link) {
+        if (link->flags & IFF_LOOPBACK)
+                return false;
+
+        if (!link->network)
+                return false;
+
+        /* If unset use system default (enabled if local forwarding is disabled.
+         * disabled if local forwarding is enabled).
+         * If set, ignore or enforce RA independent of local forwarding state.
+         */
+        if (link->network->ipv6_accept_ra < 0)
+                /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */
+                return !link_ipv6_forward_enabled(link);
+        else if (link->network->ipv6_accept_ra > 0)
+                /* accept RA even if ip_forward is enabled */
+                return true;
+        else
+                /* ignore RA */
+                return false;
+}
+
 static IPv6PrivacyExtensions link_ipv6_privacy_extensions(Link *link) {
+
+        if (!socket_ipv6_is_supported())
+                return _IPV6_PRIVACY_EXTENSIONS_INVALID;
+
         if (link->flags & IFF_LOOPBACK)
                 return _IPV6_PRIVACY_EXTENSIONS_INVALID;
 
@@ -470,9 +506,6 @@ static int link_stop_clients(Link *link) {
         assert(link->manager);
         assert(link->manager->event);
 
-        if (!link->network)
-                return 0;
-
         if (link->dhcp_client) {
                 k = sd_dhcp_client_stop(link->dhcp_client);
                 if (k < 0)
@@ -485,13 +518,13 @@ static int link_stop_clients(Link *link) {
                         r = log_link_warning_errno(link, r, "Could not stop IPv4 link-local: %m");
         }
 
-        if(link->ndisc_router_discovery) {
-                if (link->dhcp6_client) {
-                        k = sd_dhcp6_client_stop(link->dhcp6_client);
-                        if (k < 0)
-                                r = log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m");
-                }
+        if (link->dhcp6_client) {
+                k = sd_dhcp6_client_stop(link->dhcp6_client);
+                if (k < 0)
+                        r = log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m");
+        }
 
+        if (link->ndisc_router_discovery) {
                 k = sd_ndisc_stop(link->ndisc_router_discovery);
                 if (k < 0)
                         r = log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m");
@@ -581,6 +614,10 @@ void link_check_ready(Link *link) {
                     !link->ipv4ll_route)
                         return;
 
+        if (link_ipv6ll_enabled(link))
+                if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) > 0)
+                        return;
+
         if ((link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) &&
              !link->dhcp4_configured) ||
             (link_dhcp6_enabled(link) && !link_dhcp4_enabled(link) &&
@@ -589,6 +626,9 @@ void link_check_ready(Link *link) {
              !link->dhcp4_configured && !link->dhcp6_configured))
                 return;
 
+        if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured)
+                return;
+
         SET_FOREACH(a, link->addresses, i)
                 if (!address_is_ready(a))
                         return;
@@ -1213,6 +1253,39 @@ static void lldp_handler(sd_lldp *lldp, int event, void *userdata) {
         }
 }
 
+static int link_acquire_ipv6_conf(Link *link) {
+        int r;
+
+        assert(link);
+
+        if (link_dhcp6_enabled(link)) {
+                assert(link->dhcp6_client);
+                assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
+
+                log_link_debug(link, "Acquiring DHCPv6 lease");
+
+                r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address);
+                if (r < 0 && r != -EBUSY)
+                        return log_link_warning_errno(link, r, "Could not set IPv6LL address in DHCP client: %m");
+
+                r = sd_dhcp6_client_start(link->dhcp6_client);
+                if (r < 0 && r != -EBUSY)
+                        return log_link_warning_errno(link, r,  "Could not acquire DHCPv6 lease: %m");
+        }
+
+        if (link_ipv6_accept_ra_enabled(link)) {
+                assert(link->ndisc_router_discovery);
+
+                log_link_debug(link, "Discovering IPv6 routers");
+
+                r = sd_ndisc_router_discovery_start(link->ndisc_router_discovery);
+                if (r < 0 && r != -EBUSY)
+                        return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m");
+        }
+
+        return 0;
+}
+
 static int link_acquire_conf(Link *link) {
         int r;
 
@@ -1241,16 +1314,6 @@ static int link_acquire_conf(Link *link) {
                         return log_link_warning_errno(link, r, "Could not acquire DHCPv4 lease: %m");
         }
 
-        if (link_dhcp6_enabled(link)) {
-                assert(link->ndisc_router_discovery);
-
-                log_link_debug(link, "Discovering IPv6 routers");
-
-                r = sd_ndisc_router_discovery_start(link->ndisc_router_discovery);
-                if (r < 0)
-                        return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m");
-        }
-
         if (link_lldp_enabled(link)) {
                 assert(link->lldp);
 
@@ -1800,55 +1863,43 @@ static int link_enter_join_netdev(Link *link) {
 }
 
 static int link_set_ipv4_forward(Link *link) {
-        const char *p = NULL, *v;
         int r;
 
-        if (link->flags & IFF_LOOPBACK)
-                return 0;
-
-        if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
+        if (!link_ipv4_forward_enabled(link))
                 return 0;
 
-        p = strjoina("/proc/sys/net/ipv4/conf/", link->ifname, "/forwarding");
-        v = one_zero(link_ipv4_forward_enabled(link));
-
-        r = write_string_file(p, v, 0);
-        if (r < 0) {
-                /* If the right value is set anyway, don't complain */
-                if (verify_one_line_file(p, v) > 0)
-                        return 0;
+        /* We propagate the forwarding flag from one interface to the
+         * global setting one way. This means: as long as at least one
+         * interface was configured at any time that had IP forwarding
+         * enabled the setting will stay on for good. We do this
+         * primarily to keep IPv4 and IPv6 packet forwarding behaviour
+         * somewhat in sync (see below). */
 
-                log_link_warning_errno(link, r, "Cannot configure IPv4 forwarding for interface %s: %m", link->ifname);
-        }
+        r = write_string_file("/proc/sys/net/ipv4/ip_forward", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE);
+        if (r < 0)
+                log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m");
 
         return 0;
 }
 
 static int link_set_ipv6_forward(Link *link) {
-        const char *p = NULL, *v = NULL;
         int r;
 
-        /* Make this a NOP if IPv6 is not available */
-        if (!socket_ipv6_is_supported())
-                return 0;
-
-        if (link->flags & IFF_LOOPBACK)
+        if (!link_ipv6_forward_enabled(link))
                 return 0;
 
-        if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
-                return 0;
-
-        p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/forwarding");
-        v = one_zero(link_ipv6_forward_enabled(link));
-
-        r = write_string_file(p, v, 0);
-        if (r < 0) {
-                /* If the right value is set anyway, don't complain */
-                if (verify_one_line_file(p, v) > 0)
-                        return 0;
+        /* On Linux, the IPv6 stack does not not know a per-interface
+         * packet forwarding setting: either packet forwarding is on
+         * for all, or off for all. We hence don't bother with a
+         * per-interface setting, but simply propagate the interface
+         * flag, if it is set, to the global flag, one-way. Note that
+         * while IPv4 would allow a per-interface flag, we expose the
+         * same behaviour there and also propagate the setting from
+         * one to all, to keep things simple (see above). */
 
-                log_link_warning_errno(link, r, "Cannot configure IPv6 forwarding for interface: %m");
-        }
+        r = write_string_file("/proc/sys/net/ipv6/conf/all/forwarding", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE);
+        if (r < 0)
+                log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m");
 
         return 0;
 }
@@ -1859,31 +1910,22 @@ static int link_set_ipv6_privacy_extensions(Link *link) {
         const char *p = NULL;
         int r;
 
-        /* Make this a NOP if IPv6 is not available */
-        if (!socket_ipv6_is_supported())
-                return 0;
-
         s = link_ipv6_privacy_extensions(link);
-        if (s == _IPV6_PRIVACY_EXTENSIONS_INVALID)
+        if (s < 0)
                 return 0;
 
         p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/use_tempaddr");
-        xsprintf(buf, "%u", link->network->ipv6_privacy_extensions);
-
-        r = write_string_file(p, buf, 0);
-        if (r < 0) {
-                /* If the right value is set anyway, don't complain */
-                if (verify_one_line_file(p, buf) > 0)
-                        return 0;
+        xsprintf(buf, "%u", (unsigned) link->network->ipv6_privacy_extensions);
 
+        r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE);
+        if (r < 0)
                 log_link_warning_errno(link, r, "Cannot configure IPv6 privacy extension for interface: %m");
-        }
 
         return 0;
 }
 
 static int link_set_ipv6_accept_ra(Link *link) {
-        const char *p = NULL, *v = NULL;
+        const char *p = NULL;
         int r;
 
         /* Make this a NOP if IPv6 is not available */
@@ -1893,36 +1935,21 @@ static int link_set_ipv6_accept_ra(Link *link) {
         if (link->flags & IFF_LOOPBACK)
                 return 0;
 
-        /* If unset use system default (enabled if local forwarding is disabled.
-         * disabled if local forwarding is enabled).
-         * If set, ignore or enforce RA independent of local forwarding state.
-         */
-        if (link->network->ipv6_accept_ra < 0)
-                /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */
-                v = "1";
-        else if (link->network->ipv6_accept_ra > 0)
-                /* "2" means accept RA even if ip_forward is enabled */
-                v = "2";
-        else
-                /* "0" means ignore RA */
-                v = "0";
+        if (!link->network)
+                return 0;
 
         p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/accept_ra");
 
-        r = write_string_file(p, v, 0);
-        if (r < 0) {
-                /* If the right value is set anyway, don't complain */
-                if (verify_one_line_file(p, v) > 0)
-                        return 0;
-
-                log_link_warning_errno(link, r, "Cannot configure IPv6 accept_ra for interface: %m");
-        }
+        /* We handle router advertisments ourselves, tell the kernel to GTFO */
+        r = write_string_file(p, "0", WRITE_STRING_FILE_VERIFY_ON_FAILURE);
+        if (r < 0)
+                log_link_warning_errno(link, r, "Cannot disable kernel IPv6 accept_ra for interface: %m");
 
         return 0;
 }
 
 static int link_set_ipv6_dad_transmits(Link *link) {
-        char buf[DECIMAL_STR_MAX(unsigned) + 1];
+        char buf[DECIMAL_STR_MAX(int) + 1];
         const char *p = NULL;
         int r;
 
@@ -1933,27 +1960,24 @@ static int link_set_ipv6_dad_transmits(Link *link) {
         if (link->flags & IFF_LOOPBACK)
                 return 0;
 
+        if (!link->network)
+                return 0;
+
         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);
 
-        xsprintf(buf, "%u", link->network->ipv6_dad_transmits);
-
-        r = write_string_file(p, buf, 0);
-        if (r < 0) {
-                /* If the right value is set anyway, don't complain */
-                if (verify_one_line_file(p, buf) > 0)
-                        return 0;
-
+        r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE);
+        if (r < 0)
                 log_link_warning_errno(link, r, "Cannot set IPv6 dad transmits for interface: %m");
-        }
 
         return 0;
 }
 
 static int link_set_ipv6_hop_limit(Link *link) {
-        char buf[DECIMAL_STR_MAX(unsigned) + 1];
+        char buf[DECIMAL_STR_MAX(int) + 1];
         const char *p = NULL;
         int r;
 
@@ -1964,20 +1988,46 @@ static int link_set_ipv6_hop_limit(Link *link) {
         if (link->flags & IFF_LOOPBACK)
                 return 0;
 
+        if (!link->network)
+                return 0;
+
         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);
 
-        xsprintf(buf, "%u", link->network->ipv6_hop_limit);
+        r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE);
+        if (r < 0)
+                log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface: %m");
 
-        r = write_string_file(p, buf, 0);
-        if (r < 0) {
-                /* If the right value is set anyway, don't complain */
-                if (verify_one_line_file(p, buf) > 0)
-                        return 0;
+        return 0;
+}
 
-                log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface: %m");
+static int link_drop_foreign_config(Link *link) {
+        Address *address;
+        Route *route;
+        Iterator i;
+        int r;
+
+        SET_FOREACH(address, link->addresses_foreign, i) {
+                /* we consider IPv6LL addresses to be managed by the kernel */
+                if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1)
+                        continue;
+
+                r = address_remove(address, link, link_address_remove_handler);
+                if (r < 0)
+                        return r;
+        }
+
+        SET_FOREACH(route, link->routes_foreign, i) {
+                /* do not touch routes managed by the kernel */
+                if (route->protocol == RTPROT_KERNEL)
+                        continue;
+
+                r = route_remove(route, link, link_address_remove_handler);
+                if (r < 0)
+                        return r;
         }
 
         return 0;
@@ -1990,6 +2040,10 @@ static int link_configure(Link *link) {
         assert(link->network);
         assert(link->state == LINK_STATE_PENDING);
 
+        r = link_drop_foreign_config(link);
+        if (r < 0)
+                return r;
+
         r = link_set_bridge_fdb(link);
         if (r < 0)
                 return r;
@@ -2040,7 +2094,14 @@ static int link_configure(Link *link) {
                         return r;
         }
 
-        if (link_dhcp6_enabled(link)) {
+        if (link_dhcp6_enabled(link) ||
+            link_ipv6_accept_ra_enabled(link)) {
+                r = dhcp6_configure(link);
+                if (r < 0)
+                        return r;
+        }
+
+        if (link_ipv6_accept_ra_enabled(link)) {
                 r = ndisc_configure(link);
                 if (r < 0)
                         return r;
@@ -2065,6 +2126,12 @@ static int link_configure(Link *link) {
                 r = link_acquire_conf(link);
                 if (r < 0)
                         return r;
+
+                if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) == 0) {
+                        r = link_acquire_ipv6_conf(link);
+                        if (r < 0)
+                                return r;
+                }
         }
 
         return link_enter_join_netdev(link);
@@ -2229,7 +2296,8 @@ network_file_fail:
                         if (r < 0) {
                                 log_link_debug_errno(link, r, "Failed to extract next address string: %m");
                                 continue;
-                        } if (r == 0)
+                        }
+                        if (r == 0)
                                 break;
 
                         prefixlen_str = strchr(address_str, '/');
@@ -2273,7 +2341,8 @@ network_file_fail:
                         if (r < 0) {
                                 log_link_debug_errno(link, r, "Failed to extract next route string: %m");
                                 continue;
-                        } if (r == 0)
+                        }
+                        if (r == 0)
                                 break;
 
                         prefixlen_str = strchr(route_str, '/');
@@ -2411,12 +2480,33 @@ failed:
         return r;
 }
 
+int link_ipv6ll_gained(Link *link, const struct in6_addr *address) {
+        int r;
+
+        assert(link);
+
+        log_link_info(link, "Gained IPv6LL");
+
+        link->ipv6ll_address = *address;
+        link_check_ready(link);
+
+        if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
+                r = link_acquire_ipv6_conf(link);
+                if (r < 0) {
+                        link_enter_failed(link);
+                        return r;
+                }
+        }
+
+        return 0;
+}
+
 static int link_carrier_gained(Link *link) {
         int r;
 
         assert(link);
 
-        if (link->network) {
+        if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
                 r = link_acquire_conf(link);
                 if (r < 0) {
                         link_enter_failed(link);
@@ -2639,9 +2729,8 @@ int link_save(Link *link) {
                 sd_dhcp6_lease *dhcp6_lease = NULL;
 
                 if (link->dhcp6_client) {
-                        r = sd_dhcp6_client_get_lease(link->dhcp6_client,
-                                                      &dhcp6_lease);
-                        if (r < 0)
+                        r = sd_dhcp6_client_get_lease(link->dhcp6_client, &dhcp6_lease);
+                        if (r < 0 && r != -ENOMSG)
                                 log_link_debug(link, "No DHCPv6 lease");
                 }