1
0
mirror of https://github.com/containers/netavark.git synced 2026-02-05 06:45:56 +01:00

Set bridge MTU to match default route.

When creating a custom network, netavark previously used
the default Linux bridge MTU (1500), which undermined
performance optimization strategy of using a large MTU
(65520) for improved TCP throughput.

This patch ensures that when the user does not explicitly
set an MTU, podman attempts to derive a more suitable MTU
from the system's default route interface, preserving
intended performance benefits.

Also moved get_default_route_interface into core_utils for
reuse across modules.

Fixes: containers/podman#23883
Fixes: containers/podman#20009

Signed-off-by: Jan Kaluza <jkaluza@redhat.com>
This commit is contained in:
Jan Kaluza
2025-07-14 15:38:02 +02:00
parent 98f1a4a301
commit cd4d1ffb0b
5 changed files with 195 additions and 46 deletions

View File

@@ -8,6 +8,7 @@ use netlink_packet_route::link::{
};
use crate::dns::aardvark::SafeString;
use crate::network::core_utils::get_default_route_interface;
use crate::network::dhcp::{dhcp_teardown, get_dhcp_lease};
use crate::{
dns::aardvark::AardvarkEntry,
@@ -557,18 +558,22 @@ fn create_interfaces(
Option<sysctl::SysctlDWriter<'static, String, String>>,
)> {
let mut sysctl_writer = None;
let (bridge_index, mac) = match host.get_link(netlink::LinkID::Name(
let (bridge_index, mtu, mac) = match host.get_link(netlink::LinkID::Name(
data.bridge_interface_name.to_string(),
)) {
Ok(bridge) => (
validate_bridge_link(
Ok(bridge) => {
let (bridge_index, mtu) = validate_bridge_link(
bridge,
data.vlan.is_some(),
host,
&data.bridge_interface_name,
)?,
None,
),
)?;
(
bridge_index,
if data.mtu == 0 { mtu } else { data.mtu },
None,
)
}
Err(err) => match err.unwrap() {
NetavarkError::Netlink(e) => {
if -e.raw_code() != libc::ENODEV {
@@ -652,6 +657,21 @@ fn create_interfaces(
data.bridge_interface_name.to_string(),
InfoKind::Bridge,
);
let mut mtu = data.mtu;
if mtu == 0 {
// if we have a default route, use its mtu as default
if let Ok(iface_name) = get_default_route_interface(host) {
match core_utils::get_mtu_from_iface(host, &iface_name) {
Ok(iface_mtu) => {
mtu = iface_mtu;
},
Err(e) => debug!(
"failed to get mtu for default interface {iface_name}: {e}, using kernel default",
),
}
}
}
create_link_opts.mtu = data.mtu;
if data.vlan.is_some() {
@@ -699,7 +719,7 @@ fn create_interfaces(
host.set_up(netlink::LinkID::ID(link.header.index))
.wrap("set bridge up")?;
(link.header.index, mac)
(link.header.index, mtu, mac)
}
_ => return Err(err),
},
@@ -714,6 +734,7 @@ fn create_interfaces(
internal,
hostns_fd,
netns_fd,
mtu,
)?;
Ok((mac, sysctl_writer))
}
@@ -729,11 +750,12 @@ fn create_veth_pair<'fd>(
internal: bool,
hostns_fd: BorrowedFd<'fd>,
netns_fd: BorrowedFd<'fd>,
mtu: u32,
) -> NetavarkResult<String> {
let mut peer_opts =
netlink::CreateLinkOptions::new(data.container_interface_name.to_string(), InfoKind::Veth);
peer_opts.mac = data.mac_address.clone().unwrap_or_default();
peer_opts.mtu = data.mtu;
peer_opts.mtu = mtu;
peer_opts.netns = Some(netns_fd);
let mut peer = LinkMessage::default();
@@ -741,7 +763,7 @@ fn create_veth_pair<'fd>(
let mut host_veth =
netlink::CreateLinkOptions::new(data.host_interface_name.clone(), InfoKind::Veth);
host_veth.mtu = data.mtu;
host_veth.mtu = mtu;
host_veth.primary_index = primary_index;
host_veth.info_data = Some(InfoData::Veth(InfoVeth::Peer(peer)));
@@ -880,8 +902,13 @@ fn validate_bridge_link(
vlan: bool,
netlink: &mut netlink::Socket,
br_name: &str,
) -> NetavarkResult<u32> {
) -> NetavarkResult<(u32, u32)> {
let mut mtu: u32 = 0;
let mut header_index: u32 = 0;
for nla in msg.attributes.iter() {
if let LinkAttribute::Mtu(m) = nla {
mtu = *m;
}
if let LinkAttribute::LinkInfo(info) = nla {
// when vlan is requested also check the VlanFiltering attribute
if vlan {
@@ -919,7 +946,8 @@ fn validate_bridge_link(
for inf in info.iter() {
if let LinkInfo::Kind(kind) = inf {
if *kind == InfoKind::Bridge {
return Ok(msg.header.index);
header_index = msg.header.index;
break;
} else {
return Err(NetavarkError::Message(format!(
"bridge interface {br_name} already exists but is a {kind:?} interface"
@@ -929,6 +957,11 @@ fn validate_bridge_link(
}
}
}
if header_index != 0 {
return Ok((header_index, mtu));
}
Err(NetavarkError::Message(format!(
"could not determine namespace link kind for bridge {br_name}"
)))

View File

@@ -398,3 +398,49 @@ pub fn get_mac_address(v: Vec<LinkAttribute>) -> NetavarkResult<String> {
pub fn is_using_systemd() -> bool {
Path::new("/run/systemd/system").exists()
}
pub fn get_default_route_interface(host: &mut netlink::Socket) -> NetavarkResult<String> {
let routes = host.dump_routes().wrap("dump routes")?;
for route in routes {
let mut dest = false;
let mut out_if = 0;
for nla in route.attributes {
if let netlink_packet_route::route::RouteAttribute::Destination(_) = nla {
dest = true;
}
if let netlink_packet_route::route::RouteAttribute::Oif(oif) = nla {
out_if = oif;
}
}
// if there is no dest we have a default route
// return the output interface for this route
if !dest && out_if > 0 {
let link = host.get_link(netlink::LinkID::ID(out_if))?;
let name = link.attributes.iter().find_map(|nla| {
if let LinkAttribute::IfName(name) = nla {
Some(name)
} else {
None
}
});
if let Some(name) = name {
return Ok(name.to_owned());
}
}
}
Err(NetavarkError::msg("failed to get default route interface"))
}
pub fn get_mtu_from_iface(host: &mut netlink::Socket, iface_name: &str) -> NetavarkResult<u32> {
let link = host.get_link(netlink::LinkID::Name(iface_name.to_string()))?;
for nla in link.attributes.iter() {
if let LinkAttribute::Mtu(mtu) = nla {
return Ok(*mtu);
}
}
// It is possible that the interface has no MTU set, in this case the kernel will use the default.
// We return 0 to signal this, which netavark uses to mean "kernel default".
Ok(0)
}

View File

@@ -3,10 +3,11 @@ use std::os::fd::BorrowedFd;
use std::{collections::HashMap, net::IpAddr};
use netlink_packet_route::link::{
InfoData, InfoIpVlan, InfoKind, InfoMacVlan, IpVlanMode, LinkAttribute, MacVlanMode,
InfoData, InfoIpVlan, InfoKind, InfoMacVlan, IpVlanMode, MacVlanMode,
};
use rand::distr::{Alphanumeric, SampleString};
use crate::network::core_utils::get_default_route_interface;
use crate::network::dhcp::{dhcp_teardown, get_dhcp_lease};
use crate::{
dns::aardvark::AardvarkEntry,
@@ -358,37 +359,3 @@ fn setup(
get_mac_address(dev.attributes)
}
fn get_default_route_interface(host: &mut netlink::Socket) -> NetavarkResult<String> {
let routes = host.dump_routes().wrap("dump routes")?;
for route in routes {
let mut dest = false;
let mut out_if = 0;
for nla in route.attributes {
if let netlink_packet_route::route::RouteAttribute::Destination(_) = nla {
dest = true;
}
if let netlink_packet_route::route::RouteAttribute::Oif(oif) = nla {
out_if = oif;
}
}
// if there is no dest we have a default route
// return the output interface for this route
if !dest && out_if > 0 {
let link = host.get_link(netlink::LinkID::ID(out_if))?;
let name = link.attributes.iter().find_map(|nla| {
if let LinkAttribute::IfName(name) = nla {
Some(name)
} else {
None
}
});
if let Some(name) = name {
return Ok(name.to_owned());
}
}
}
Err(NetavarkError::msg("failed to get default route interface"))
}

68
test/640-bridge-mtu.bats Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bats -*- bats -*-
#
# bridge driver tests with explicit modes
#
load helpers
function check_iface_mtu() {
local host_or_container=$1
local iface=$2
local mtu=$3
if [ "$host_or_container" = "host" ]; then
run_in_host_netns ip -j --details link show "$iface"
else
run_in_container_netns ip -j --details link show "$iface"
fi
assert_json "$output" ".[].mtu" "==" "$mtu" "$iface MTU matches $mtu"
}
function check_mtu() {
local mtu=$1
check_iface_mtu container eth0 "$mtu"
check_iface_mtu host veth0 "$mtu"
check_iface_mtu host podman0 "$mtu"
}
function add_default_route() {
run_in_host_netns ip link add default_route type dummy
run_in_host_netns ip link set default_route mtu 9000
run_in_host_netns ip addr add 192.168.0.0/24 dev default_route
run_in_host_netns ip link set default_route up
run_in_host_netns ip route add default via 192.168.0.0
}
function add_bridge() {
run_in_host_netns ip link add podman0 type bridge
run_in_host_netns ip link set podman0 mtu 9001
run_in_host_netns ip link set up podman0
}
@test "bridge - mtu from default route" {
add_default_route
run_netavark --file ${TESTSDIR}/testfiles/bridge-managed.json setup $(get_container_netns_path)
check_mtu 9000
}
@test "bridge - mtu from existing bridge" {
add_bridge
run_netavark --file ${TESTSDIR}/testfiles/bridge-managed.json setup $(get_container_netns_path)
check_mtu 9001
}
@test "bridge - mtu from config with default route" {
add_default_route
run_netavark --file ${TESTSDIR}/testfiles/bridge-mtu.json setup $(get_container_netns_path)
check_mtu 9002
}
@test "bridge - mtu from config with existing bridge" {
add_bridge
run_netavark --file ${TESTSDIR}/testfiles/bridge-mtu.json setup $(get_container_netns_path)
check_iface_mtu container eth0 9002
check_iface_mtu host veth0 9002
# The existing bridge MTU should not be overriden.
check_iface_mtu host podman0 9001
}

View File

@@ -0,0 +1,35 @@
{
"container_id": "6ce776ea58b5",
"container_name": "testcontainer",
"networks": {
"podman1": {
"static_ips": [
"10.88.0.2"
],
"interface_name": "eth0"
}
},
"network_info": {
"podman1": {
"name": "podman0",
"id": "ed82e3a703682a9c09629d3cf45c1f1e7da5b32aeff3faf82837ef4d005356e6",
"driver": "bridge",
"network_interface": "podman0",
"subnets": [
{
"gateway": "10.88.0.1",
"subnet": "10.88.0.0/16"
}
],
"ipv6_enabled": true,
"internal": false,
"dns_enabled": false,
"ipam_options": {
"driver": "host-local"
},
"options": {
"mtu": "9002"
}
}
}
}