From d4386dd1c84ecb0c6eff0160f64849d24a1cf989 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 10 Apr 2024 11:35:21 +0900 Subject: [PATCH 1/5] network/setlink: adjust requested MTU when it is ready to set This should not change any effective behavior. Just for safety, and making the logic consistent with others, e.g. setting master ifindex. --- src/network/networkd-setlink.c | 91 +++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index eb65429a783..a7e04dde2e7 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -453,6 +453,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 +607,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 +913,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, From eb426caae79ae2929213702d40411793b209b942 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 10 Apr 2024 11:47:09 +0900 Subject: [PATCH 2/5] network: always set IPv6 MTU when we detect the device MTU is changed Then, we can keep the IPv6 MTU even if the device MTU is changed by an external tool, e.g. 'ip link set' command. --- src/network/networkd-link.c | 7 +++++++ src/network/networkd-setlink.c | 14 +------------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index bac44a38581..a69402ee9fd 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -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); + 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-setlink.c b/src/network/networkd-setlink.c index a7e04dde2e7..058bc00ba10 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( From fb71748e4f8596e9a2878c3a8968ec81b7b32d84 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 10 Apr 2024 10:07:50 +0900 Subject: [PATCH 3/5] network/sysctl: round IPv6 MTU with the current device MTU rather than the maximum MTU Otherwise, writing IPv6 MTU may fail. This also makes link_set_ipv6_mtu() take log level about rounding IPv6 MTU, and downgrade the log level from LOG_WARNING -> LOG_INFO, as we usually use LOG_WARNING for per-interface critical failure. --- src/network/networkd-link.c | 4 ++-- src/network/networkd-sysctl.c | 19 +++++++++++-------- src/network/networkd-sysctl.h | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index a69402ee9fd..61e0e16818e 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"); @@ -2436,7 +2436,7 @@ static int link_update_mtu(Link *link, sd_netlink_message *message) { 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); + r = link_set_ipv6_mtu(link, LOG_INFO); if (r < 0) log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); } diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index f9db1f7f4af..66ee2afeed2 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -250,7 +250,7 @@ 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) { +int link_set_ipv6_mtu(Link *link, int log_level) { uint32_t mtu; assert(link); @@ -258,14 +258,17 @@ int link_set_ipv6_mtu(Link *link) { if (!link_is_configured_for_family(link, AF_INET6)) return 0; - if (link->network->ipv6_mtu == 0) - return 0; + assert(link->network); 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 == 0) + return 0; + + 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 +358,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 a47dda015de..d7a9b1f3201 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_; From 8c9ef90b19a1fc4ca2a9c2d28895bef19030b861 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 10 Apr 2024 10:13:07 +0900 Subject: [PATCH 4/5] network/ndisc: set IPv6 MTU through sysctl Closes #31496. --- src/network/networkd-link.h | 1 + src/network/networkd-ndisc.c | 36 +++++++++++++++++++++++++++++++++++ src/network/networkd-sysctl.c | 7 +++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 9d15b4acce9..e56ee4f4197 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 e1853bb31cc..2c21a1ce6c6 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1027,6 +1027,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; _cleanup_set_free_ Set *addresses = NULL; @@ -2098,6 +2129,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; @@ -2432,6 +2467,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-sysctl.c b/src/network/networkd-sysctl.c index 66ee2afeed2..68c23e0eb79 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -251,7 +251,7 @@ static int link_set_ipv6_proxy_ndp(Link *link) { } int link_set_ipv6_mtu(Link *link, int log_level) { - uint32_t mtu; + uint32_t mtu = 0; assert(link); @@ -260,7 +260,10 @@ int link_set_ipv6_mtu(Link *link, int log_level) { assert(link->network); - mtu = link->network->ipv6_mtu; + if (link->network->ndisc_use_mtu) + mtu = link->ndisc_mtu; + if (mtu == 0) + mtu = link->network->ipv6_mtu; if (mtu == 0) return 0; From a0430b0d95ef9e235dd0e8f62712a8702527e4a9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 10 Apr 2024 11:14:13 +0900 Subject: [PATCH 5/5] test-network: add test case for ndisc MTU option --- .../conf/25-veth-peer-no-address.network | 6 +++ test/test-network/systemd-networkd-tests.py | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 test/test-network/conf/25-veth-peer-no-address.network 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 00000000000..dbea3bd7517 --- /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 4c69bc90336..99e46a24718 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()