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