- Abraxas - http://www.effinger.org/blog -

Virtualbox und Intrepid – das perfekte Setup – Netzwerk

Nach der Installation von Virtualbox [1] erhält man zunächst folgende Fehlermeldung: „Could not load the Host USB Proxy Service (VERR_FILE_NOT_FOUND). The service might be not installed on the host computer. Fehlercode:  NS_ERROR_FAILURE (0x00004005)“. Damit USB wie gewünscht funktioniert und die Fehlermeldung verschwindet muss man unter Intrepid folgende Zeile in /etc/fstab einfügen:

none	/proc/bus/usb	usbfs	devgid=46,devmode=775	0	0

Als nächstes machen wir uns an die Netzwerkeinstellungen. Für die virtuellen Maschinen möchte ich tap Interfaces nutzen, die über eine Proxy ARP Bridge in mein Netzwerk eingebunden werden und per dhcp eine IP zugewiesen bekommen sollen. Die Proxy ARP Bridge hat den Vorteil, dass sie auch in einem drahtlosen Netzwerk funktioniert, wo eine normale Bridge mit brctl Probleme verursacht. Benötigt werden dazu die Programme parprouted und bcrelay. Gilbert Mendoza beschreibt ausführlich, wie man dieses Setup manuell einrichten [2] kann. Zunächst müssen wir verschiedene Programme installieren:

sudo apt-get install parprouted uml-utilities bcrelay

Damit bei jedem Rechnerstart die Netzwerkinterfaces automatisch erstellt werden, habe ich ein Skript gebastelt, das die entsprechenden Netzwerkinterfaces, z.B. tap0 bereitstellt. Dieses Skript basiert auf der Vorarbeit von Jochem Kossen [3] und der Anleitung von Dominic Edmonds [4]. Zur Einrichtung müssen wir zunächst die Konfigurationsdatei erstellen und einrichten:

sudo mkdir /etc/virtualbox
sudo nano /etc/virtualbox/config

Diese Datei hat bei mir folgenden Inhalt:

HOST_IF="eth1"
HOST_IP="192.168.0.64"
VM_USER="markus"
USE_NAT="no"
TAPS="tap0 tap1"
USE_ARPBRIDGE="yes"
ARP_IPS="169.254.0.1/32 169.254.0.2/32"

Die Datei config muss nun natürlich angepasst werden. HOST_IF bezeichnet das Netzwerkinterfaces, mit dem die virtuellen Systeme verbunden werden sollen, die HOST_IP ist die entsprechene IP-Adresse des Hosts. Als VM_USER muss der Benutzer eingetragen werden, der die virtuellen Maschinen nutzt. USE_NAT wird auf no gesetzt, da ein Anbindung über NAT bei Proxy ARP nicht notwendig ist. Unter TAPS werden die zu erzeugenden tap-Netzwerkinterfaces durch ein Leerzeichen getrennt aufgelistet. USE_ARPBRIDGE setzen wir auf yes, denn wir wollen eine Proxy ARP Bridge erzeugen. Unter ARP_IPS listen wir die zu den tap-Interfaces zugehörigen IP-Adressen auf. Dazu muss man allerdings wissen, dass diese IP-Adressen keinerlei Bedeutung haben und eigentlich völlig beliebig sind. Man sollte jedoch darauf achten, dass die vergebenen IP-Adressen nicht mit denen im Netzwerksetup im Konflikt stehen. Ich verwende deshalb die oben angegebenen Microsoft IP-Adressen, die eigentlich von Windows vergeben werden, wenn kein DHCP-Server eine IP-Adresse zuweist.
Im nächsten Schritt erzeugen wir eine weitere Konfigurationsdatei, die die Namen derjenigen virtuellen Systeme enthält (jedes in einer Zeile), die automatisch gestartet werden sollen, wenn das System hochfährt. Ich persönlich habe hierauf verzichtet und erzeuge deshalb eine leere Datei.

sudo touch /etc/virtualbox/machines_enabled

Nun erzeugen wir das eigentlich Skript vboxcontrol, das die gesamte Arbeit erledigt mit

sudo nano /etc/init.d/vboxcontrol

Die Datei füllen wir mit folgendem Inhalt (alternativ kann man das Script herunterladen [5]):

#! /bin/sh
### BEGIN INIT INFO
# Provides: virtualbox_vms
# Required-Start:    $local_fs $syslog $remote_fs
# Required-Stop:     $local_fs $syslog $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Control VirtualBox Virtual Machine instances
### END INIT INFO
#
# Modified by Markus Effinger, http://www.effinger.org
# based on
# Version 2008051100 by Jochem Kossen <jochem.kossen@gmail.com>
# http://farfewertoes.com
#
# Released in the public domain
#
# This file came with a README file containing the instructions on how
# to use this script.
#

. /lib/lsb/init-functions

# Are we running from init?
run_by_init() {
    ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ]
}

################################################################################
# INITIAL CONFIGURATION
VBOXDIR="/etc/virtualbox"
VM_USER="root"
USE_NAT="yes"
USE_ARPBRIDGE="no"

export PATH="${PATH:+$PATH:}/bin:/usr/bin:/usr/sbin:/sbin"

if [ -f $VBOXDIR/config ]; then
    . $VBOXDIR/config
else
    echo "ERROR: $VBOXDIR/config does not exist. Exiting."
    exit 1
fi

SU="su $VM_USER -c"
VBOXMANAGE="VBoxManage -nologo"
PARPROUTEDBIN=`which parprouted`
BCRELAYBIN=`which bcrelay`

################################################################################
# FUNCTIONS

# Determine if USE_NAT is set to "yes"
use_nat() {
    if [ "$USE_NAT" = "yes" ]; then
        return `true`
    else
        return `false`
    fi
}

# Determine if USE_ARPBRIDGE is set to "yes"
use_arpbridging() {
    if [ "$USE_ARPBRIDGE" = "yes" ]; then
        return `true`
    else
        return `false`
    fi
}

# Bring up the bridge interface
enable_bridge() {
    # If NAT is enabled, don't do anything
    use_nat && return

    # If ARP Bridge is enabled, don't do anything
    use_arpbridging && return

    # Load the tun module
    if [ ! -e /dev/net/tun ]; then
        modprobe tun
    fi

    brctl addbr br0 || /bin/true

    # Disable $HOST_IF; host will use br0 instead
    ifdown $HOST_IF
    ifconfig $HOST_IF 0.0.0.0 promisc
    brctl addif br0 $HOST_IF

    # Bring up br0
    ifup br0

    # Answer ARP requests for $HOST_IP (which now come on br0) with the MAC
    # address of $HOST_IF
    arp -Ds $HOST_IP $HOST_IF pub
}

# Bring down the bridge interface
disable_bridge() {
    # If NAT is enabled, don't do anything
    use_nat && return

    ifdown br0
    brctl delbr br0
    ifup $HOST_IF
}

# Activate tap interfaces
enable_taps() {
    # If NAT is enabled, don't do anything
    use_nat && return

    # If ARP Bridge is enabled, don't do anything
    use_arpbridging && return

    for TAP in $TAPS; do
        # Check if $TAP is configured already
        ifconfig $TAP > /dev/null 2>&1
        if [ $? != 0 ]; then
            tunctl -t $TAP -u $VM_USER
            brctl addif br0 $TAP

            # Disable tap interfaces for host; guest will activate them for themselves
            ifconfig $TAP 0.0.0.0 promisc

            # Enable proxy_arp so that ARP requests can be answered correctly
            # by the host
            sysctl net.ipv4.conf.$TAP.proxy_arp=1

            # Add a route for the tap device
            route add -host $HOST_IP dev $TAP
        else
            log_failure_msg "Interface $TAP already configured"
        fi
    done
}

# Activate tap interfaces for ARP Bridging
enable_taps_arp() {
    # If ARP Bridge is disabled, don't do anything
    ! use_arpbridging && return

    # Enable routing that pakets can be transfered from $TAP to $HOST_IF
    # by the host
    sysctl net.ipv4.ip_forward=1

    # i contains the index of the current TAP device in the array of all TAP devices
    i=0
    for TAP in $TAPS; do
        # make sure that ARP_IP contains the corresponding IP of the interface TAP
        # j contains the index of the ip address in the array of all ip addresses
        j=0
        for ARP_IP in $ARP_IPS; do
           if [ $i = $j ]; then
              break
           fi
           j=$(( $j + 1 ))
        done
        i=$(( $i + 1 ))

        # Check if $TAP is configured already
        ifconfig $TAP > /dev/null 2>&1
        if [ $? != 0 ]; then
            tunctl -t $TAP -u $VM_USER

            # Disable tap interfaces for host; guest will activate them for themselves
            #ifconfig $TAP 0.0.0.0 promisc
            ip link set $TAP up
            ip addr add $ARP_IP dev $TAP

            # Enable proxy_arp so that ARP requests can be answered correctly
            # by the host
            sysctl net.ipv4.conf.$TAP.proxy_arp=1

            # Start parprouted for Proxy ARP Bridging
            start-stop-daemon --background --start --quiet --pidfile /var/run/parprouted.$TAP.pid \
               --exec $PARPROUTEDBIN -- $HOST_IF $TAP

            # Start relaying broadcasts - required for dhcp to work,
            # do not use the -d switch for bcrelay - start-stop-daemon would not work then
            start-stop-daemon --background --start --quiet --make-pidfile --pidfile /var/run/bcrelay.$TAP.pid \
               --exec $BCRELAYBIN -- -n -i $TAP -o $HOST_IF

        else
            log_failure_msg "Interface $TAP already configured"
        fi
    done
}

# Disable/deconfigure tap interfaces for ARP Bridging
disable_taps_arp() {
    # If ARP Bridge is disabled, don't do anything
    ! use_arpbridging && return

    #Delete all created tap interfaces and kill corresponding processes
    for TAP in $TAPS; do
        start-stop-daemon --stop --quiet --pidfile /var/run/parprouted.$TAP.pid \
           --exec $PARPROUTEDBIN
        start-stop-daemon --stop --quiet --pidfile /var/run/bcrelay.$TAP.pid \
           --exec $BCRELAYBIN
        tunctl -d $TAP
    done
}

# Disable/deconfigure tap interfaces
disable_taps() {
    # If NAT is enabled, don't do anything
    use_nat && return

    # If ARP Bridge is enabled, don't do anything
    use_arpbridging && return

    for TAP in $TAPS; do
        route del -host $HOST_IP dev $TAP
        brctl delif br0 $TAP
        tunctl -d $TAP
    done
}

# Check for running machines every few seconds; return when all machines are
# down
wait_for_closing_machines() {
    RUNNING_MACHINES=`$SU "$VBOXMANAGE list runningvms" | wc -l`
    if [ $RUNNING_MACHINES != 0 ]; then
        sleep 5
        wait_for_closing_machines
    fi
}

################################################################################
# RUN
case "$1" in
    start)
        if [ -f /etc/virtualbox/machines_enabled ]; then
            if [ ! use_nat ] && [ ! use_arpbridging ]; then
                enable_bridge
                enable_taps

                chown root.vboxusers /dev/net/tun
                chmod 0660 /dev/net/tun
            fi

            if [ use_arpbridging ]; then
                enable_taps_arp
                chown root.vboxusers /dev/net/tun
                chmod 0660 /dev/net/tun
            fi

            cat /etc/virtualbox/machines_enabled | while read VM; do
                log_action_msg "Starting VM: $VM ..."
                $SU "$VBOXMANAGE startvm \"$VM\" -type vrdp"
            done
        fi
        ;;
    stop)
        # NOTE: this stops all running VM's. Not just the ones listed in the
        # config
        $SU "$VBOXMANAGE list runningvms" | while read VM; do
            log_action_msg "Shutting down VM: $VM ..."
            $SU "$VBOXMANAGE controlvm \"$VM\" acpipowerbutton"
        done

        wait_for_closing_machines

        if [ ! use_nat ] && [ ! use_arpbridging ]; then
            disable_taps
            disable_bridge
        fi

        if [ use_arpbridging ]; then
            disable_taps_arp
        fi
        ;;
    bridge-up)
        enable_bridge
        ;;
    bridge-down)
        disable_bridge
        ;;
    taps-up)
        enable_taps
        ;;
    taps-down)
        disable_taps
        ;;
    taps-arp-up)
        enable_taps_arp
        ;;
    taps-arp-down)
        disable_taps_arp
        ;;
    start-vm)
        log_action_msg "Starting VM: $2 ..."
        $SU "$VBOXMANAGE startvm \"$2\" -type vrdp"
        ;;
    stop-vm)
        log_action_msg "Stopping VM: $2 ..."
        $SU "$VBOXMANAGE controlvm \"$2\" acpipowerbutton"
        ;;
    poweroff-vm)
        log_action_msg "Powering off VM: $2 ..."
        $SU "$VBOXMANAGE controlvm \"$2\" poweroff"
        ;;
    status)
        log_action_msg "The following virtual machines are currently running:"
        $SU "$VBOXMANAGE list runningvms" | while read VM; do
            echo -n "$VM ("
            echo -n `$SU "VBoxManage showvminfo $VM|grep Name:|sed -e 's/^Name:\s*//g'"`
            echo ')'
        done
        ;;
    *)
        log_failure_msg "Usage: $0 {start|stop|status|start-vm <VM name>|stop-vm <VM name>|poweroff-vm <VM name>|bridge-up|bridge-down|taps-up|taps-down}"
        exit 3
esac

exit 0

Nach dem Speichern der Datei setzen wir noch entsprechende Dateiberechtigungen und sorgen dafür, dass dieses Skript in den Startvorgang des Systems aufgenommen wird:

sudo chown root.root /etc/init.d/vboxcontrol
sudo chmod 755 /etc/init.d/vboxcontrol
sudo update-rc.d vboxcontrol defaults 99 10

Damit wir in Virtualbox gleich loslegen können, starten wir das Skript durch direkten Aufruf mit

sudo /etc/init.d/vboxcontrol start

Bei der virtuellen Maschine passen wir unter dem Punkt Netzwerk folgende Punkte an:
Angeschlossen an: Hostinterface
Name des Interfaces: tap0 (also eines der Interfaces, welches oben unter TAPS angegeben wurde)

Sehr praktisch an dem vboxcontrol Skript ist, dass beim Herunterfahren des Systems oder dem Aufruf des Skripts mit dem Parameter stop alle laufenden virtuellen Maschinen ordnungsgemäß heruntergefahren werden. Das Wort alle habe ich deswegen kursiv hervorgehoben, da nicht nur die
/etc/virtualbox/machines_enabled gelisteten Maschinen heruntergefahren werden.