diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index bac44a3858..61e0e16818 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1881,7 +1881,7 @@ static int link_admin_state_up(Link *link) { /* We set the ipv6 mtu after the device mtu, but the kernel resets * ipv6 mtu on NETDEV_UP, so we need to reset it. */ - r = link_set_ipv6_mtu(link); + r = link_set_ipv6_mtu(link, LOG_INFO); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); @@ -2434,6 +2434,13 @@ static int link_update_mtu(Link *link, sd_netlink_message *message) { link->mtu = mtu; + if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) { + /* The kernel resets IPv6 MTU after changing device MTU. So, we need to re-set IPv6 MTU again. */ + r = link_set_ipv6_mtu(link, LOG_INFO); + if (r < 0) + log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); + } + if (link->dhcp_client) { r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu); if (r < 0) diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 9d15b4acce..e56ee4f419 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -167,6 +167,7 @@ typedef struct Link { Set *ndisc_captive_portals; Set *ndisc_pref64; Set *ndisc_redirects; + uint32_t ndisc_mtu; unsigned ndisc_messages; bool ndisc_configured:1; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 2b039bed96..7473a926d4 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1048,6 +1048,37 @@ static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) { return 0; } +static int ndisc_router_process_mtu(Link *link, sd_ndisc_router *rt) { + uint32_t mtu; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ndisc_use_mtu) + return 0; + + /* Ignore the MTU option if the lifetime is zero. */ + r = sd_ndisc_router_get_lifetime(rt, NULL); + if (r <= 0) + return r; + + r = sd_ndisc_router_get_mtu(rt, &mtu); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get MTU from RA: %m"); + + link->ndisc_mtu = mtu; + + r = link_set_ipv6_mtu(link, LOG_DEBUG); + if (r < 0) + log_link_warning_errno(link, r, "Failed to apply IPv6 MTU (%"PRIu32"), ignoring: %m", mtu); + + return 0; +} + static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { usec_t lifetime_valid_usec, lifetime_preferred_usec; struct in6_addr prefix; @@ -2122,6 +2153,10 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + r = ndisc_router_process_mtu(link, rt); + if (r < 0) + return r; + r = ndisc_router_process_options(link, rt); if (r < 0) return r; @@ -2456,6 +2491,7 @@ void ndisc_flush(Link *link) { link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); link->ndisc_pref64 = set_free(link->ndisc_pref64); link->ndisc_redirects = set_free(link->ndisc_redirects); + link->ndisc_mtu = 0; } static const char* const ndisc_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index eb65429a78..058bc00ba1 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -173,19 +173,7 @@ static int link_unset_master_handler(sd_netlink *rtnl, sd_netlink_message *m, Re } static int link_set_mtu_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { - int r; - - r = set_link_handler_internal(rtnl, m, req, link, /* ignore = */ true, get_link_default_handler); - if (r <= 0) - return r; - - /* The kernel resets ipv6 mtu after changing device mtu; - * we must set this here, after we've set device mtu */ - r = link_set_ipv6_mtu(link); - if (r < 0) - log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); - - return 0; + return set_link_handler_internal(rtnl, m, req, link, /* ignore = */ true, get_link_default_handler); } static int link_configure_fill_message( @@ -453,6 +441,43 @@ static bool netdev_is_ready(NetDev *netdev) { return true; } +static uint32_t link_adjust_mtu(Link *link, uint32_t mtu) { + const char *origin; + uint32_t min_mtu; + + assert(link); + assert(link->network); + + min_mtu = link->min_mtu; + origin = "the minimum MTU of the interface"; + if (link_ipv6_enabled(link)) { + /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes on the interface. Bump up + * MTU bytes to IPV6_MTU_MIN. */ + if (min_mtu < IPV6_MIN_MTU) { + min_mtu = IPV6_MIN_MTU; + origin = "the minimum IPv6 MTU"; + } + if (min_mtu < link->network->ipv6_mtu) { + min_mtu = link->network->ipv6_mtu; + origin = "the requested IPv6 MTU in IPv6MTUBytes="; + } + } + + if (mtu < min_mtu) { + log_link_warning(link, "Bumping the requested MTU %"PRIu32" to %s (%"PRIu32")", + mtu, origin, min_mtu); + mtu = min_mtu; + } + + if (mtu > link->max_mtu) { + log_link_warning(link, "Reducing the requested MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", + mtu, link->max_mtu); + mtu = link->max_mtu; + } + + return mtu; +} + static int link_is_ready_to_set_link(Link *link, Request *req) { int r; @@ -570,13 +595,24 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { })) return false; - /* Changing FD mode may affect MTU. */ + /* Changing FD mode may affect MTU. + * See https://docs.kernel.org/networking/can.html#can-fd-flexible-data-rate-driver-support + * MTU = 16 (CAN_MTU) => Classical CAN device + * MTU = 72 (CANFD_MTU) => CAN FD capable device */ if (ordered_set_contains(link->manager->request_queue, &(const Request) { .link = link, .type = REQUEST_TYPE_SET_LINK_CAN, })) return false; + + /* Now, it is ready to set MTU, but before setting, adjust requested MTU. */ + uint32_t mtu = link_adjust_mtu(link, PTR_TO_UINT32(req->userdata)); + if (mtu == link->mtu) + return -EALREADY; /* Not necessary to set the same value. */ + + req->userdata = UINT32_TO_PTR(mtu); + return true; } default: break; @@ -865,51 +901,12 @@ int link_request_to_set_master(Link *link) { } int link_request_to_set_mtu(Link *link, uint32_t mtu) { - const char *origin; - uint32_t min_mtu, max_mtu; Request *req; int r; assert(link); - assert(link->network); - min_mtu = link->min_mtu; - origin = "the minimum MTU of the interface"; - if (link_ipv6_enabled(link)) { - /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes on the interface. Bump up - * MTU bytes to IPV6_MTU_MIN. */ - if (min_mtu < IPV6_MIN_MTU) { - min_mtu = IPV6_MIN_MTU; - origin = "the minimum IPv6 MTU"; - } - if (min_mtu < link->network->ipv6_mtu) { - min_mtu = link->network->ipv6_mtu; - origin = "the requested IPv6 MTU in IPv6MTUBytes="; - } - } - - if (mtu < min_mtu) { - log_link_warning(link, "Bumping the requested MTU %"PRIu32" to %s (%"PRIu32")", - mtu, origin, min_mtu); - mtu = min_mtu; - } - - max_mtu = link->max_mtu; - if (link->iftype == ARPHRD_CAN) - /* The maximum MTU may be changed when FD mode is changed. - * See https://docs.kernel.org/networking/can.html#can-fd-flexible-data-rate-driver-support - * MTU = 16 (CAN_MTU) => Classical CAN device - * MTU = 72 (CANFD_MTU) => CAN FD capable device - * So, even if the current maximum is 16, we should not reduce the requested value now. */ - max_mtu = MAX(max_mtu, 72u); - - if (mtu > max_mtu) { - log_link_warning(link, "Reducing the requested MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, max_mtu); - mtu = max_mtu; - } - - if (link->mtu == mtu) + if (mtu == 0) return 0; r = link_request_set_link(link, REQUEST_TYPE_SET_LINK_MTU, diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index f9db1f7f4a..68c23e0eb7 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -250,22 +250,28 @@ static int link_set_ipv6_proxy_ndp(Link *link) { return sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "proxy_ndp", v); } -int link_set_ipv6_mtu(Link *link) { - uint32_t mtu; +int link_set_ipv6_mtu(Link *link, int log_level) { + uint32_t mtu = 0; assert(link); if (!link_is_configured_for_family(link, AF_INET6)) return 0; - if (link->network->ipv6_mtu == 0) + assert(link->network); + + if (link->network->ndisc_use_mtu) + mtu = link->ndisc_mtu; + if (mtu == 0) + mtu = link->network->ipv6_mtu; + if (mtu == 0) return 0; - mtu = link->network->ipv6_mtu; - if (mtu > link->max_mtu) { - log_link_warning(link, "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, link->max_mtu); - mtu = link->max_mtu; + if (mtu > link->mtu) { + log_link_full(link, log_level, + "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", + mtu, link->mtu); + mtu = link->mtu; } return sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu); @@ -355,7 +361,7 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m"); - r = link_set_ipv6_mtu(link); + r = link_set_ipv6_mtu(link, LOG_INFO); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); diff --git a/src/network/networkd-sysctl.h b/src/network/networkd-sysctl.h index a47dda015d..d7a9b1f320 100644 --- a/src/network/networkd-sysctl.h +++ b/src/network/networkd-sysctl.h @@ -31,7 +31,7 @@ void manager_set_sysctl(Manager *manager); int link_get_ip_forwarding(Link *link, int family); int link_set_sysctl(Link *link); -int link_set_ipv6_mtu(Link *link); +int link_set_ipv6_mtu(Link *link, int log_level); const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_; IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; diff --git a/test/test-network/conf/25-veth-peer-no-address.network b/test/test-network/conf/25-veth-peer-no-address.network new file mode 100644 index 0000000000..dbea3bd751 --- /dev/null +++ b/test/test-network/conf/25-veth-peer-no-address.network @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth-peer + +[Network] +IPv6AcceptRA=no diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index b510207e35..3732e7a0d7 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5572,6 +5572,48 @@ class NetworkdRATests(unittest.TestCase, Utilities): self.wait_route_dropped('veth99', 'proto redirect', ipv='-6', timeout_sec=10) self.wait_route_dropped('veth99', 'proto ra', ipv='-6', timeout_sec=10) + def check_ndisc_mtu(self, mtu): + for _ in range(20): + output = read_ipv6_sysctl_attr('veth99', 'mtu') + if output == f'{mtu}': + break + time.sleep(0.5) + else: + self.fail(f'IPv6 MTU does not matches: value={output}, expected={mtu}') + + def test_ndisc_mtu(self): + if not os.path.exists(test_ndisc_send): + self.skipTest(f"{test_ndisc_send} does not exist.") + + copy_network_unit('25-veth.netdev', + '25-veth-peer-no-address.network', + '25-ipv6-prefix-veth-token-static.network') + start_networkd() + self.wait_online('veth-peer:degraded') + + for _ in range(20): + output = read_networkd_log() + if 'veth99: NDISC: Started IPv6 Router Solicitation client' in output: + break + time.sleep(0.5) + else: + self.fail('sd-ndisc does not started on veth99.') + + check_output(f'{test_ndisc_send} --interface veth-peer --type ra --lifetime 1hour --mtu 1400') + self.check_ndisc_mtu(1400) + + check_output(f'{test_ndisc_send} --interface veth-peer --type ra --lifetime 1hour --mtu 1410') + self.check_ndisc_mtu(1410) + + check_output('ip link set dev veth99 mtu 1600') + self.check_ndisc_mtu(1410) + + check_output(f'{test_ndisc_send} --interface veth-peer --type ra --lifetime 1hour --mtu 1700') + self.check_ndisc_mtu(1600) + + check_output('ip link set dev veth99 mtu 1800') + self.check_ndisc_mtu(1700) + def test_ipv6_token_prefixstable(self): copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network') start_networkd()