-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
2012-lab-reseau-kvm.html
382 lines (311 loc) · 15.5 KB
/
2012-lab-reseau-kvm.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
---
title: "Labo virtuel avec QEMU"
uuid: aac3ecda-175b-11e2-99e8-0018f3034e06
attachments:
"https://github.com/vincentbernat/network-lab/tree/master/lab-ecmp-ipv6": "dépôt GitHub"
tags:
- network
---
J'utilisais jusqu'ici des [machines virtuelles UML][uml-lab] pour
toutes mes expérimentations relatives au réseau. De nombreuses
alternatives existent : [GNS3][], [Netkit][], [Marionnet][] ou
[Cloonix][]. Malgré leurs qualités, je considère encore ma propre
solution comme la plus adaptée à mes attentes :
- Je ne veux pas utiliser d'**images disques**. Elles prennent
beaucoup de place et il faut les maintenir. Elles ont aussi parfois
tendance à l'embonpoint si elles sont beaucoup utilisées. Elles
sont de plus difficiles à partager.
- L'accès à mon **répertoire personnel** (`/home`) depuis chacune des
machines virtuelles m'est aussi essentiel. Cela facilite l'échange
de fichiers avec les VM et je peux disposer de tous les fichiers de
configuration important pour le labo dans un répertoire dédié.
- Je ne veux pas **démarrer un système complet**. Cela permet
d'économiser de la mémoire et de démarrer le labo en quelques
secondes.
L'utilisation d'UML me posait quelques soucis :
- Il est peu utilisé et sujet à certains bugs. Il n'est par exemple
pas possible d'utiliser un
[_gdbserver_ depuis UML][gdbserver inside UML] sans un
correctif. Parfois, le noyau peut simplement ne pas compiler.
- Il est plutôt lent.
Toutefois, l'un des points forts d'UML est la présence de [HostFS][],
un système de fichiers donnant accès à une partie du système de
fichiers de l'hôte. C'est une fonctionnalité clé pour mon usage qui
me permet de me passer d'images disques et d'accéder à mon `/home`
depuis chaque machine invitée.
J'ai découvert que QEMU fournissait [9P][], un système de fichiers
similaire se basant sur _VirtIO_, un système d'IO paravirtualisé.
[TOC]
# Mise en place du labo
La mise en palce du labo est effectuée à travers un
[unique fichier de script][setup]. La procédure est très similaire à
ce que [je faisais avec UML][uml-lab]. Je décris ici les étapes les
plus intéressantes.
## Démarrer QEMU avec un noyau minimal
Mon principal objectif était de tester le
[patch de Nicolas Dichtel concernant le support IPv6 d'ECMP][ecmp-ipv6]. J'ai
donc besoin d'un noyau adhoc. Concernant la configuration de celui-ci,
je suis parti du résultat de `make defconfig`, j'ai retiré tout ce qui
n'était pas nécessaire, ajouté ce dont j'avais spécifiquement besoin
pour le labo (des options concernant principalement la pile réseau) et
ajoutés les pilotes pour utiliser _VirtIO_ :
::
CONFIG_NET_9P_VIRTIO=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_CONSOLE=y
CONFIG_HW_RANDOM_VIRTIO=y
CONFIG_VIRTIO=y
CONFIG_VIRTIO_RING=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BALLOON=y
CONFIG_VIRTIO_MMIO=y
Aucun module. Jetez un œil sur [la configuration complète][.config] si
besoin.
Il est ensuite possible de faire démarrer ce noyau :
::sh
qemu-system-x86_64 \
-m 256m \
-display none \
-nodefconfig -no-user-config -nodefaults \
\
-chardev stdio,id=charserial0,signal=off \
-device isa-serial,chardev=charserial0,id=serial0 \
\
-chardev socket,id=con0,path=$TMP/vm-$name-console.pipe,server,nowait \
-mon chardev=con0,mode=readline,default \
\
-kernel $LINUX \
-append "init=/bin/sh console=ttyS0"
`$LINUX` correspond au fichier `bzImage`. Bien sûr, sans disque, le
noyau s'arrête net quand il cherche la racine. La
configuration n'utilise pas d'écran virtuel (`-display none`). Un port
série est défini et utilise l'entrée/sortie standard[^signal]. Le
noyau est configuré pour utiliser ce dernier comme console
(`console=ttyS0`). Il existe des consoles _VirtIO_, mais elles ne sont
pas fonctionnelles au moment du démarrage du noyau.
[^signal]: `stdio` est configuré de façon à ne pas gérer les
signaux. Ainsi, QEMU ne s'arrêtera pas s'il reçoit
`SIGINT`. Comme on l'utilise comme console série, c'est un
aspect important.
Le moniteur de QEMU est configuré pour écouter sur une socket Unix. On
s'y connecte avec la commande `socat UNIX:$TMP/vm-$name-console.pipe -`.
## Disque mémoire (_ramdisk_)
!!! "Mise à jour (10.2012)" Initialement, j'utilisais un _initrd_ car je ne
parvenais pas à convaincre le noyau de monter directement le système
de fichiers de l'hôte comme racine pour le système invité. Dans un
commentaire, [Josh Triplett](https://joshtriplett.org/) m'a mis sur la
bonne voie en m'indiquant d'utiliser `/dev/root` comme étiquette de
montage. À titre documentaire, je continue d'utiliser ici un _initrd_,
mais j'ai déjà mis à jour le labo sur [GitHub][setup] pour ne plus en
faire usage.
Voici comment construire un _ramdisk_ minimal:
::sh
# Setup initrd
setup_initrd() {
info "Build initrd"
DESTDIR=$TMP/initrd
mkdir -p $DESTDIR
# Setup busybox
copy_exec $($WHICH busybox) /bin/busybox
for applet in $(${DESTDIR}/bin/busybox --list); do
ln -s busybox ${DESTDIR}/bin/${applet}
done
# Setup init
cp $PROGNAME ${DESTDIR}/init
cd "${DESTDIR}" && find . | \
cpio --quiet -R 0:0 -o -H newc | \
gzip > $TMP/initrd.gz
}
La fonction `copy_exec` provient du paquet `initramfs-tools` dans
Debian. Elle permet de copier un exécutable ainsi que toutes les
bibliothèques nécessaires pour son exécution. Une autre approche
aurait été d'exploiter une version statique de _busybox_.
Le script de configuration du labo est copié dans le _ramdisk_ sous le
nom `/init`. Il détectera qu'il est invoqué ainsi. Si ce script est
omis, le noyau démarrera un shell à la place.
L'option `-initrd` indique à QEMU de charger ce _ramdisk_ en mémoire.
## Système de fichiers racine
Mettons maintenant en place le système de fichiers racine à l'aide de
_9P_. Indiquons d'abord à QEMU d'exporter le système de fichiers de
l'hôte au système invité :
::sh
qemu-system-x86_64 \
${PREVIOUS_ARGS} \
-fsdev local,security_model=passthrough,id=fsdev-root,path=${ROOT},readonly \
-device virtio-9p-pci,id=fs-root,fsdev=fsdev-root,mount_tag=rootshare
`${ROOT}` peut soit être `/`, soit un répertoire contenant un système
complet. Dans l'invité, il est alors possible de monter le répertoire
ainsi partagé :
::sh
mkdir -p /target/ro
mount -t 9p rootshare /target/ro -o trans=virtio,version=9p2000.u
On se retrouve alors avec un système de fichiers racine complet dans
`/target/ro`. Ce dernier a été monté en lecture seule car nous ne
disposons pas des droits pour le modifier (il ne faut pas lancer le
labo avec les droits du superutilisateur). Comme pour un _Live CD_,
nous allons superposer un système de fichiers en mémoire qui recevra
les modifications effectuées pendant la vie du système. Les noyaux
Debian fournissent actuellement [AUFS][] tandis que Ubuntu et OpenWRT
sont passés à [overlayfs][]. J'ai par le passé rencontré quelques
erreurs lors de l'utilisation d'AUFS. Il n'est pas encore très clair
[lequel des deux sera officiellement intégré dans le noyau][lwn-overlayfs].
Essayons _overlayfs_.
Il n'existe à ma connaissance pas de patch tout fait à appliquer sur
son noyau pour _overlayfs_. Je travaille avec la
[branche _net-next_ de David Miller][davem]. Voici comme appliquer
_overlayfs_ par dessus celle-ci :
::console
$ git remote add torvalds git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
$ git fetch torvalds
$ git remote add overlayfs git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git
$ git fetch overlayfs
$ git merge-base overlayfs.v15 v3.6
4cbe5a555fa58a79b6ecbb6c531b8bab0650778d
$ git checkout -b net-next+overlayfs
$ git cherry-pick 4cbe5a555fa58a79b6ecbb6c531b8bab0650778d..overlayfs.v15
Ne pas oublier d'activer `CONFIG_OVERLAYFS_FS` dans le
`.config`. Voici comment est configuré le système de fichiers racine :
::sh
info "Setup overlayfs"
mkdir /target
mkdir /target/ro
mkdir /target/rw
mkdir /target/overlay
# Version 9p2000.u allows one to access /dev, /sys and mount new
# partitions over them. This is not the case for 9p2000.L.
mount -t 9p rootshare /target/ro -o trans=virtio,version=9p2000.u
mount -t tmpfs tmpfs /target/rw -o rw
mount -t overlayfs overlayfs /target/overlay -o lowerdir=/target/ro,upperdir=/target/rw
mount -n -t proc proc /target/overlay/proc
mount -n -t sysfs sys /target/overlay/sys
info "Mount home directory on /root"
mount -t 9p homeshare /target/overlay/root -o trans=virtio,version=9p2000.L,access=0,rw
info "Mount lab directory on /lab"
mkdir /target/overlay/lab
mount -t 9p labshare /target/overlay/lab -o trans=virtio,version=9p2000.L,access=0,rw
info "Chroot"
export STATE=1
cp "$PROGNAME" /target/overlay
exec chroot /target/overlay "$PROGNAME"
Il faut configurer QEMU pour exporter `${HOME}` et le répertoire
contenant le labo :
::sh
qemu-system-x86_64 \
${PREVIOUS_ARGS} \
-fsdev local,security_model=passthrough,id=fsdev-root,path=${ROOT},readonly \
-device virtio-9p-pci,id=fs-root,fsdev=fsdev-root,mount_tag=rootshare \
-fsdev local,security_model=none,id=fsdev-home,path=${HOME} \
-device virtio-9p-pci,id=fs-home,fsdev=fsdev-home,mount_tag=homeshare \
-fsdev local,security_model=none,id=fsdev-lab,path=$(dirname "$PROGNAME") \
-device virtio-9p-pci,id=fs-lab,fsdev=fsdev-lab,mount_tag=labshare
## Réseau
N'oublions pas la partie réseau. Pour chaque réseau L2 nécessaire au
labo, mettons en place un switch VDE :
::sh
# Setup a VDE switch
setup_switch() {
info "Setup switch $1"
screen -t "sw-$1" \
start-stop-daemon --make-pidfile --pidfile "$TMP/switch-$1.pid" \
--start --startas $($WHICH vde_switch) -- \
--sock "$TMP/switch-$1.sock"
screen -X select 0
}
Et pour y attacher une interface :
::sh
mac=$(echo $name-$net | sha1sum | \
awk '{print "52:54:" substr($1,0,2) ":" substr($1, 2, 2) ":" substr($1, 4, 2) ":" substr($1, 6, 2)}')
qemu-system-x86_64 \
${PREVIOUS_ARGS} \
-net nic,model=virtio,macaddr=$mac,vlan=$net \
-net vde,sock=$TMP/switch-$net.sock,vlan=$net
L'utilisation d'un switch VDE permet de se passer des droits
superutilisateur. Il est possible de donner accès à Internet à chaque
VM, soit en utilisant `-net user`, soit en utilisant un switch VDE et
en y attachant un processus `slirpvde`. Cette dernière solution permet
aux VM de communiquer entre elles via Internet si besoin.
# Debug
Le but principal de ce labo était de débugguer à la fois le noyau et
Quagga, ce qui peut se faire « à distance » dans les deux cas.
## Debug noyau
[KGDB][] est le débuggueur, compatible avec [GDB][], inclus dans le
noyau. Cependant, QEMU embarque un _serveur GDB_ qui est plus simple
d'utilisation.
::sh
qemu-system-x86_64 \
${PREVIOUS_ARGS} \
-gdb unix:$TMP/vm-$name-gdb.pipe,server,nowait
Pour se connecter à ce serveur, il faut d'abord localiser le fichier
`vmlinux`, présent à la racine des sources d'un noyau compilé. Pour en
faire bon usage, le noyau doit avoir été compilé avec l'option
`CONFIG_DEBUG_INFO=y`. Il est ensuite possible d'utiliser `socat` pour
s'attacher au débuggueur distant :
::console
$ gdb vmlinux
GNU gdb (GDB) 7.4.1-debian
Reading symbols from /home/bernat/src/linux/vmlinux...done.
(gdb) target remote | socat UNIX:$TMP/vm-r1-gdb.pipe -
Remote debugging using | socat UNIX:/tmp/tmp.W36qWnrCEj/vm-r1-gdb.pipe -
native_safe_halt () at /home/bernat/src/linux/arch/x86/include/asm/irqflags.h:50
50 }
(gdb)
Le noyau se débuggue alors comme un programme classique.
Le debug est plus simple si les optimisations sont
désactivées. Cependant,
[il n'est pas possible de les désactiver globalement][gcc20217]. On
peut toutefois le faire de manière sélective. Par exemple, si on
est intéressé par `net/ipv6/route.c`, il suffit d'ajouter
`CFLAGS_route.o = -O0` à la fin de `net/ipv6/Makefile`, de supprimer
le fichier `net/ipv6/route.o` et de lancer `make`.
## Debug Quagga
Pour débugguer un programme classique, le plus simple est de
simplement utiliser `gdb` dans le système invité. La présence des
sources dans le `/home` rend cette approche tout à fait
supportable. Nonobstant, il est assez simple de débugguer à
distance. Ajoutons un port série à QEMU :
::sh
qemu-system-x86_64 \
${PREVIOUS_ARGS} \
-chardev socket,id=charserial1,path=$TMP/vm-$name-serial.pipe,server,nowait \
-device isa-serial,chardev=charserial1,id=serial1
Puis lançons `gdbserver` dans le système invité :
::console
$ libtool execute gdbserver /dev/ttyS1 zebra/zebra
Process /root/code/orange/quagga/build/zebra/.libs/lt-zebra created; pid = 800
Remote debugging using /dev/ttyS1
Depuis l'hôte, attachons-nous au processus distant :
::console
$ libtool execute gdb zebra/zebra
GNU gdb (GDB) 7.4.1-debian
Reading symbols from /home/bernat/code/orange/quagga/build/zebra/.libs/lt-zebra...done.
(gdb) target remote | socat UNIX:/tmp/tmp.W36qWnrCEj/vm-r1-serial.pipe -
Remote debugging using | socat UNIX:/tmp/tmp.W36qWnrCEj/vm-r1-serial.pipe -
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00007ffff7dddaf0 in ?? () from /lib64/ld-linux-x86-64.so.2
(gdb)
# Démonstration
Voici une courte démonstration du labo ainsi obtenu :
![]([[!!videos/2012-network-lab-kvm.m3u8]])
*[UML]: User Mode Linux
*[KVM]: Kernel-based Virtual Machine
*[VM]: Virtual Machine
[uml-lab]: [[fr/blog/2011-lab-reseau-uml.html]] "Labo virtuel avec UML"
[Cloonix]: http://clownix.net/ "Cloonix: dynamical topology virtual networks"
[Marionnet]: https://www.marionnet.org/ "Marionnet: a virtual network laboratory"
[Netkit]: https://www.netkit.org/ "Netkit: the poor man's system to experiment computer networking"
[GNS3]: https://www.gns3.com "GNS3: Graphical Network Simulator"
[HostFS]: http://user-mode-linux.sourceforge.net/hostfs.html "UML: Host File Access"
[gdbserver inside UML]: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=676184 "Bug #676184: gdbserver not usable inside UML"
[9P]: https://wiki.qemu.org/Documentation/9psetup "Setting up 9P in QEMU"
[ecmp-ipv6]: https://patchwork.ozlabs.org/patch/188562/ "ipv6: add support of ECMP"
[setup]: https://github.com/vincentbernat/network-lab/blob/master/lab-ecmp-ipv6/setup
[AUFS]: http://aufs.sourceforge.net/ "AUFS: advanced multi-layered unification filesystem"
[overlayfs]: https://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git "Dépôt git pour overlayfs"
[lwn-overlayfs]: https://lwn.net/Articles/447650/ "LWN: Debating overlayfs"
[davem]: https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git "Dépôt git pour net-next"
[.config]: https://github.com/vincentbernat/network-lab/blob/master/lab-ecmp-ipv6/config-3.6%2Becmp%2Boverlayfs ".config minimaliste"
[KGDB]: https://web.archive.org/web/2012/http://kgdb.linsyssoft.com/ "KGDB: Linux Kernel Source Level Debugger"
[GDB]: http://www.gnu.org/software/gdb/ "GDB: The GNU Project Debugger"
[gcc20217]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=20217 "Bug 20217 - Switching off the optimization triggers undefined reference at link time when building Linux kernel"