lol

nixos/test-driver: allow assigning other vsock number ranges

I'm a little annoyed at myself that I only realized this _after_ #392030
got merged. But I realized that if something else is using AF_VSOCK or
you simply have another interactive test running (e.g. by another user
on a larger builder), starting up VMs in the driver fails with

qemu-system-x86_64: -device vhost-vsock-pci,guest-cid=3: vhost-vsock: unable to set guest cid: Address already in use

Multi-user setups are broken anyways because you usually don't have
permissions to remove the VM state from another user and thus starting
the driver fails with

PermissionError: [Errno 13] Permission denied: PosixPath('/tmp/vm-state-machine')

but this is something you can work around at least.

I was considering to generate random offsets, but that's not feasible
given we need to know the numbers at eval time to inject them into the
QEMU args. Also, while we could do this via the test-driver, we should
also probe if the vsock numbers are unused making the code even more
complex for a use-case I consider rather uncommon.

Hence the solution is to do

sshBackdoor.vsockOffset = 23542;

when encountering conflicts.

+68 -9
+23 -1
nixos/doc/manual/development/running-nixos-tests-interactively.section.md
··· 87 87 88 88 The socket numbers correspond to the node number of the test VM, but start 89 89 at three instead of one because that's the lowest possible 90 - vsock number. 90 + vsock number. The exact SSH commands are also printed out when starting 91 + `nixos-test-driver`. 91 92 92 93 On non-NixOS systems you'll probably need to enable 93 94 the SSH config from {manpage}`systemd-ssh-proxy(1)` yourself. 95 + 96 + If starting VM fails with an error like 97 + 98 + ``` 99 + qemu-system-x86_64: -device vhost-vsock-pci,guest-cid=3: vhost-vsock: unable to set guest cid: Address already in use 100 + ``` 101 + 102 + it means that the vsock numbers for the VMs are already in use. This can happen 103 + if another interactive test with SSH backdoor enabled is running on the machine. 104 + 105 + In that case, you need to assign another range of vsock numbers. You can pick another 106 + offset with 107 + 108 + ```nix 109 + { 110 + sshBackdoor = { 111 + enable = true; 112 + vsockOffset = 23542; 113 + }; 114 + } 115 + ``` 94 116 95 117 ## Port forwarding to NixOS test VMs {#sec-nixos-test-port-forwarding} 96 118
+3
nixos/doc/manual/redirects.json
··· 1826 1826 "test-opt-sshBackdoor.enable": [ 1827 1827 "index.html#test-opt-sshBackdoor.enable" 1828 1828 ], 1829 + "test-opt-sshBackdoor.vsockOffset": [ 1830 + "index.html#test-opt-sshBackdoor.vsockOffset" 1831 + ], 1829 1832 "test-opt-defaults": [ 1830 1833 "index.html#test-opt-defaults" 1831 1834 ],
+3 -3
nixos/lib/test-driver/src/test_driver/__init__.py
··· 112 112 arg_parser.add_argument( 113 113 "--dump-vsocks", 114 114 help="indicates that the interactive SSH backdoor is active and dumps information about it on start", 115 - action="store_true", 115 + type=int, 116 116 ) 117 117 118 118 args = arg_parser.parse_args() ··· 141 141 if args.interactive: 142 142 history_dir = os.getcwd() 143 143 history_path = os.path.join(history_dir, ".nixos-test-history") 144 - if args.dump_vsocks: 145 - driver.dump_machine_ssh() 144 + if offset := args.dump_vsocks: 145 + driver.dump_machine_ssh(offset) 146 146 ptpython.ipython.embed( 147 147 user_ns=driver.test_symbols(), 148 148 history_filename=history_path,
+2 -2
nixos/lib/test-driver/src/test_driver/driver.py
··· 178 178 ) 179 179 return {**general_symbols, **machine_symbols, **vlan_symbols} 180 180 181 - def dump_machine_ssh(self) -> None: 181 + def dump_machine_ssh(self, offset: int) -> None: 182 182 print("SSH backdoor enabled, the machines can be accessed like this:") 183 183 print( 184 184 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)." 185 185 ) 186 186 names = [machine.name for machine in self.machines] 187 187 longest_name = len(max(names, key=len)) 188 - for num, name in enumerate(names, start=3): 188 + for num, name in enumerate(names, start=offset + 1): 189 189 spaces = " " * (longest_name - len(name) + 2) 190 190 print( 191 191 f" {name}:{spaces}{Style.BRIGHT}ssh -o User=root vsock/{num}{Style.RESET_ALL}"
+19 -2
nixos/lib/testing/nodes.nix
··· 84 84 type = types.bool; 85 85 description = "Whether to turn on the VSOCK-based access to all VMs. This provides an unauthenticated access intended for debugging."; 86 86 }; 87 + vsockOffset = mkOption { 88 + default = 2; 89 + type = types.ints.between 2 4294967296; 90 + description = '' 91 + By default this assigns vsock numbers starting at 3 to the nodes. 92 + On e.g. large builders used by multiple people, this would cause conflicts 93 + between multiple users doing interactive debugging. 94 + 95 + This option allows to assign an offset to each vsock number to 96 + resolve this. 97 + 98 + This is a 32bit number. The lowest possible vsock number is `3` 99 + (i.e. with the lowest node number being `1`, this is 2+1). 100 + ''; 101 + }; 87 102 }; 88 103 89 104 node.type = mkOption { ··· 182 197 passthru.nodes = config.nodesCompat; 183 198 184 199 extraDriverArgs = mkIf config.sshBackdoor.enable [ 185 - "--dump-vsocks" 200 + "--dump-vsocks=${toString config.sshBackdoor.vsockOffset}" 186 201 ]; 187 202 188 203 defaults = mkMerge [ ··· 191 206 imports = [ ../../modules/misc/nixpkgs/read-only.nix ]; 192 207 }) 193 208 (mkIf config.sshBackdoor.enable { 194 - testing.sshBackdoor.enable = true; 209 + testing.sshBackdoor = { 210 + inherit (config.sshBackdoor) enable vsockOffset; 211 + }; 195 212 }) 196 213 ]; 197 214
+18 -1
nixos/modules/testing/test-instrumentation.nix
··· 89 89 90 90 sshBackdoor = { 91 91 enable = mkEnableOption "vsock-based ssh backdoor for the VM"; 92 + vsockOffset = mkOption { 93 + default = 2; 94 + type = types.ints.between 2 4294967296; 95 + description = '' 96 + By default this assigns vsock numbers starting at 3 to the nodes. 97 + On e.g. large builders used by multiple people, this would cause conflicts 98 + between multiple users doing interactive debugging. 99 + 100 + This option allows to assign an offset to each vsock number to 101 + resolve this. 102 + 103 + This is a 32bit number. The lowest possible vsock number is `3` 104 + (i.e. with the lowest node number being `1`, this is 2+1). 105 + ''; 106 + }; 92 107 }; 93 108 94 109 }; ··· 193 208 package = lib.mkDefault pkgs.qemu_test; 194 209 195 210 options = mkIf config.testing.sshBackdoor.enable [ 196 - "-device vhost-vsock-pci,guest-cid=${toString (config.virtualisation.test.nodeNumber + 2)}" 211 + "-device vhost-vsock-pci,guest-cid=${ 212 + toString (config.virtualisation.test.nodeNumber + config.testing.sshBackdoor.vsockOffset) 213 + }" 197 214 ]; 198 215 }; 199 216 };