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