migration: preserve host_mtu for virtio-net devices

The virtual hardware is generated differently (at least for i440fx
machines) when host_mtu is set or not set on the netdev command line
[0]. When the MTU is the same value as the default 1500, Proxmox VE
did not add a host_mtu parameter. This is problematic for migration
where host_mtu is present on one end of the migration, but not on the
other [1]. Moreover, the effective setting in the guest (state) will
still be the host_mtu from the source side, even if a different value
is used for host_mtu on the target instance's commandline. This will
not lead to an error loading the migration stream in QEMU, but having
a larger host_mtu than the bridge MTU is still problematic for certain
network traffic like
> iperf3 -c 10.10.10.11 -u -l 2k
when host_mtu=9000 and bridge MTU=1500.

Pass the values from the source to the target during migration to be
able to preserve them.

[0]: https://bugzilla.redhat.com/show_bug.cgi?id=1449346
[1]: https://forum.proxmox.com/threads/live-vm-migration-fails.169537/post-796379

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Link: https://lore.proxmox.com/20250904124113.81772-4-f.ebner@proxmox.com
This commit is contained in:
Fiona Ebner 2025-09-04 14:40:47 +02:00 committed by Thomas Lamprecht
parent c4d2ee0610
commit 20c91f7f3a
3 changed files with 52 additions and 0 deletions

View file

@ -998,6 +998,10 @@ sub phase2_start_local_cluster {
push @$cmd, '--force-cpu', $start->{forcecpu};
}
if ($start->{'nets-host-mtu'}) {
push @$cmd, '--nets-host-mtu', $start->{'nets-host-mtu'};
}
if ($self->{storage_migration}) {
push @$cmd, '--targetstorage', ($self->{opts}->{targetstorage} // '1');
}
@ -1187,6 +1191,10 @@ sub phase2 {
},
};
if (my $nets_host_mtu = PVE::QemuServer::Network::get_nets_host_mtu($vmid, $conf)) {
$params->{start_params}->{'nets-host-mtu'} = $nets_host_mtu;
}
my ($tunnel_info, $spice_port);
my @online_local_volumes = $self->filter_local_volumes('online');

View file

@ -11,6 +11,8 @@ use PVE::Network::SDN::Zones;
use PVE::RESTEnvironment qw(log_warn);
use PVE::Tools qw($IPV6RE file_read_firstline);
use PVE::QemuServer::Monitor qw(mon_cmd);
my $nic_model_list = [
'e1000',
'e1000-82540em',
@ -330,4 +332,31 @@ sub tap_plug {
PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate);
}
sub get_nets_host_mtu {
my ($vmid, $conf) = @_;
my $nets_host_mtu = [];
for my $opt (sort keys $conf->%*) {
next if $opt !~ m/^net(\d+)$/;
my $net = parse_net($conf->{$opt});
next if $net->{model} ne 'virtio';
my $host_mtu = eval {
mon_cmd(
$vmid, 'qom-get',
path => "/machine/peripheral/$opt",
property => 'host_mtu',
);
};
if (my $err = $@) {
log_warn("$opt: could not query host_mtu - $err");
} elsif (defined($host_mtu)) {
push $nets_host_mtu->@*, "${opt}=${host_mtu}";
} else {
log_warn("$opt: got undefined value when querying host_mtu");
}
}
return join(',', $nets_host_mtu->@*);
}
1;

View file

@ -225,6 +225,21 @@ $qemu_server_machine_module->mock(
my $qemu_server_network_module = Test::MockModule->new("PVE::QemuServer::Network");
$qemu_server_network_module->mock(
del_nets_bridge_fdb => sub { return; },
mon_cmd => sub {
my ($vmid, $command, %params) = @_;
if ($command eq 'qom-get') {
if (
$params{path} =~ m|^/machine/peripheral/net\d+$|
&& $params{property} eq 'host_mtu'
) {
return 1500;
}
die "mon_cmd (mocked) - implement me: $command for path '$params{path}' property"
. " '$params{property}'";
}
die "mon_cmd (mocked) - implement me: $command";
},
);
my $qemu_server_qmphelpers_module = Test::MockModule->new("PVE::QemuServer::QMPHelpers");