Merge pull request #32195 from yuwata/network-ndisc-mtu

network: several cleanups for IPv6 MTU
This commit is contained in:
Luca Boccassi 2024-04-10 23:12:21 +01:00 committed by GitHub
commit d0ea800943
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 160 additions and 65 deletions

View File

@ -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 /* We set the ipv6 mtu after the device mtu, but the kernel resets
* ipv6 mtu on NETDEV_UP, so we need to reset it. */ * 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) if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); 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; 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) { if (link->dhcp_client) {
r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu); r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu);
if (r < 0) if (r < 0)

View File

@ -167,6 +167,7 @@ typedef struct Link {
Set *ndisc_captive_portals; Set *ndisc_captive_portals;
Set *ndisc_pref64; Set *ndisc_pref64;
Set *ndisc_redirects; Set *ndisc_redirects;
uint32_t ndisc_mtu;
unsigned ndisc_messages; unsigned ndisc_messages;
bool ndisc_configured:1; bool ndisc_configured:1;

View File

@ -1048,6 +1048,37 @@ static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) {
return 0; 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) { static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
usec_t lifetime_valid_usec, lifetime_preferred_usec; usec_t lifetime_valid_usec, lifetime_preferred_usec;
struct in6_addr prefix; struct in6_addr prefix;
@ -2122,6 +2153,10 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
if (r < 0) if (r < 0)
return r; return r;
r = ndisc_router_process_mtu(link, rt);
if (r < 0)
return r;
r = ndisc_router_process_options(link, rt); r = ndisc_router_process_options(link, rt);
if (r < 0) if (r < 0)
return r; return r;
@ -2456,6 +2491,7 @@ void ndisc_flush(Link *link) {
link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); link->ndisc_captive_portals = set_free(link->ndisc_captive_portals);
link->ndisc_pref64 = set_free(link->ndisc_pref64); link->ndisc_pref64 = set_free(link->ndisc_pref64);
link->ndisc_redirects = set_free(link->ndisc_redirects); 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] = { static const char* const ndisc_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = {

View File

@ -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) { static int link_set_mtu_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) {
int r; return set_link_handler_internal(rtnl, m, req, link, /* ignore = */ true, get_link_default_handler);
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;
} }
static int link_configure_fill_message( static int link_configure_fill_message(
@ -453,6 +441,43 @@ static bool netdev_is_ready(NetDev *netdev) {
return true; 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) { static int link_is_ready_to_set_link(Link *link, Request *req) {
int r; int r;
@ -570,13 +595,24 @@ static int link_is_ready_to_set_link(Link *link, Request *req) {
})) }))
return false; 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, if (ordered_set_contains(link->manager->request_queue,
&(const Request) { &(const Request) {
.link = link, .link = link,
.type = REQUEST_TYPE_SET_LINK_CAN, .type = REQUEST_TYPE_SET_LINK_CAN,
})) }))
return false; 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: default:
break; break;
@ -865,51 +901,12 @@ int link_request_to_set_master(Link *link) {
} }
int link_request_to_set_mtu(Link *link, uint32_t mtu) { int link_request_to_set_mtu(Link *link, uint32_t mtu) {
const char *origin;
uint32_t min_mtu, max_mtu;
Request *req; Request *req;
int r; int r;
assert(link); assert(link);
assert(link->network);
min_mtu = link->min_mtu; if (mtu == 0)
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)
return 0; return 0;
r = link_request_set_link(link, REQUEST_TYPE_SET_LINK_MTU, r = link_request_set_link(link, REQUEST_TYPE_SET_LINK_MTU,

View File

@ -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); 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; uint32_t mtu = 0;
assert(link); assert(link);
if (!link_is_configured_for_family(link, AF_INET6)) if (!link_is_configured_for_family(link, AF_INET6))
return 0; 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; return 0;
mtu = link->network->ipv6_mtu; if (mtu > link->mtu) {
if (mtu > link->max_mtu) { log_link_full(link, log_level,
log_link_warning(link, "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".",
mtu, link->max_mtu); mtu, link->mtu);
mtu = link->max_mtu; mtu = link->mtu;
} }
return sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", 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) if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m"); 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) if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m");

View File

@ -31,7 +31,7 @@ void manager_set_sysctl(Manager *manager);
int link_get_ip_forwarding(Link *link, int family); int link_get_ip_forwarding(Link *link, int family);
int link_set_sysctl(Link *link); 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_; const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_;
IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_;

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth-peer
[Network]
IPv6AcceptRA=no

View File

@ -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 redirect', ipv='-6', timeout_sec=10)
self.wait_route_dropped('veth99', 'proto ra', 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): def test_ipv6_token_prefixstable(self):
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network') copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network')
start_networkd() start_networkd()