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}