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