# Xen hypervisor (Dom0) support. { config, lib, pkgs, ... }: with lib; let cfg = config.virtualisation.xen; in { ###### interface options = { virtualisation.xen.enable = mkOption { default = false; description = '' Setting this option enables the Xen hypervisor, a virtualisation technology that allows multiple virtual machines, known as domains, to run concurrently on the physical machine. NixOS runs as the privileged Domain 0. This option requires a reboot to take effect. ''; }; virtualisation.xen.package = mkOption { type = types.package; defaultText = "pkgs.xen"; example = literalExample "pkgs.xen-light"; description = '' The package used for Xen binary. ''; }; virtualisation.xen.qemu = mkOption { type = types.path; defaultText = "\${pkgs.xen}/lib/xen/bin/qemu-system-i386"; example = literalExample "''${pkgs.qemu_xen-light}/bin/qemu-system-i386"; description = '' The qemu binary to use for Dom-0 backend. ''; }; virtualisation.xen.qemu-package = mkOption { type = types.package; defaultText = "pkgs.xen"; example = literalExample "pkgs.qemu_xen-light"; description = '' The package with qemu binaries for xendomains. ''; }; virtualisation.xen.bootParams = mkOption { default = ""; description = '' Parameters passed to the Xen hypervisor at boot time. ''; }; virtualisation.xen.domain0MemorySize = mkOption { default = 0; example = 512; description = '' Amount of memory (in MiB) allocated to Domain 0 on boot. If set to 0, all memory is assigned to Domain 0. ''; }; virtualisation.xen.bridge = { name = mkOption { default = "xenbr0"; description = '' Name of bridge the Xen domUs connect to. ''; }; address = mkOption { type = types.str; default = "172.16.0.1"; description = '' IPv4 address of the bridge. ''; }; prefixLength = mkOption { type = types.addCheck types.int (n: n >= 0 && n <= 32); default = 16; description = '' Subnet mask of the bridge interface, specified as the number of bits in the prefix (24). A DHCP server will provide IP addresses for the whole, remaining subnet. ''; }; }; virtualisation.xen.stored = mkOption { type = types.path; description = '' Xen Store daemon to use. Defaults to oxenstored of the xen package. ''; }; virtualisation.xen.trace = mkOption { default = false; description = '' Enable Xen tracing. ''; }; }; ###### implementation config = mkIf cfg.enable { assertions = [ { assertion = pkgs.stdenv.isx86_64; message = "Xen currently not supported on ${pkgs.stdenv.system}"; } { assertion = config.boot.loader.grub.enable && (config.boot.loader.grub.efiSupport == false); message = "Xen currently does not support EFI boot"; } ]; virtualisation.xen.package = mkDefault pkgs.xen; virtualisation.xen.qemu = mkDefault "${pkgs.xen}/lib/xen/bin/qemu-system-i386"; virtualisation.xen.qemu-package = mkDefault pkgs.xen; virtualisation.xen.stored = mkDefault "${cfg.package}/bin/oxenstored"; environment.systemPackages = [ cfg.package ]; # Make sure Domain 0 gets the required configuration #boot.kernelPackages = pkgs.boot.kernelPackages.override { features={xen_dom0=true;}; }; boot.kernelModules = [ "xen-evtchn" "xen-gntdev" "xen-gntalloc" "xen-blkback" "xen-netback" "xen-pciback" "evtchn" "gntdev" "netbk" "blkbk" "xen-scsibk" "usbbk" "pciback" "xen-acpi-processor" "blktap2" "tun" "netxen_nic" "xen_wdt" "xen-acpi-processor" "xen-privcmd" "xen-scsiback" "xenfs" ]; # The xenfs module is needed in system.activationScripts.xen, but # the modprobe command there fails silently. Include xenfs in the # initrd as a work around. boot.initrd.kernelModules = [ "xenfs" ]; # The radeonfb kernel module causes the screen to go black as soon # as it's loaded, so don't load it. boot.blacklistedKernelModules = [ "radeonfb" ]; # Increase the number of loopback devices from the default (8), # which is way too small because every VM virtual disk requires a # loopback device. boot.extraModprobeConfig = '' options loop max_loop=64 ''; virtualisation.xen.bootParams = [] ++ optionals cfg.trace [ "loglvl=all" "guest_loglvl=all" ] ++ optional (cfg.domain0MemorySize != 0) "dom0_mem=${toString cfg.domain0MemorySize}M"; system.extraSystemBuilderCmds = '' ln -s ${cfg.package}/boot/xen.gz $out/xen.gz echo "${toString cfg.bootParams}" > $out/xen-params ''; # Mount the /proc/xen pseudo-filesystem. system.activationScripts.xen = '' if [ -d /proc/xen ]; then ${pkgs.kmod}/bin/modprobe xenfs 2> /dev/null ${pkgs.utillinux}/bin/mountpoint -q /proc/xen || \ ${pkgs.utillinux}/bin/mount -t xenfs none /proc/xen fi ''; # Domain 0 requires a pvops-enabled kernel. system.requiredKernelConfig = with config.lib.kernelConfig; [ (isYes "XEN") (isYes "X86_IO_APIC") (isYes "ACPI") (isYes "XEN_DOM0") (isYes "PCI_XEN") (isYes "XEN_DEV_EVTCHN") (isYes "XENFS") (isYes "XEN_COMPAT_XENFS") (isYes "XEN_SYS_HYPERVISOR") (isYes "XEN_GNTDEV") (isYes "XEN_BACKEND") (isModule "XEN_NETDEV_BACKEND") (isModule "XEN_BLKDEV_BACKEND") (isModule "XEN_PCIDEV_BACKEND") (isYes "XEN_BALLOON") (isYes "XEN_SCRUB_PAGES") ]; environment.etc = [ { source = "${cfg.package}/etc/xen/xl.conf"; target = "xen/xl.conf"; } { source = "${cfg.package}/etc/xen/scripts"; target = "xen/scripts"; } { source = "${cfg.package}/etc/default/xendomains"; target = "default/xendomains"; } ]; # Xen provides udev rules. services.udev.packages = [ cfg.package ]; services.udev.path = [ pkgs.bridge-utils pkgs.iproute ]; systemd.services.xen-store = { description = "Xen Store Daemon"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "xen-store.socket" ]; requires = [ "xen-store.socket" ]; preStart = '' export XENSTORED_ROOTDIR="/var/lib/xenstored" rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null mkdir -p /var/run mkdir -p /var/log/xen # Running xl requires /var/log/xen and /var/lib/xen, mkdir -p /var/lib/xen # so we create them here unconditionally. grep -q control_d /proc/xen/capabilities ''; serviceConfig.ExecStart = '' ${cfg.stored}${optionalString cfg.trace " -T /var/log/xen/xenstored-trace.log"} --no-fork ''; postStart = '' time=0 timeout=30 # Wait for xenstored to actually come up, timing out after 30 seconds while [ $time -lt $timeout ] && ! `${cfg.package}/bin/xenstore-read -s / >/dev/null 2>&1` ; do time=$(($time+1)) sleep 1 done # Exit if we timed out if ! [ $time -lt $timeout ] ; then echo "Could not start Xenstore Daemon" exit 1 fi ${cfg.package}/bin/xenstore-write "/local/domain/0/name" "Domain-0" ${cfg.package}/bin/xenstore-write "/local/domain/0/domid" 0 ''; }; systemd.sockets.xen-store = { description = "XenStore Socket for userspace API"; wantedBy = [ "sockets.target" ]; socketConfig = { ListenStream = [ "/var/run/xenstored/socket" "/var/run/xenstored/socket_ro" ]; SocketMode = "0660"; SocketUser = "root"; SocketGroup = "root"; }; }; systemd.services.xen-console = { description = "Xen Console Daemon"; wantedBy = [ "multi-user.target" ]; after = [ "xen-store.service" ]; preStart = '' mkdir -p /var/run/xen ${optionalString cfg.trace "mkdir -p /var/log/xen"} grep -q control_d /proc/xen/capabilities ''; serviceConfig = { ExecStart = '' ${cfg.package}/bin/xenconsoled${optionalString cfg.trace " --log=all --log-dir=/var/log/xen"} ''; }; }; systemd.services.xen-qemu = { description = "Xen Qemu Daemon"; wantedBy = [ "multi-user.target" ]; after = [ "xen-console.service" ]; serviceConfig.ExecStart = '' ${cfg.qemu} -xen-attach -xen-domid 0 -name dom0 -M xenpv \ -nographic -monitor /dev/null -serial /dev/null -parallel /dev/null ''; }; systemd.services.xen-watchdog = { description = "Xen Watchdog Daemon"; wantedBy = [ "multi-user.target" ]; after = [ "xen-qemu.service" ]; serviceConfig.ExecStart = "${cfg.package}/bin/xenwatchdogd 30 15"; serviceConfig.Type = "forking"; serviceConfig.RestartSec = "1"; serviceConfig.Restart = "on-failure"; }; systemd.services.xen-bridge = { description = "Xen bridge"; wantedBy = [ "multi-user.target" ]; before = [ "xen-domains.service" ]; preStart = '' mkdir -p /var/run/xen touch /var/run/xen/dnsmasq.pid touch /var/run/xen/dnsmasq.etherfile touch /var/run/xen/dnsmasq.leasefile IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Usable\ range` export XEN_BRIDGE_IP_RANGE_START="${"\${data[1]//[[:blank:]]/}"}" export XEN_BRIDGE_IP_RANGE_END="${"\${data[2]//[[:blank:]]/}"}" IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address` export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}" echo "${cfg.bridge.address} host gw dns" > /var/run/xen/dnsmasq.hostsfile cat < /var/run/xen/dnsmasq.conf no-daemon pid-file=/var/run/xen/dnsmasq.pid interface=${cfg.bridge.name} except-interface=lo bind-interfaces auth-server=dns.xen.local,${cfg.bridge.name} auth-zone=xen.local,$XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} domain=xen.local addn-hosts=/var/run/xen/dnsmasq.hostsfile expand-hosts strict-order no-hosts bogus-priv no-resolv no-poll filterwin2k clear-on-reload domain-needed dhcp-hostsfile=/var/run/xen/dnsmasq.etherfile dhcp-authoritative dhcp-range=$XEN_BRIDGE_IP_RANGE_START,$XEN_BRIDGE_IP_RANGE_END dhcp-no-override no-ping dhcp-leasefile=/var/run/xen/dnsmasq.leasefile EOF # DHCP ${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 ${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 # DNS ${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 ${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 ${pkgs.bridge-utils}/bin/brctl addbr ${cfg.bridge.name} ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} ${cfg.bridge.address} ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} up ''; serviceConfig.ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq --conf-file=/var/run/xen/dnsmasq.conf"; postStop = '' ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} down ${pkgs.bridge-utils}/bin/brctl delbr ${cfg.bridge.name} # DNS ${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 ${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 # DHCP ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p udp --sport 68 --dport 67 -j ACCEPT ${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p tcp --sport 68 --dport 67 -j ACCEPT ''; }; systemd.services.xen-domains = { description = "Xen domains - automatically starts, saves and restores Xen domains"; wantedBy = [ "multi-user.target" ]; after = [ "xen-bridge.service" "xen-qemu.service" ]; ## To prevent a race between dhcpcd and xend's bridge setup script ## (which renames eth* to peth* and recreates eth* as a virtual ## device), start dhcpcd after xend. before = [ "dhcpd.service" ]; restartIfChanged = false; serviceConfig.RemainAfterExit = "yes"; path = [ cfg.package cfg.qemu-package ]; environment.XENDOM_CONFIG = "${cfg.package}/etc/sysconfig/xendomains"; preStart = "mkdir -p /var/lock/subsys -m 755"; serviceConfig.ExecStart = "${cfg.package}/etc/init.d/xendomains start"; serviceConfig.ExecStop = "${cfg.package}/etc/init.d/xendomains stop"; }; }; }