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:
@@ -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}"
|
||||
)))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
68
test/640-bridge-mtu.bats
Normal 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
|
||||
}
|
||||
35
test/testfiles/bridge-mtu.json
Normal file
35
test/testfiles/bridge-mtu.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user