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 relatedPackages = [ "xen" "xen-light" ];
39 };
40
41 virtualisation.xen.package-qemu = mkOption {
42 type = types.package;
43 defaultText = "pkgs.xen";
44 example = literalExample "pkgs.qemu_xen-light";
45 description = ''
46 The package with qemu binaries for dom0 qemu and xendomains.
47 '';
48 relatedPackages = [ "xen"
49 { name = "qemu_xen-light"; comment = "For use with pkgs.xen-light."; }
50 ];
51 };
52
53 virtualisation.xen.bootParams =
54 mkOption {
55 default = "";
56 description =
57 ''
58 Parameters passed to the Xen hypervisor at boot time.
59 '';
60 };
61
62 virtualisation.xen.domain0MemorySize =
63 mkOption {
64 default = 0;
65 example = 512;
66 description =
67 ''
68 Amount of memory (in MiB) allocated to Domain 0 on boot.
69 If set to 0, all memory is assigned to Domain 0.
70 '';
71 };
72
73 virtualisation.xen.bridge = {
74 name = mkOption {
75 default = "xenbr0";
76 description = ''
77 Name of bridge the Xen domUs connect to.
78 '';
79 };
80
81 address = mkOption {
82 type = types.str;
83 default = "172.16.0.1";
84 description = ''
85 IPv4 address of the bridge.
86 '';
87 };
88
89 prefixLength = mkOption {
90 type = types.addCheck types.int (n: n >= 0 && n <= 32);
91 default = 16;
92 description = ''
93 Subnet mask of the bridge interface, specified as the number of
94 bits in the prefix (<literal>24</literal>).
95 A DHCP server will provide IP addresses for the whole, remaining
96 subnet.
97 '';
98 };
99
100 forwardDns = mkOption {
101 default = false;
102 description = ''
103 If set to <literal>true</literal>, the DNS queries from the
104 hosts connected to the bridge will be forwarded to the DNS
105 servers specified in /etc/resolv.conf .
106 '';
107 };
108
109 };
110
111 virtualisation.xen.stored =
112 mkOption {
113 type = types.path;
114 description =
115 ''
116 Xen Store daemon to use. Defaults to oxenstored of the xen package.
117 '';
118 };
119
120 virtualisation.xen.domains = {
121 extraConfig = mkOption {
122 type = types.string;
123 default = "";
124 description =
125 ''
126 Options defined here will override the defaults for xendomains.
127 The default options can be seen in the file included from
128 /etc/default/xendomains.
129 '';
130 };
131 };
132
133 virtualisation.xen.trace =
134 mkOption {
135 default = false;
136 description =
137 ''
138 Enable Xen tracing.
139 '';
140 };
141 };
142
143
144 ###### implementation
145
146 config = mkIf cfg.enable {
147 assertions = [ {
148 assertion = pkgs.stdenv.isx86_64;
149 message = "Xen currently not supported on ${pkgs.stdenv.hostPlatform.system}";
150 } {
151 assertion = config.boot.loader.grub.enable && (config.boot.loader.grub.efiSupport == false);
152 message = "Xen currently does not support EFI boot";
153 } ];
154
155 virtualisation.xen.package = mkDefault pkgs.xen;
156 virtualisation.xen.package-qemu = mkDefault pkgs.xen;
157 virtualisation.xen.stored = mkDefault "${cfg.package}/bin/oxenstored";
158
159 environment.systemPackages = [ cfg.package ];
160
161 # Make sure Domain 0 gets the required configuration
162 #boot.kernelPackages = pkgs.boot.kernelPackages.override { features={xen_dom0=true;}; };
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.utillinux}/bin/mountpoint -q /proc/xen || \
205 ${pkgs.utillinux}/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 [ { source = "${cfg.package}/etc/xen/xl.conf";
232 target = "xen/xl.conf";
233 }
234 { source = "${cfg.package}/etc/xen/scripts";
235 target = "xen/scripts";
236 }
237 { text = ''
238 source ${cfg.package}/etc/default/xendomains
239
240 ${cfg.domains.extraConfig}
241 '';
242 target = "default/xendomains";
243 }
244 ]
245 ++ lib.optionals (builtins.compareVersions cfg.package.version "4.10" >= 0) [
246 # in V 4.10 oxenstored requires /etc/xen/oxenstored.conf to start
247 { source = "${cfg.package}/etc/xen/oxenstored.conf";
248 target = "xen/oxenstored.conf";
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 (builtins.compareVersions cfg.package.version "4.8" < 0) 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 (builtins.compareVersions cfg.package.version "4.8" < 0) ''
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 ((builtins.compareVersions cfg.package.version "4.8" >= 0)) " -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.package-qemu}/${cfg.package-qemu.qemu-system-i386} \
343 -xen-attach -xen-domid 0 -name dom0 -M xenpv \
344 -nographic -monitor /dev/null -serial /dev/null -parallel /dev/null
345 '';
346 };
347
348
349 systemd.services.xen-watchdog = {
350 description = "Xen Watchdog Daemon";
351 wantedBy = [ "multi-user.target" ];
352 after = [ "xen-qemu.service" "xen-domains.service" ];
353 serviceConfig.ExecStart = "${cfg.package}/bin/xenwatchdogd 30 15";
354 serviceConfig.Type = "forking";
355 serviceConfig.RestartSec = "1";
356 serviceConfig.Restart = "on-failure";
357 };
358
359
360 systemd.services.xen-bridge = {
361 description = "Xen bridge";
362 wantedBy = [ "multi-user.target" ];
363 before = [ "xen-domains.service" ];
364 preStart = ''
365 mkdir -p /var/run/xen
366 touch /var/run/xen/dnsmasq.pid
367 touch /var/run/xen/dnsmasq.etherfile
368 touch /var/run/xen/dnsmasq.leasefile
369
370 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Usable\ range`
371 export XEN_BRIDGE_IP_RANGE_START="${"\${data[1]//[[:blank:]]/}"}"
372 export XEN_BRIDGE_IP_RANGE_END="${"\${data[2]//[[:blank:]]/}"}"
373
374 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
375 export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
376
377 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ mask`
378 export XEN_BRIDGE_NETMASK="${"\${data[1]//[[:blank:]]/}"}"
379
380 echo "${cfg.bridge.address} host gw dns" > /var/run/xen/dnsmasq.hostsfile
381
382 cat <<EOF > /var/run/xen/dnsmasq.conf
383 no-daemon
384 pid-file=/var/run/xen/dnsmasq.pid
385 interface=${cfg.bridge.name}
386 except-interface=lo
387 bind-interfaces
388 auth-zone=xen.local,$XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength}
389 domain=xen.local
390 addn-hosts=/var/run/xen/dnsmasq.hostsfile
391 expand-hosts
392 strict-order
393 no-hosts
394 bogus-priv
395 ${optionalString (!cfg.bridge.forwardDns) ''
396 no-resolv
397 no-poll
398 auth-server=dns.xen.local,${cfg.bridge.name}
399 ''}
400 filterwin2k
401 clear-on-reload
402 domain-needed
403 dhcp-hostsfile=/var/run/xen/dnsmasq.etherfile
404 dhcp-authoritative
405 dhcp-range=$XEN_BRIDGE_IP_RANGE_START,$XEN_BRIDGE_IP_RANGE_END
406 dhcp-no-override
407 no-ping
408 dhcp-leasefile=/var/run/xen/dnsmasq.leasefile
409 EOF
410
411 # DHCP
412 ${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
413 ${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
414 # DNS
415 ${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
416 ${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
417
418 ${pkgs.bridge-utils}/bin/brctl addbr ${cfg.bridge.name}
419 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} ${cfg.bridge.address}
420 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} netmask $XEN_BRIDGE_NETMASK
421 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} up
422 '';
423 serviceConfig.ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq --conf-file=/var/run/xen/dnsmasq.conf";
424 postStop = ''
425 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
426 export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
427
428 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} down
429 ${pkgs.bridge-utils}/bin/brctl delbr ${cfg.bridge.name}
430
431 # DNS
432 ${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
433 ${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
434 # DHCP
435 ${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
436 ${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
437 '';
438 };
439
440
441 systemd.services.xen-domains = {
442 description = "Xen domains - automatically starts, saves and restores Xen domains";
443 wantedBy = [ "multi-user.target" ];
444 after = [ "xen-bridge.service" "xen-qemu.service" ];
445 requires = [ "xen-bridge.service" "xen-qemu.service" ];
446 ## To prevent a race between dhcpcd and xend's bridge setup script
447 ## (which renames eth* to peth* and recreates eth* as a virtual
448 ## device), start dhcpcd after xend.
449 before = [ "dhcpd.service" ];
450 restartIfChanged = false;
451 serviceConfig.RemainAfterExit = "yes";
452 path = [ cfg.package cfg.package-qemu ];
453 environment.XENDOM_CONFIG = "${cfg.package}/etc/sysconfig/xendomains";
454 preStart = "mkdir -p /var/lock/subsys -m 755";
455 serviceConfig.ExecStart = "${cfg.package}/etc/init.d/xendomains start";
456 serviceConfig.ExecStop = "${cfg.package}/etc/init.d/xendomains stop";
457 };
458
459 };
460
461}