diff --git a/nixos/doc/manual/development/running-nixos-tests-interactively.section.md b/nixos/doc/manual/development/running-nixos-tests-interactively.section.md index 572173cb4fbf..121abd51c804 100644 --- a/nixos/doc/manual/development/running-nixos-tests-interactively.section.md +++ b/nixos/doc/manual/development/running-nixos-tests-interactively.section.md @@ -88,52 +88,34 @@ An SSH-based backdoor to log into machines can be enabled with } ``` -::: {.warning} -Make sure to only enable the backdoor for interactive tests -(i.e. by using `interactive.sshBackdoor.enable`)! This is the only -supported configuration. - -Running a test in a sandbox with this will fail because `/dev/vhost-vsock` isn't available -in the sandbox. -::: - This creates a [vsock socket](https://man7.org/linux/man-pages/man7/vsock.7.html) for each VM to log in with SSH. This configures root login with an empty password. -When the VMs get started interactively with the test-driver, it's possible to -connect to `machine` with +On the host-side a UNIX domain-socket is used with +[vhost-device-vsock](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-vsock/README.md). +That way, it's not necessary to assign system-wide unique vsock numbers. ``` -$ ssh vsock/3 -o User=root +$ ssh vsock-mux//tmp/path/to/host -o User=root ``` -The socket numbers correspond to the node number of the test VM, but start -at three instead of one because that's the lowest possible -vsock number. The exact SSH commands are also printed out when starting -`nixos-test-driver`. +The socket paths are printed when starting the test driver: + +``` +Note: this requires systemd-ssh-proxy(1) to be enabled (default on NixOS 25.05 and newer). + machine: ssh -o User=root vsock-mux//tmp/tmpg1rp9nti/machine_host.socket +``` On non-NixOS systems you'll probably need to enable the SSH config from {manpage}`systemd-ssh-proxy(1)` yourself. -If starting VM fails with an error like +During a test-run, it's possible to print the SSH commands again by running ``` -qemu-system-x86_64: -device vhost-vsock-pci,guest-cid=3: vhost-vsock: unable to set guest cid: Address already in use -``` - -it means that the vsock numbers for the VMs are already in use. This can happen -if another interactive test with SSH backdoor enabled is running on the machine. - -In that case, you need to assign another range of vsock numbers. You can pick another -offset with - -```nix -{ - sshBackdoor = { - enable = true; - vsockOffset = 23542; - }; -} +In [2]: dump_machine_ssh() +SSH backdoor enabled, the machines can be accessed like this: +Note: this requires systemd-ssh-proxy(1) to be enabled (default on NixOS 25.05 and newer). + machine: ssh -o User=root vsock-mux//tmp/tmpg1rp9nti/machine_host.socket ``` ## Port forwarding to NixOS test VMs {#sec-nixos-test-port-forwarding} diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md index 7e7414bd61a9..8296b4904d3a 100644 --- a/nixos/doc/manual/development/writing-nixos-tests.section.md +++ b/nixos/doc/manual/development/writing-nixos-tests.section.md @@ -512,19 +512,11 @@ Once you are in the sandbox shell, you can access the VMs (for example, `machine with SSH over vsock: ``` -bash# ssh -F ./ssh_config vsock/3 +bash# ssh -F ./ssh_config -o User=root vsock-mux//tmp/.../machine_host.socket ``` -For the AF_VSOCK feature to work, `/dev/vhost-vsock` is needed in the sandbox -which can be done with e.g. - -``` -nix-build -A nixosTests.foo --option sandbox-paths /dev/vhost-vsock -``` - -As described in [](#sec-nixos-test-ssh-access), the numbers for vsock start at -`3` instead of `1`. So the first VM in the network (sorted alphabetically) can -be accessed with `vsock/3`. +The socket paths are printed at the beginning of the test. See +[](#sec-nixos-test-ssh-access) for more context. ### SSH access to test containers {#sec-test-container-ssh-access} diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json index 4dbb255682ed..88111e5953f6 100644 --- a/nixos/doc/manual/redirects.json +++ b/nixos/doc/manual/redirects.json @@ -2174,9 +2174,6 @@ "test-opt-sshBackdoor.enable": [ "index.html#test-opt-sshBackdoor.enable" ], - "test-opt-sshBackdoor.vsockOffset": [ - "index.html#test-opt-sshBackdoor.vsockOffset" - ], "test-opt-enableDebugHook": [ "index.html#test-opt-enableDebugHook" ], diff --git a/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix index 88fd70ee7f47..69967ccf325e 100644 --- a/nixos/lib/test-driver/default.nix +++ b/nixos/lib/test-driver/default.nix @@ -14,6 +14,7 @@ ty, netpbm, + vhost-device-vsock, nixosTests, qemu_pkg ? qemu_test, qemu_test, @@ -56,6 +57,7 @@ buildPythonApplication { socat util-linux vde2 + vhost-device-vsock ] ++ lib.optionals enableNspawn [ systemd diff --git a/nixos/lib/test-driver/src/test_driver/__init__.py b/nixos/lib/test-driver/src/test_driver/__init__.py index 847ab0582448..2000bcdbcc2c 100644 --- a/nixos/lib/test-driver/src/test_driver/__init__.py +++ b/nixos/lib/test-driver/src/test_driver/__init__.py @@ -148,9 +148,9 @@ def main() -> None: type=Path, ) arg_parser.add_argument( - "--dump-vsocks", + "--enable-ssh-backdoor", help="indicates that the interactive SSH backdoor is active and dumps information about it on start", - type=int, + action="store_true", ) log_level_map = {level.name.lower(): level for level in LogLevel} arg_parser.add_argument( @@ -210,9 +210,10 @@ def main() -> None: keep_machine_state=args.keep_machine_state, global_timeout=args.global_timeout, debug=debugger, + enable_ssh_backdoor=args.enable_ssh_backdoor, ) as driver: - if offset := args.dump_vsocks: - driver.dump_machine_ssh(offset) + if args.enable_ssh_backdoor: + driver.dump_machine_ssh() if args.interactive: history_dir = os.getcwd() history_path = os.path.join(history_dir, ".nixos-test-history") diff --git a/nixos/lib/test-driver/src/test_driver/driver.py b/nixos/lib/test-driver/src/test_driver/driver.py index 714a11905306..50b6fdb2170d 100644 --- a/nixos/lib/test-driver/src/test_driver/driver.py +++ b/nixos/lib/test-driver/src/test_driver/driver.py @@ -8,6 +8,7 @@ import threading import traceback from collections.abc import Callable, Iterator from contextlib import AbstractContextManager, contextmanager +from dataclasses import dataclass from pathlib import Path from typing import Any from unittest import TestCase @@ -63,19 +64,64 @@ def pythonize_name(name: str) -> str: return re.sub(r"^[^A-Za-z_]|[^A-Za-z0-9_]", "_", name) +@dataclass +class VsockPair: + guest: Path + host: Path + cid: int + + +class VHostDeviceVsock: + def __init__(self, tmp_dir: Path, machines: list[str]): + self.temp_dir_handle = tempfile.TemporaryDirectory(dir=tmp_dir) + self.temp_dir = Path(self.temp_dir_handle.name) + self.sockets = { + machine: VsockPair( + self.temp_dir / f"{machine}_guest.socket", + self.temp_dir / f"{machine}_host.socket", + cid, + ) + for cid, machine in enumerate(machines, start=3) + } + + self.vhost_proc = subprocess.Popen( + [ + "vhost-device-vsock", + *( + arg + for vsock_pair in self.sockets.values() + for arg in ( + "--vm", + f"guest-cid={vsock_pair.cid},socket={vsock_pair.guest},uds-path={vsock_pair.host}", + ) + ), + ] + ) + + def __del__(self) -> None: + self.vhost_proc.kill() + self.temp_dir_handle.cleanup() + + class Driver: """A handle to the driver that sets up the environment and runs the tests""" tests: str - vlans: list[VLan] - machines_qemu: list[QemuMachine] - machines_nspawn: list[NspawnMachine] + vlans: list[VLan] = [] + machines_qemu: list[QemuMachine] = [] + machines_nspawn: list[NspawnMachine] = [] polling_conditions: list[PollingCondition] global_timeout: int race_timer: threading.Timer + vm_start_scripts: dict[str, str] + container_start_scripts: dict[str, str] + vlan_ids: list[int] + keep_machine_state: bool logger: AbstractLogger debug: DebugAbstract + vhost_vsock: VHostDeviceVsock | None = None + enable_ssh_backdoor: bool def __init__( self, @@ -90,36 +136,62 @@ class Driver: keep_machine_state: bool = False, global_timeout: int = 24 * 60 * 60 * 7, debug: DebugAbstract = DebugNop(), + enable_ssh_backdoor: bool = False, ): self.tests = tests self.out_dir = out_dir self.global_timeout = global_timeout - self.race_timer = threading.Timer(global_timeout, self.terminate_test) self.logger = logger self.debug = debug + self.vlan_ids = list(set(vlans)) + self.polling_conditions = [] + self.keep_machine_state = keep_machine_state + self.global_timeout = global_timeout + self.vm_start_scripts = dict(zip(vm_names, vm_start_scripts)) + self.container_start_scripts = dict( + zip(container_names, container_start_scripts) + ) + self.enable_ssh_backdoor = enable_ssh_backdoor + def __enter__(self) -> "Driver": + self.race_timer = threading.Timer(self.global_timeout, self.terminate_test) tmp_dir = get_tmp_dir() with self.logger.nested("start all VLans"): - vlans = list(set(vlans)) - self.vlans = [VLan(nr, tmp_dir, self.logger) for nr in vlans] + self.vlans = [VLan(nr, tmp_dir, self.logger) for nr in self.vlan_ids] self.polling_conditions = [] + if self.enable_ssh_backdoor and self.vm_start_scripts: + with self.logger.nested("start vhost-device-vsock"): + self.vhost_vsock = VHostDeviceVsock( + tmp_dir, list(self.vm_start_scripts.keys()) + ) + self.machines_qemu = [ QemuMachine( name=name, start_command=vm_start_script, - keep_machine_state=keep_machine_state, + keep_machine_state=self.keep_machine_state, tmp_dir=tmp_dir, callbacks=[self.check_polling_conditions], out_dir=self.out_dir, logger=self.logger, + vsock_host=( + self.vhost_vsock.sockets[name].host + if self.vhost_vsock is not None + else None + ), + vsock_guest=( + self.vhost_vsock.sockets[name].guest + if self.vhost_vsock is not None + else None + ), ) - for name, vm_start_script in zip(vm_names, vm_start_scripts) + for name, vm_start_script in self.vm_start_scripts.items() ] - if len(container_start_scripts) > 0: + if len(self.container_start_scripts) > 0: self._init_nspawn_environment() self.machines_nspawn = [ @@ -128,16 +200,15 @@ class Driver: start_command=container_start_script, tmp_dir=tmp_dir, logger=self.logger, - keep_machine_state=keep_machine_state, + keep_machine_state=self.keep_machine_state, callbacks=[self.check_polling_conditions], out_dir=self.out_dir, ) - for name, container_start_script in zip( - container_names, - container_start_scripts, - ) + for name, container_start_script in self.container_start_scripts.items() ] + return self + def _init_nspawn_environment(self) -> None: assert os.geteuid() == 0, ( f"systemd-nspawn requires root to work. You are {os.geteuid()}" @@ -193,9 +264,6 @@ class Driver: machines.sort(key=lambda machine: machine.name) return machines - def __enter__(self) -> "Driver": - return self - def __exit__(self, *_: Any) -> None: with self.logger.nested("cleanup"): self.race_timer.cancel() @@ -211,6 +279,14 @@ class Driver: except Exception as e: self.logger.error(f"Error during cleanup of vlan{vlan.nr}: {e}") + if self.enable_ssh_backdoor: + try: + del self.vhost_vsock + except Exception as e: + self.logger.error( + f"Error during cleanup of vhost-device-vsock process: {e}" + ) + def subtest(self, name: str) -> Iterator[None]: """Group logs under a given test name""" with self.logger.subtest(name): @@ -248,6 +324,7 @@ class Driver: NspawnMachine=NspawnMachine, # for typing t=AssertionTester(), debug=self.debug, + dump_machine_ssh=self.dump_machine_ssh, ) machine_symbols = {pythonize_name(m.name): m for m in self.machines} # If there's exactly one machine, make it available under the name @@ -267,18 +344,28 @@ class Driver: ) return {**general_symbols, **machine_symbols, **vlan_symbols} - def dump_machine_ssh(self, offset: int) -> None: - print("SSH backdoor enabled, the machines can be accessed like this:") - print( - f"{Style.BRIGHT}Note:{Style.RESET_ALL} vsocks require {Style.BRIGHT}systemd-ssh-proxy(1){Style.RESET_ALL} to be enabled (default on NixOS 25.05 and newer)." - ) - longest_name = len(max((machine.name for machine in self.machines), key=len)) - for index, machine in enumerate(self.machines, start=offset + 1): - name = machine.name - spaces = " " * (longest_name - len(name) + 2) + def dump_machine_ssh(self) -> None: + if not self.enable_ssh_backdoor: + return + + assert self.vhost_vsock is not None + + if self.machines: + print("SSH backdoor enabled, the machines can be accessed like this:") print( - f" {name}:{spaces}{Style.BRIGHT}{machine.ssh_backdoor_command(index)}{Style.RESET_ALL}" + f"{Style.BRIGHT}Note:{Style.RESET_ALL} this requires {Style.BRIGHT}systemd-ssh-proxy(1){Style.RESET_ALL} to be enabled (default on NixOS 25.05 and newer)." ) + longest_name = len( + max((machine.name for machine in self.machines), key=len) + ) + for index, machine in enumerate(self.machines): + name = machine.name + spaces = " " * (longest_name - len(name) + 2) + print( + f" {name}:{spaces}{Style.BRIGHT}{machine.ssh_backdoor_command()}{Style.RESET_ALL}" + ) + else: + print("SSH backdoor enabled, but no machines defined") def test_script(self) -> None: """Run the test script""" @@ -396,6 +483,10 @@ class Driver: """ tmp_dir = get_tmp_dir() + if self.enable_ssh_backdoor: + self.logger.warning( + f"create_machine({name}): not enabling SSH backdoor, this is not supported for VMs created with create_machine!" + ) return QemuMachine( tmp_dir=tmp_dir, out_dir=self.out_dir, diff --git a/nixos/lib/test-driver/src/test_driver/machine/__init__.py b/nixos/lib/test-driver/src/test_driver/machine/__init__.py index 62c10d425f86..909a906a9a0d 100644 --- a/nixos/lib/test-driver/src/test_driver/machine/__init__.py +++ b/nixos/lib/test-driver/src/test_driver/machine/__init__.py @@ -149,6 +149,7 @@ class QemuStartCommand: qmp_socket_path: Path, shell_socket_path: Path, allow_reboot: bool = False, + vsock_guest: Path | None = None, ) -> str: display_opts = "" @@ -172,6 +173,12 @@ class QemuStartCommand: if not allow_reboot: qemu_opts += " -no-reboot" + if vsock_guest is not None: + qemu_opts += ( + f" -chardev socket,id=vsock_ssh,path={vsock_guest} " + f"-device vhost-user-vsock-pci,chardev=vsock_ssh " + ) + return ( f"{self._cmd}" f" -qmp unix:{qmp_socket_path},server=on,wait=off" @@ -205,10 +212,15 @@ class QemuStartCommand: qmp_socket_path: Path, shell_socket_path: Path, allow_reboot: bool, + vsock_guest: Path | None = None, ) -> subprocess.Popen: return subprocess.Popen( self.cmd( - monitor_socket_path, qmp_socket_path, shell_socket_path, allow_reboot + monitor_socket_path, + qmp_socket_path, + shell_socket_path, + allow_reboot, + vsock_guest, ), stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -727,6 +739,9 @@ class QemuMachine(BaseMachine): shell: socket.socket | None serial_thread: threading.Thread | None + vsock_guest: Path | None + vsock_host: Path | None + booted: bool connected: bool # Store last serial console lines for use @@ -744,6 +759,8 @@ class QemuMachine(BaseMachine): name: str | None = None, keep_machine_state: bool = False, callbacks: list[Callable] | None = None, + vsock_guest: Path | None = None, + vsock_host: Path | None = None, ) -> None: self.start_command = QemuStartCommand(start_command) super().__init__( @@ -756,6 +773,8 @@ class QemuMachine(BaseMachine): ) self.full_console_log = [] + self.vsock_guest = vsock_guest + self.vsock_host = vsock_host # set up directories self.monitor_path = self.state_dir / "monitor" @@ -772,8 +791,9 @@ class QemuMachine(BaseMachine): self.booted = False self.connected = False - def ssh_backdoor_command(self, index: int) -> str: - return f"ssh -o User=root vsock/{index}" + def ssh_backdoor_command(self) -> str: + assert self.vsock_host is not None + return f"ssh -o User=root vsock-mux/{self.vsock_host}" def is_up(self) -> bool: return self.booted and self.connected @@ -1227,6 +1247,7 @@ class QemuMachine(BaseMachine): self.qmp_path, self.shell_path, allow_reboot, + self.vsock_guest, ) def accept_or_fail(sock: socket.socket, name: str) -> socket.socket: @@ -1458,7 +1479,7 @@ class NspawnMachine(BaseMachine): self.machine_sock_path = self.tmp_dir / f"{self.name}-nspawn.sock" - def ssh_backdoor_command(self, index: int) -> str: + def ssh_backdoor_command(self) -> str: # documented in systemd-ssh-generator(8) and https://systemd.io/CONTAINER_INTERFACE/ socket_path = f"/run/systemd/nspawn/unix-export/{self.name}/ssh" proxy_cmd = f"socat - UNIX-CLIENT:{socket_path}" diff --git a/nixos/lib/test-script-prepend.py b/nixos/lib/test-script-prepend.py index e836e779300c..b1ca8e7b1678 100644 --- a/nixos/lib/test-script-prepend.py +++ b/nixos/lib/test-script-prepend.py @@ -58,3 +58,4 @@ serial_stdout_on: Callable[[], None] polling_condition: PollingConditionProtocol debug: DebugAbstract t: TestCase +dump_machine_ssh: Callable[[], None] diff --git a/nixos/lib/testing/driver.nix b/nixos/lib/testing/driver.nix index e9808907b2c5..d8d93a224b3d 100644 --- a/nixos/lib/testing/driver.nix +++ b/nixos/lib/testing/driver.nix @@ -7,6 +7,8 @@ let inherit (lib) mkOption types literalMD; + inherit (config) sshBackdoor; + # Reifies and correctly wraps the python test driver for # the respective qemu version and with or without ocr support testDriver = config.pythonTestDriverPackage.override { @@ -254,5 +256,27 @@ in # make available on the test runner passthru.driver = config.driver; + + nodeDefaults = + { config, ... }: + { + # This is needed for the SSH backdoor to function. + # Set this to `true` by default to not change essential QEMU flags + # depending on whether debugging is enabled. + # + # If needed, this can still be turned off. + virtualisation.qemu.enableSharedMemory = lib.mkDefault true; + + assertions = [ + { + assertion = sshBackdoor.enable -> config.virtualisation.qemu.enableSharedMemory; + message = '' + When turning on the SSH backdoor of the NixOS test-framework, + `virtualisation.qemu.enableSharedMemory` MUST be `true` + (affected: ${config.networking.hostName}). + ''; + } + ]; + }; }; } diff --git a/nixos/lib/testing/nodes.nix b/nixos/lib/testing/nodes.nix index e1a148297e32..474070c60e3e 100644 --- a/nixos/lib/testing/nodes.nix +++ b/nixos/lib/testing/nodes.nix @@ -13,6 +13,7 @@ let mapAttrs mkIf mkMerge + mkRemovedOptionModule mkOption optionalAttrs types @@ -130,6 +131,12 @@ let in { + imports = [ + (mkRemovedOptionModule [ "sshBackdoor" "vsockOffset" ] '' + The option `sshBackdoor.vsockOffset` has been removed from the testing framework. + The functionality provided by it is not needed anymore. + '') + ]; options = { sshBackdoor = { @@ -139,22 +146,6 @@ in type = types.bool; description = "Whether to turn on the VSOCK-based access to all VMs. This provides an unauthenticated access intended for debugging."; }; - vsockOffset = mkOption { - default = 2; - type = types.ints.between 2 4294967296; - description = '' - This field is only relevant when multiple users run the (interactive) - driver outside the sandbox and with the SSH backdoor activated. - The typical symptom for this being a problem are error messages like this: - `vhost-vsock: unable to set guest cid: Address already in use` - - This option allows to assign an offset to each vsock number to - resolve this. - - This is a 32bit number. The lowest possible vsock number is `3` - (i.e. with the lowest node number being `1`, this is 2+1). - ''; - }; }; node.type = mkOption { @@ -320,7 +311,7 @@ in passthru.containers = config.containers; extraDriverArgs = mkIf config.sshBackdoor.enable [ - "--dump-vsocks=${toString config.sshBackdoor.vsockOffset}" + "--enable-ssh-backdoor" ]; defaults = mkMerge [ @@ -343,20 +334,6 @@ in }) ]; - nodeDefaults = mkIf config.sshBackdoor.enable ( - let - inherit (config.sshBackdoor) vsockOffset; - in - { config, ... }: - { - virtualisation.qemu.options = [ - "-device vhost-vsock-pci,guest-cid=${ - toString (config.virtualisation.test.nodeNumber + vsockOffset) - }" - ]; - } - ); - # Docs: nixos/doc/manual/development/writing-nixos-tests.section.md /** See https://nixos.org/manual/nixos/unstable#sec-override-nixos-test diff --git a/nixos/lib/testing/run.nix b/nixos/lib/testing/run.nix index 646832f71e62..fcdd81412123 100644 --- a/nixos/lib/testing/run.nix +++ b/nixos/lib/testing/run.nix @@ -80,6 +80,10 @@ in }; }; + imports = [ + ../../modules/misc/assertions.nix + ]; + config = { rawTestDerivation = hostPkgs.stdenv.mkDerivation config.rawTestDerivationArg; rawTestDerivationArg = @@ -131,7 +135,7 @@ in }; test = lib.lazyDerivation { # lazyDerivation improves performance when only passthru items and/or meta are used. - derivation = config.rawTestDerivation; + derivation = lib.asserts.checkAssertWarn config.assertions config.warnings config.rawTestDerivation; inherit (config) passthru meta; }; diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index efc7921da00b..2a528b036404 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -750,6 +750,8 @@ in }; virtualisation.qemu = { + enableSharedMemory = mkEnableOption "shared memory"; + package = mkOption { type = types.package; default = @@ -1383,6 +1385,10 @@ in "-device usb-kbd" "-device usb-tablet" ]) + (mkIf cfg.qemu.enableSharedMemory [ + "-object memory-backend-memfd,id=mem0,size=${toString config.virtualisation.memorySize}M,share=on" + "-machine memory-backend=mem0" + ]) ( let alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 929e8362e8f3..94d1f424951c 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -149,6 +149,7 @@ in lib-extend = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./nixos-test-driver/lib-extend.nix { }; node-name = runTest ./nixos-test-driver/node-name.nix; busybox = runTest ./nixos-test-driver/busybox.nix; + ssh-backdoor = runTestOn [ "x86_64-linux" ] ./nixos-test-driver/ssh-backdoor.nix; console-log = runTest ./nixos-test-driver/console-log.nix; containers = runTest ./nixos-test-driver/containers.nix; driver-timeout = diff --git a/nixos/tests/nixos-test-driver/ssh-backdoor.nix b/nixos/tests/nixos-test-driver/ssh-backdoor.nix new file mode 100644 index 000000000000..5c84b24474e9 --- /dev/null +++ b/nixos/tests/nixos-test-driver/ssh-backdoor.nix @@ -0,0 +1,36 @@ +{ pkgs, lib, ... }: +{ + name = "ssh-backdoor"; + sshBackdoor.enable = true; + + nodes.machine = { }; + + testScript = '' + import subprocess + + start_all() + machine.wait_for_unit("multi-user.target") + + assert driver.vhost_vsock is not None + host_socket = driver.vhost_vsock.sockets["machine"].host + + with subtest("ssh from the host via systemd-ssh-proxy"): + subprocess.run( + [ + "${lib.getExe pkgs.openssh}", + "-vvv", + f"vsock-mux/{host_socket}", + "-o", + "User=root", + "-F", + # The backdoor feature of the driver copies this into NIX_BUILD_TOP. + # We can't do this here since `enableDebugHook=true;` would halt + # instead of terminating the test execution if it fails. + "${pkgs.systemd}/lib/systemd/ssh_config.d/20-systemd-ssh-proxy.conf", + "--", + "true" + ], + check=True + ) + ''; +} diff --git a/pkgs/by-name/vh/vhost-device-vsock/package.nix b/pkgs/by-name/vh/vhost-device-vsock/package.nix new file mode 100644 index 000000000000..ef717a777d26 --- /dev/null +++ b/pkgs/by-name/vh/vhost-device-vsock/package.nix @@ -0,0 +1,45 @@ +{ + lib, + fetchFromGitHub, + rustPlatform, + installShellFiles, +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "vhost-device-vsock"; + version = "0.3.0"; + + src = fetchFromGitHub { + owner = "rust-vmm"; + repo = "vhost-device"; + tag = "vhost-device-vsock-v${finalAttrs.version}"; + hash = "sha256-g+u6WBJtizIgQwC0kkWdAcTiYCM1zMI4YBLVRU4MOrs="; + }; + + __structuredAttrs = true; + + outputs = [ + "out" + "man" + ]; + + cargoBuildFlags = "-p vhost-device-vsock"; + cargoTestFlags = "-p vhost-device-vsock"; + cargoHash = "sha256-mtORRCY/TNeIEgRCQ1ZbjpsykteRm2FHRveKaQxD/Pw="; + + nativeBuildInputs = [ installShellFiles ]; + + postInstall = '' + installManPage vhost-device-vsock/*.1 + ''; + + meta = { + homepage = "https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-vsock/README.md"; + maintainers = with lib.maintainers; [ ma27 ]; + license = with lib.licenses; [ + asl20 + bsd3 + ]; + platforms = lib.platforms.linux; + }; +})