at 23.11-pre 17 kB view raw
1# Xen hypervisor (Dom0) support. 2 3{ config, lib, pkgs, ... }: 4 5with lib; 6 7let 8 cfg = config.virtualisation.xen; 9in 10 11{ 12 imports = [ 13 (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.") 14 (mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ]) 15 ]; 16 17 ###### interface 18 19 options = { 20 21 virtualisation.xen.enable = 22 mkOption { 23 default = false; 24 type = types.bool; 25 description = 26 mdDoc '' 27 Setting this option enables the Xen hypervisor, a 28 virtualisation technology that allows multiple virtual 29 machines, known as *domains*, to run 30 concurrently on the physical machine. NixOS runs as the 31 privileged *Domain 0*. This option 32 requires a reboot to take effect. 33 ''; 34 }; 35 36 virtualisation.xen.package = mkOption { 37 type = types.package; 38 defaultText = literalExpression "pkgs.xen"; 39 example = literalExpression "pkgs.xen-light"; 40 description = lib.mdDoc '' 41 The package used for Xen binary. 42 ''; 43 relatedPackages = [ "xen" "xen-light" ]; 44 }; 45 46 virtualisation.xen.package-qemu = mkOption { 47 type = types.package; 48 defaultText = literalExpression "pkgs.xen"; 49 example = literalExpression "pkgs.qemu_xen-light"; 50 description = lib.mdDoc '' 51 The package with qemu binaries for dom0 qemu and xendomains. 52 ''; 53 relatedPackages = [ "xen" 54 { name = "qemu_xen-light"; comment = "For use with pkgs.xen-light."; } 55 ]; 56 }; 57 58 virtualisation.xen.bootParams = 59 mkOption { 60 default = []; 61 type = types.listOf types.str; 62 description = lib.mdDoc 63 '' 64 Parameters passed to the Xen hypervisor at boot time. 65 ''; 66 }; 67 68 virtualisation.xen.domain0MemorySize = 69 mkOption { 70 default = 0; 71 example = 512; 72 type = types.addCheck types.int (n: n >= 0); 73 description = lib.mdDoc 74 '' 75 Amount of memory (in MiB) allocated to Domain 0 on boot. 76 If set to 0, all memory is assigned to Domain 0. 77 ''; 78 }; 79 80 virtualisation.xen.bridge = { 81 name = mkOption { 82 default = "xenbr0"; 83 type = types.str; 84 description = lib.mdDoc '' 85 Name of bridge the Xen domUs connect to. 86 ''; 87 }; 88 89 address = mkOption { 90 type = types.str; 91 default = "172.16.0.1"; 92 description = lib.mdDoc '' 93 IPv4 address of the bridge. 94 ''; 95 }; 96 97 prefixLength = mkOption { 98 type = types.addCheck types.int (n: n >= 0 && n <= 32); 99 default = 16; 100 description = lib.mdDoc '' 101 Subnet mask of the bridge interface, specified as the number of 102 bits in the prefix (`24`). 103 A DHCP server will provide IP addresses for the whole, remaining 104 subnet. 105 ''; 106 }; 107 108 forwardDns = mkOption { 109 type = types.bool; 110 default = false; 111 description = lib.mdDoc '' 112 If set to `true`, the DNS queries from the 113 hosts connected to the bridge will be forwarded to the DNS 114 servers specified in /etc/resolv.conf . 115 ''; 116 }; 117 118 }; 119 120 virtualisation.xen.stored = 121 mkOption { 122 type = types.path; 123 description = lib.mdDoc 124 '' 125 Xen Store daemon to use. Defaults to oxenstored of the xen package. 126 ''; 127 }; 128 129 virtualisation.xen.domains = { 130 extraConfig = mkOption { 131 type = types.lines; 132 default = ""; 133 description = lib.mdDoc 134 '' 135 Options defined here will override the defaults for xendomains. 136 The default options can be seen in the file included from 137 /etc/default/xendomains. 138 ''; 139 }; 140 }; 141 142 virtualisation.xen.trace = mkEnableOption (lib.mdDoc "Xen tracing"); 143 144 }; 145 146 147 ###### implementation 148 149 config = mkIf cfg.enable { 150 assertions = [ { 151 assertion = pkgs.stdenv.isx86_64; 152 message = "Xen currently not supported on ${pkgs.stdenv.hostPlatform.system}"; 153 } { 154 assertion = config.boot.loader.grub.enable && (config.boot.loader.grub.efiSupport == false); 155 message = "Xen currently does not support EFI boot"; 156 } ]; 157 158 virtualisation.xen.package = mkDefault pkgs.xen; 159 virtualisation.xen.package-qemu = mkDefault pkgs.xen; 160 virtualisation.xen.stored = mkDefault "${cfg.package}/bin/oxenstored"; 161 162 environment.systemPackages = [ cfg.package ]; 163 164 boot.kernelModules = 165 [ "xen-evtchn" "xen-gntdev" "xen-gntalloc" "xen-blkback" "xen-netback" 166 "xen-pciback" "evtchn" "gntdev" "netbk" "blkbk" "xen-scsibk" 167 "usbbk" "pciback" "xen-acpi-processor" "blktap2" "tun" "netxen_nic" 168 "xen_wdt" "xen-acpi-processor" "xen-privcmd" "xen-scsiback" 169 "xenfs" 170 ]; 171 172 # The xenfs module is needed in system.activationScripts.xen, but 173 # the modprobe command there fails silently. Include xenfs in the 174 # initrd as a work around. 175 boot.initrd.kernelModules = [ "xenfs" ]; 176 177 # The radeonfb kernel module causes the screen to go black as soon 178 # as it's loaded, so don't load it. 179 boot.blacklistedKernelModules = [ "radeonfb" ]; 180 181 # Increase the number of loopback devices from the default (8), 182 # which is way too small because every VM virtual disk requires a 183 # loopback device. 184 boot.extraModprobeConfig = 185 '' 186 options loop max_loop=64 187 ''; 188 189 virtualisation.xen.bootParams = [] ++ 190 optionals cfg.trace [ "loglvl=all" "guest_loglvl=all" ] ++ 191 optional (cfg.domain0MemorySize != 0) "dom0_mem=${toString cfg.domain0MemorySize}M"; 192 193 system.extraSystemBuilderCmds = 194 '' 195 ln -s ${cfg.package}/boot/xen.gz $out/xen.gz 196 echo "${toString cfg.bootParams}" > $out/xen-params 197 ''; 198 199 # Mount the /proc/xen pseudo-filesystem. 200 system.activationScripts.xen = 201 '' 202 if [ -d /proc/xen ]; then 203 ${pkgs.kmod}/bin/modprobe xenfs 2> /dev/null 204 ${pkgs.util-linux}/bin/mountpoint -q /proc/xen || \ 205 ${pkgs.util-linux}/bin/mount -t xenfs none /proc/xen 206 fi 207 ''; 208 209 # Domain 0 requires a pvops-enabled kernel. 210 system.requiredKernelConfig = with config.lib.kernelConfig; 211 [ (isYes "XEN") 212 (isYes "X86_IO_APIC") 213 (isYes "ACPI") 214 (isYes "XEN_DOM0") 215 (isYes "PCI_XEN") 216 (isYes "XEN_DEV_EVTCHN") 217 (isYes "XENFS") 218 (isYes "XEN_COMPAT_XENFS") 219 (isYes "XEN_SYS_HYPERVISOR") 220 (isYes "XEN_GNTDEV") 221 (isYes "XEN_BACKEND") 222 (isModule "XEN_NETDEV_BACKEND") 223 (isModule "XEN_BLKDEV_BACKEND") 224 (isModule "XEN_PCIDEV_BACKEND") 225 (isYes "XEN_BALLOON") 226 (isYes "XEN_SCRUB_PAGES") 227 ]; 228 229 230 environment.etc = 231 { 232 "xen/xl.conf".source = "${cfg.package}/etc/xen/xl.conf"; 233 "xen/scripts".source = "${cfg.package}/etc/xen/scripts"; 234 "default/xendomains".text = '' 235 source ${cfg.package}/etc/default/xendomains 236 237 ${cfg.domains.extraConfig} 238 ''; 239 } 240 // optionalAttrs (builtins.compareVersions cfg.package.version "4.10" >= 0) { 241 # in V 4.10 oxenstored requires /etc/xen/oxenstored.conf to start 242 "xen/oxenstored.conf".source = "${cfg.package}/etc/xen/oxenstored.conf"; 243 }; 244 245 # Xen provides udev rules. 246 services.udev.packages = [ cfg.package ]; 247 248 services.udev.path = [ pkgs.bridge-utils pkgs.iproute2 ]; 249 250 systemd.services.xen-store = { 251 description = "Xen Store Daemon"; 252 wantedBy = [ "multi-user.target" ]; 253 after = [ "network.target" "xen-store.socket" ]; 254 requires = [ "xen-store.socket" ]; 255 preStart = '' 256 export XENSTORED_ROOTDIR="/var/lib/xenstored" 257 rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null 258 259 mkdir -p /var/run 260 mkdir -p /var/log/xen # Running xl requires /var/log/xen and /var/lib/xen, 261 mkdir -p /var/lib/xen # so we create them here unconditionally. 262 grep -q control_d /proc/xen/capabilities 263 ''; 264 serviceConfig = if (builtins.compareVersions cfg.package.version "4.8" < 0) then 265 { ExecStart = '' 266 ${cfg.stored}${optionalString cfg.trace " -T /var/log/xen/xenstored-trace.log"} --no-fork 267 ''; 268 } else { 269 ExecStart = '' 270 ${cfg.package}/etc/xen/scripts/launch-xenstore 271 ''; 272 Type = "notify"; 273 RemainAfterExit = true; 274 NotifyAccess = "all"; 275 }; 276 postStart = '' 277 ${optionalString (builtins.compareVersions cfg.package.version "4.8" < 0) '' 278 time=0 279 timeout=30 280 # Wait for xenstored to actually come up, timing out after 30 seconds 281 while [ $time -lt $timeout ] && ! `${cfg.package}/bin/xenstore-read -s / >/dev/null 2>&1` ; do 282 time=$(($time+1)) 283 sleep 1 284 done 285 286 # Exit if we timed out 287 if ! [ $time -lt $timeout ] ; then 288 echo "Could not start Xenstore Daemon" 289 exit 1 290 fi 291 ''} 292 echo "executing xen-init-dom0" 293 ${cfg.package}/lib/xen/bin/xen-init-dom0 294 ''; 295 }; 296 297 systemd.sockets.xen-store = { 298 description = "XenStore Socket for userspace API"; 299 wantedBy = [ "sockets.target" ]; 300 socketConfig = { 301 ListenStream = [ "/var/run/xenstored/socket" "/var/run/xenstored/socket_ro" ]; 302 SocketMode = "0660"; 303 SocketUser = "root"; 304 SocketGroup = "root"; 305 }; 306 }; 307 308 309 systemd.services.xen-console = { 310 description = "Xen Console Daemon"; 311 wantedBy = [ "multi-user.target" ]; 312 after = [ "xen-store.service" ]; 313 requires = [ "xen-store.service" ]; 314 preStart = '' 315 mkdir -p /var/run/xen 316 ${optionalString cfg.trace "mkdir -p /var/log/xen"} 317 grep -q control_d /proc/xen/capabilities 318 ''; 319 serviceConfig = { 320 ExecStart = '' 321 ${cfg.package}/bin/xenconsoled\ 322 ${optionalString ((builtins.compareVersions cfg.package.version "4.8" >= 0)) " -i"}\ 323 ${optionalString cfg.trace " --log=all --log-dir=/var/log/xen"} 324 ''; 325 }; 326 }; 327 328 329 systemd.services.xen-qemu = { 330 description = "Xen Qemu Daemon"; 331 wantedBy = [ "multi-user.target" ]; 332 after = [ "xen-console.service" ]; 333 requires = [ "xen-store.service" ]; 334 serviceConfig.ExecStart = '' 335 ${cfg.package-qemu}/${cfg.package-qemu.qemu-system-i386} \ 336 -xen-attach -xen-domid 0 -name dom0 -M xenpv \ 337 -nographic -monitor /dev/null -serial /dev/null -parallel /dev/null 338 ''; 339 }; 340 341 342 systemd.services.xen-watchdog = { 343 description = "Xen Watchdog Daemon"; 344 wantedBy = [ "multi-user.target" ]; 345 after = [ "xen-qemu.service" "xen-domains.service" ]; 346 serviceConfig.ExecStart = "${cfg.package}/bin/xenwatchdogd 30 15"; 347 serviceConfig.Type = "forking"; 348 serviceConfig.RestartSec = "1"; 349 serviceConfig.Restart = "on-failure"; 350 }; 351 352 353 systemd.services.xen-bridge = { 354 description = "Xen bridge"; 355 wantedBy = [ "multi-user.target" ]; 356 before = [ "xen-domains.service" ]; 357 preStart = '' 358 mkdir -p /var/run/xen 359 touch /var/run/xen/dnsmasq.pid 360 touch /var/run/xen/dnsmasq.etherfile 361 touch /var/run/xen/dnsmasq.leasefile 362 363 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Usable\ range` 364 export XEN_BRIDGE_IP_RANGE_START="${"\${data[1]//[[:blank:]]/}"}" 365 export XEN_BRIDGE_IP_RANGE_END="${"\${data[2]//[[:blank:]]/}"}" 366 367 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address` 368 export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}" 369 370 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ mask` 371 export XEN_BRIDGE_NETMASK="${"\${data[1]//[[:blank:]]/}"}" 372 373 echo "${cfg.bridge.address} host gw dns" > /var/run/xen/dnsmasq.hostsfile 374 375 cat <<EOF > /var/run/xen/dnsmasq.conf 376 no-daemon 377 pid-file=/var/run/xen/dnsmasq.pid 378 interface=${cfg.bridge.name} 379 except-interface=lo 380 bind-interfaces 381 auth-zone=xen.local,$XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} 382 domain=xen.local 383 addn-hosts=/var/run/xen/dnsmasq.hostsfile 384 expand-hosts 385 strict-order 386 no-hosts 387 bogus-priv 388 ${optionalString (!cfg.bridge.forwardDns) '' 389 no-resolv 390 no-poll 391 auth-server=dns.xen.local,${cfg.bridge.name} 392 ''} 393 filterwin2k 394 clear-on-reload 395 domain-needed 396 dhcp-hostsfile=/var/run/xen/dnsmasq.etherfile 397 dhcp-authoritative 398 dhcp-range=$XEN_BRIDGE_IP_RANGE_START,$XEN_BRIDGE_IP_RANGE_END 399 dhcp-no-override 400 no-ping 401 dhcp-leasefile=/var/run/xen/dnsmasq.leasefile 402 EOF 403 404 # DHCP 405 ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT 406 ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT 407 # DNS 408 ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT 409 ${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT 410 411 ${pkgs.bridge-utils}/bin/brctl addbr ${cfg.bridge.name} 412 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} ${cfg.bridge.address} 413 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} netmask $XEN_BRIDGE_NETMASK 414 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} up 415 ''; 416 serviceConfig.ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq --conf-file=/var/run/xen/dnsmasq.conf"; 417 postStop = '' 418 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address` 419 export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}" 420 421 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} down 422 ${pkgs.bridge-utils}/bin/brctl delbr ${cfg.bridge.name} 423 424 # DNS 425 ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT 426 ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT 427 # DHCP 428 ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT 429 ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT 430 ''; 431 }; 432 433 434 systemd.services.xen-domains = { 435 description = "Xen domains - automatically starts, saves and restores Xen domains"; 436 wantedBy = [ "multi-user.target" ]; 437 after = [ "xen-bridge.service" "xen-qemu.service" ]; 438 requires = [ "xen-bridge.service" "xen-qemu.service" ]; 439 ## To prevent a race between dhcpcd and xend's bridge setup script 440 ## (which renames eth* to peth* and recreates eth* as a virtual 441 ## device), start dhcpcd after xend. 442 before = [ "dhcpd.service" ]; 443 restartIfChanged = false; 444 serviceConfig.RemainAfterExit = "yes"; 445 path = [ cfg.package cfg.package-qemu ]; 446 environment.XENDOM_CONFIG = "${cfg.package}/etc/sysconfig/xendomains"; 447 preStart = "mkdir -p /var/lock/subsys -m 755"; 448 serviceConfig.ExecStart = "${cfg.package}/etc/init.d/xendomains start"; 449 serviceConfig.ExecStop = "${cfg.package}/etc/init.d/xendomains stop"; 450 }; 451 452 }; 453}