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.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
246 # Xen provides udev rules.
247 services.udev.packages = [ cfg.package ];
248
249 services.udev.path = [ pkgs.bridge-utils pkgs.iproute ];
250
251 systemd.services.xen-store = {
252 description = "Xen Store Daemon";
253 wantedBy = [ "multi-user.target" ];
254 after = [ "network.target" "xen-store.socket" ];
255 requires = [ "xen-store.socket" ];
256 preStart = ''
257 export XENSTORED_ROOTDIR="/var/lib/xenstored"
258 rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null
259
260 mkdir -p /var/run
261 mkdir -p /var/log/xen # Running xl requires /var/log/xen and /var/lib/xen,
262 mkdir -p /var/lib/xen # so we create them here unconditionally.
263 grep -q control_d /proc/xen/capabilities
264 '';
265 serviceConfig = if cfg.package.version < "4.8" then
266 { ExecStart = ''
267 ${cfg.stored}${optionalString cfg.trace " -T /var/log/xen/xenstored-trace.log"} --no-fork
268 '';
269 } else {
270 ExecStart = ''
271 ${cfg.package}/etc/xen/scripts/launch-xenstore
272 '';
273 Type = "notify";
274 RemainAfterExit = true;
275 NotifyAccess = "all";
276 };
277 postStart = ''
278 ${optionalString (cfg.package.version < "4.8") ''
279 time=0
280 timeout=30
281 # Wait for xenstored to actually come up, timing out after 30 seconds
282 while [ $time -lt $timeout ] && ! `${cfg.package}/bin/xenstore-read -s / >/dev/null 2>&1` ; do
283 time=$(($time+1))
284 sleep 1
285 done
286
287 # Exit if we timed out
288 if ! [ $time -lt $timeout ] ; then
289 echo "Could not start Xenstore Daemon"
290 exit 1
291 fi
292 ''}
293 echo "executing xen-init-dom0"
294 ${cfg.package}/lib/xen/bin/xen-init-dom0
295 '';
296 };
297
298 systemd.sockets.xen-store = {
299 description = "XenStore Socket for userspace API";
300 wantedBy = [ "sockets.target" ];
301 socketConfig = {
302 ListenStream = [ "/var/run/xenstored/socket" "/var/run/xenstored/socket_ro" ];
303 SocketMode = "0660";
304 SocketUser = "root";
305 SocketGroup = "root";
306 };
307 };
308
309
310 systemd.services.xen-console = {
311 description = "Xen Console Daemon";
312 wantedBy = [ "multi-user.target" ];
313 after = [ "xen-store.service" ];
314 requires = [ "xen-store.service" ];
315 preStart = ''
316 mkdir -p /var/run/xen
317 ${optionalString cfg.trace "mkdir -p /var/log/xen"}
318 grep -q control_d /proc/xen/capabilities
319 '';
320 serviceConfig = {
321 ExecStart = ''
322 ${cfg.package}/bin/xenconsoled\
323 ${optionalString ((cfg.package.version >= "4.8")) " -i"}\
324 ${optionalString cfg.trace " --log=all --log-dir=/var/log/xen"}
325 '';
326 };
327 };
328
329
330 systemd.services.xen-qemu = {
331 description = "Xen Qemu Daemon";
332 wantedBy = [ "multi-user.target" ];
333 after = [ "xen-console.service" ];
334 requires = [ "xen-store.service" ];
335 serviceConfig.ExecStart = ''
336 ${cfg.package-qemu}/${cfg.package-qemu.qemu-system-i386} \
337 -xen-attach -xen-domid 0 -name dom0 -M xenpv \
338 -nographic -monitor /dev/null -serial /dev/null -parallel /dev/null
339 '';
340 };
341
342
343 systemd.services.xen-watchdog = {
344 description = "Xen Watchdog Daemon";
345 wantedBy = [ "multi-user.target" ];
346 after = [ "xen-qemu.service" "xen-domains.service" ];
347 serviceConfig.ExecStart = "${cfg.package}/bin/xenwatchdogd 30 15";
348 serviceConfig.Type = "forking";
349 serviceConfig.RestartSec = "1";
350 serviceConfig.Restart = "on-failure";
351 };
352
353
354 systemd.services.xen-bridge = {
355 description = "Xen bridge";
356 wantedBy = [ "multi-user.target" ];
357 before = [ "xen-domains.service" ];
358 preStart = ''
359 mkdir -p /var/run/xen
360 touch /var/run/xen/dnsmasq.pid
361 touch /var/run/xen/dnsmasq.etherfile
362 touch /var/run/xen/dnsmasq.leasefile
363
364 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Usable\ range`
365 export XEN_BRIDGE_IP_RANGE_START="${"\${data[1]//[[:blank:]]/}"}"
366 export XEN_BRIDGE_IP_RANGE_END="${"\${data[2]//[[:blank:]]/}"}"
367
368 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
369 export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
370
371 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ mask`
372 export XEN_BRIDGE_NETMASK="${"\${data[1]//[[:blank:]]/}"}"
373
374 echo "${cfg.bridge.address} host gw dns" > /var/run/xen/dnsmasq.hostsfile
375
376 cat <<EOF > /var/run/xen/dnsmasq.conf
377 no-daemon
378 pid-file=/var/run/xen/dnsmasq.pid
379 interface=${cfg.bridge.name}
380 except-interface=lo
381 bind-interfaces
382 auth-zone=xen.local,$XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength}
383 domain=xen.local
384 addn-hosts=/var/run/xen/dnsmasq.hostsfile
385 expand-hosts
386 strict-order
387 no-hosts
388 bogus-priv
389 ${optionalString (!cfg.bridge.forwardDns) ''
390 no-resolv
391 no-poll
392 auth-server=dns.xen.local,${cfg.bridge.name}
393 ''}
394 filterwin2k
395 clear-on-reload
396 domain-needed
397 dhcp-hostsfile=/var/run/xen/dnsmasq.etherfile
398 dhcp-authoritative
399 dhcp-range=$XEN_BRIDGE_IP_RANGE_START,$XEN_BRIDGE_IP_RANGE_END
400 dhcp-no-override
401 no-ping
402 dhcp-leasefile=/var/run/xen/dnsmasq.leasefile
403 EOF
404
405 # DHCP
406 ${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
407 ${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
408 # DNS
409 ${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
410 ${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
411
412 ${pkgs.bridge-utils}/bin/brctl addbr ${cfg.bridge.name}
413 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} ${cfg.bridge.address}
414 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} netmask $XEN_BRIDGE_NETMASK
415 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} up
416 '';
417 serviceConfig.ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq --conf-file=/var/run/xen/dnsmasq.conf";
418 postStop = ''
419 IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
420 export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
421
422 ${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} down
423 ${pkgs.bridge-utils}/bin/brctl delbr ${cfg.bridge.name}
424
425 # DNS
426 ${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
427 ${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
428 # DHCP
429 ${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
430 ${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
431 '';
432 };
433
434
435 systemd.services.xen-domains = {
436 description = "Xen domains - automatically starts, saves and restores Xen domains";
437 wantedBy = [ "multi-user.target" ];
438 after = [ "xen-bridge.service" "xen-qemu.service" ];
439 requires = [ "xen-bridge.service" "xen-qemu.service" ];
440 ## To prevent a race between dhcpcd and xend's bridge setup script
441 ## (which renames eth* to peth* and recreates eth* as a virtual
442 ## device), start dhcpcd after xend.
443 before = [ "dhcpd.service" ];
444 restartIfChanged = false;
445 serviceConfig.RemainAfterExit = "yes";
446 path = [ cfg.package cfg.package-qemu ];
447 environment.XENDOM_CONFIG = "${cfg.package}/etc/sysconfig/xendomains";
448 preStart = "mkdir -p /var/lock/subsys -m 755";
449 serviceConfig.ExecStart = "${cfg.package}/etc/init.d/xendomains start";
450 serviceConfig.ExecStop = "${cfg.package}/etc/init.d/xendomains stop";
451 };
452
453 };
454
455}