[pve-devel] [PATCH qemu-server v2 7/9] refactor: move rng related code into its own module

Fiona Ebner f.ebner at proxmox.com
Thu Jan 30 13:17:53 CET 2025


Am 29.01.25 um 16:53 schrieb Filip Schauer:
> Signed-off-by: Filip Schauer <f.schauer at proxmox.com>
> ---
>  PVE/QemuServer.pm       |  83 +++---------------------
>  PVE/QemuServer/Makefile |   1 +
>  PVE/QemuServer/RNG.pm   | 135 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 145 insertions(+), 74 deletions(-)
>  create mode 100644 PVE/QemuServer/RNG.pm
> 
> diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
> index 5cde94a1..93e65825 100644
> --- a/PVE/QemuServer.pm
> +++ b/PVE/QemuServer.pm
> @@ -61,6 +61,7 @@ use PVE::QemuServer::Memory qw(get_current_memory);
>  use PVE::QemuServer::Monitor qw(mon_cmd);
>  use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
>  use PVE::QemuServer::QMPHelpers qw(qemu_deviceadd qemu_devicedel qemu_objectadd qemu_objectdel);
> +use PVE::QemuServer::RNG;
>  use PVE::QemuServer::USB;
>  
>  my $have_sdn;
> @@ -249,39 +250,6 @@ my $spice_enhancements_fmt = {
>      },
>  };
>  
> -my $rng_fmt = {
> -    source => {
> -	type => 'string',
> -	enum => ['/dev/urandom', '/dev/random', '/dev/hwrng'],
> -	default_key => 1,
> -	description => "The file on the host to gather entropy from. In most cases '/dev/urandom'"
> -	    ." should be preferred over '/dev/random' to avoid entropy-starvation issues on the"
> -	    ." host. Using urandom does *not* decrease security in any meaningful way, as it's"
> -	    ." still seeded from real entropy, and the bytes provided will most likely be mixed"
> -	    ." with real entropy on the guest as well. '/dev/hwrng' can be used to pass through"
> -	    ." a hardware RNG from the host.",
> -    },
> -    max_bytes => {
> -	type => 'integer',
> -	description => "Maximum bytes of entropy allowed to get injected into the guest every"
> -	    ." 'period' milliseconds. Prefer a lower value when using '/dev/random' as source. Use"
> -	    ." `0` to disable limiting (potentially dangerous!).",
> -	optional => 1,
> -
> -	# default is 1 KiB/s, provides enough entropy to the guest to avoid boot-starvation issues
> -	# (e.g. systemd etc...) while allowing no chance of overwhelming the host, provided we're
> -	# reading from /dev/urandom
> -	default => 1024,
> -    },
> -    period => {
> -	type => 'integer',
> -	description => "Every 'period' milliseconds the entropy-injection quota is reset, allowing"
> -	    ." the guest to retrieve another 'max_bytes' of entropy.",
> -	optional => 1,
> -	default => 1000,
> -    },
> -};
> -
>  my $meta_info_fmt = {
>      'ctime' => {
>  	type => 'integer',

Does not apply, because this got moved recently ;)

> @@ -724,7 +692,7 @@ EODESCR
>      },
>      rng0 => {
>  	type => 'string',
> -	format => $rng_fmt,
> +	format => $PVE::QemuServer::RNG::rng_fmt,
>  	description => "Configure a VirtIO-based Random Number Generator.",
>  	optional => 1,
>      },

Could instead use the standard option you define in the RNG module.

> @@ -2078,16 +2046,6 @@ sub parse_vga {
>      return $res;
>  }
>  
> -sub parse_rng {
> -    my ($value) = @_;
> -
> -    return if !$value;
> -
> -    my $res = eval { parse_property_string($rng_fmt, $value) };
> -    warn $@ if $@;
> -    return $res;
> -}
> -
>  sub parse_meta_info {
>      my ($value) = @_;
> 

Does not apply, because this got moved recently ;)

> @@ -3940,20 +3898,14 @@ sub config_to_command {
>  	}
>      }
>  
> -    my $rng = $conf->{rng0} ? parse_rng($conf->{rng0}) : undef;
> +    my $rng = $conf->{rng0} ? PVE::QemuServer::RNG::parse_rng($conf->{rng0}) : undef;
>      if ($rng && $version_guard->(4, 1, 2)) {
> -	check_rng_source($rng->{source});
> -
> -	my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
> -	my $period = $rng->{period} // $rng_fmt->{period}->{default};
> -	my $limiter_str = "";
> -	if ($max_bytes) {
> -	    $limiter_str = ",max-bytes=$max_bytes,period=$period";
> -	}
> -
> -	my $rng_addr = print_pci_addr("rng0", $bridges, $arch, $machine_type);
> -	push @$devices, '-object', "rng-random,filename=$rng->{source},id=rng0";
> -	push @$devices, '-device', "virtio-rng-pci,rng=rng0$limiter_str$rng_addr";
> +	my $rng_object = PVE::QemuServer::RNG::print_rng_object('rng0', $rng);
> +	my $rng_device = PVE::QemuServer::RNG::print_rng_device(
> +	    'rng0', $rng, $bridges, $arch, $machine_type
> +	);

Style nit: that's not how multiline function calls are usually wrapped
in our Perl code base:
https://pve.proxmox.com/wiki/Perl_Style_Guide#Wrapping_Arguments

> +	push @$devices, '-object', $rng_object;
> +	push @$devices, '-device', $rng_device;
>      }
>  
>      my $spice_port;

Would be nice to have the moving to a dedicated module be separate from
adding the new helpers.

> @@ -4235,23 +4187,6 @@ sub config_to_command {
>      return wantarray ? ($cmd, $vollist, $spice_port, $pci_devices) : $cmd;
>  }
>  
> -sub check_rng_source {
> -    my ($source) = @_;
> -
> -    # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
> -    die "cannot create VirtIO RNG device: source file '$source' doesn't exist\n"
> -	if ! -e $source;
> -
> -    my $rng_current = '/sys/devices/virtual/misc/hw_random/rng_current';
> -    if ($source eq '/dev/hwrng' && file_read_firstline($rng_current) eq 'none') {
> -	# Needs to abort, otherwise QEMU crashes on first rng access. Note that rng_current cannot
> -	# be changed to 'none' manually, so once the VM is past this point, it's no longer an issue.
> -	die "Cannot start VM with passed-through RNG device: '/dev/hwrng' exists, but"
> -	    ." '$rng_current' is set to 'none'. Ensure that a compatible hardware-RNG is attached"
> -	    ." to the host.\n";
> -    }
> -}
> -
>  sub spice_port {
>      my ($vmid) = @_;
>  
> diff --git a/PVE/QemuServer/Makefile b/PVE/QemuServer/Makefile
> index 89d12091..72c287fc 100644
> --- a/PVE/QemuServer/Makefile
> +++ b/PVE/QemuServer/Makefile
> @@ -1,4 +1,5 @@
>  SOURCES=PCI.pm		\
> +	RNG.pm		\
>  	USB.pm		\
>  	Memory.pm	\
>  	ImportDisk.pm	\
> diff --git a/PVE/QemuServer/RNG.pm b/PVE/QemuServer/RNG.pm
> new file mode 100644
> index 00000000..f7a62f3b
> --- /dev/null
> +++ b/PVE/QemuServer/RNG.pm
> @@ -0,0 +1,135 @@
> +package PVE::QemuServer::RNG;
> +
> +use strict;
> +use warnings;
> +
> +use PVE::QemuServer::PCI qw(print_pci_addr);
> +use PVE::JSONSchema;
> +use PVE::Tools qw(file_read_firstline);
> +use base 'Exporter';

Style nit: The QemuServer::PCI module should be grouped below and I'd
prefer having a blank before it:
https://lore.proxmox.com/pve-devel/e24881cf-bfd2-4063-bde2-99f41031f0f0@proxmox.com/
Having the 'use base' be last is fine, but I'd also prefer a blank
before it.

> +
> +our @EXPORT_OK = qw(
> +parse_rng
> +check_rng_source
> +print_rng_device
> +print_rng_object
> +);
> +
> +our $rng_fmt = {
> +    source => {
> +	type => 'string',
> +	enum => ['/dev/urandom', '/dev/random', '/dev/hwrng'],
> +	default_key => 1,
> +	optional => 1,

Adding the optional should not be part of this patch.

> +	description => "The file on the host to gather entropy from. In most cases '/dev/urandom'"
> +	    ." should be preferred over '/dev/random' to avoid entropy-starvation issues on the"
> +	    ." host. Using urandom does *not* decrease security in any meaningful way, as it's"
> +	    ." still seeded from real entropy, and the bytes provided will most likely be mixed"
> +	    ." with real entropy on the guest as well. '/dev/hwrng' can be used to pass through"
> +	    ." a hardware RNG from the host.",

The part about /dev/random is outdated as you pointed out in v1 ;)

> +    },
> +    max_bytes => {
> +	type => 'integer',
> +	description => "Maximum bytes of entropy allowed to get injected into the guest every"
> +	    ." 'period' milliseconds. Prefer a lower value when using '/dev/random' as source. Use"
> +	    ." `0` to disable limiting (potentially dangerous!).",


The part about /dev/random is outdated as you pointed out in v1 ;)

> +	optional => 1,
> +
> +	# default is 1 KiB/s, provides enough entropy to the guest to avoid boot-starvation issues
> +	# (e.g. systemd etc...) while allowing no chance of overwhelming the host, provided we're
> +	# reading from /dev/urandom
> +	default => 1024,
> +    },
> +    period => {
> +	type => 'integer',
> +	description => "Every 'period' milliseconds the entropy-injection quota is reset, allowing"
> +	    ." the guest to retrieve another 'max_bytes' of entropy.",
> +	optional => 1,
> +	default => 1000,
> +    },
> +};
> +
> +PVE::JSONSchema::register_format('pve-qm-rng', $rng_fmt);

Since you register the format here, you don't need to make $rng_fmt
shared with 'our' or? (Still need to include the module in QemuServer.pm
to have registering come first).

> +
> +our $rngdesc = {
> +    type => 'string',
> +    format => $rng_fmt,
> +    optional => 1,
> +    description => "Configure a VirtIO-based Random Number Generator.",
> +};
> +PVE::JSONSchema::register_standard_option('pve-qm-rng', $rngdesc);
> +
> +sub parse_rng {
> +    my ($value) = @_;
> +
> +    return if !$value;
> +
> +    my $res = eval { PVE::JSONSchema::parse_property_string($rng_fmt, $value) };
> +    warn $@ if $@;
> +
> +    my $source = $res->{source};

Unused variable, should be part of a later patch.

> +
> +    return $res;
> +}
> +
> +sub check_rng_source {
> +    my ($source) = @_;
> +
> +    # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
> +    die "cannot create VirtIO RNG device: source file '$source' doesn't exist\n"
> +	if ! -e $source;
> +
> +    my $rng_current = '/sys/devices/virtual/misc/hw_random/rng_current';
> +    if ($source eq '/dev/hwrng' && file_read_firstline($rng_current) eq 'none') {
> +	# Needs to abort, otherwise QEMU crashes on first rng access. Note that rng_current cannot
> +	# be changed to 'none' manually, so once the VM is past this point, it's no longer an issue.
> +	die "Cannot start VM with passed-through RNG device: '/dev/hwrng' exists, but"
> +	    ." '$rng_current' is set to 'none'. Ensure that a compatible hardware-RNG is attached"
> +	    ." to the host.\n";
> +    }
> +}
> +
> +sub get_rng_source_path {
> +    my ($rng) = @_;
> +
> +    my $source = $rng->{source};
> +
> +    if (defined($source)) {
> +	return $source;
> +    }
> +
> +    return;
> +}
> +
> +sub print_rng_device {

I'd add _commandline to reduce potential for confusion

> +    my ($id, $rng, $bridges, $arch, $machine) = @_;
> +
> +    return if !$rng;

Isn't failing better? IMHO caller should first check that it has
something to print rather than check the result.

> +
> +    my $source_path = get_rng_source_path($rng);
> +    check_rng_source($source_path);

Since the source is not part of the result, maybe not check it here, but
only in print_rng_object()?

> +
> +    my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
> +    my $period = $rng->{period} // $rng_fmt->{period}->{default};
> +    my $limiter_str = "";
> +    if ($max_bytes) {
> +	$limiter_str = ",max-bytes=$max_bytes,period=$period";
> +    }
> +
> +    my $rng_addr = print_pci_addr($id, $bridges, $arch, $machine);
> +
> +    return "virtio-rng-pci,rng=$id$limiter_str$rng_addr";
> +}
> +
> +sub print_rng_object {

I'd add _commandline to reduce potential for confusion

> +    my ($id, $rng) = @_;
> +
> +    return if !$rng;

Isn't failing better? IMHO caller should first check that it has
something to print rather than check the result.

> +
> +    my $source_path = get_rng_source_path($rng);
> +    check_rng_source($source_path);
> +
> +    return "rng-random,filename=$source_path,id=$id";
> +}
> +
> +1;





More information about the pve-devel mailing list