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}