29 Jan 2015

Qemu Networking Investigation - Details

Assuming you have a virtual machine on a disk image that you want to run in qemu such that:
  • the target can access the network at large from inside the target
  • you can access the target's network from the host
  • you don't want to assign a static IP within the image itself but you want to be able to flexibly set up the image on any sort of network (class A, class B, class C) with any IP address without having to make changes to the image itself
here are some notes on how to go about accomplishing these goals.

  • create the disk file
$ qemu-img create -f qcow2 myimage.qcow2 200G
  • install your favourite distribution into the disk file
$ qemu-system-x86_64 \
        -enable-kvm \
        -smp 2 \
        -cpu host \
        -m 4096 \
        -drive file=/.../myimage.qcow2,if=virtio \
        -net nic,model=virtio \
        -net user \
        -cdrom /.../openSUSE-Tumbleweed-NET-x86_64-Snapshot20150126-Media.iso
  • run the image
$ qemu-system-x86_64 \
        -enable-kvm \
        -cpu host \
        -smp 6 \
        -m 4096 \
        -net nic,model=virtio \
        -net user \
        -drive file=/.../myimage.qcow2 \
        -nographic
  • tweak it to your preferences
edit /etc/default/grub, edit GRUB_CMDLINE_LINUX_DEFAULT:
          to add
                " console=ttyS0,115200"
          to remove
                "splash=silent quiet"
# grub2-mkconfig -o /boot/grub2/grub.cfg
configure /tmp for tmpfs, add the following to /etc/fstab:
          none /tmp tmpfs defaults,noatime 0 0
  • take a snapshot
$ qemu-img snapshot -c afterInstallAndConfig myimage.qcow2
  • create a configuration file named CONFIG
checkenv() {
        if [ -z "${!1}" ]; then
                echo "required env var '$1' not defined"
                exit 1
        fi
}

findcmd() {
        which $1 > /dev/null 2>&1
        if [ $? -ne 0 ]; then
                echo "can't find required binary: '$1'"
                exit 1
        fi
}

MACADDR=DE:AD:BE:EF:00:01
USERID=trevor
GROUPID=users
IPBASE=192.168.8.
HOSTIP=${IPBASE}1
  • create a "super script" called start_vm
#!/bin/bash

if [ $# -ne 1 ]; then
        echo "usage: $(basename $0) <image>"
        exit 1
fi

source CONFIG
checkenv MACADDR
checkenv USERID
checkenv GROUPID
checkenv IPBASE

THISDIR=$(pwd)
IMAGE=$1

TAPDEV=$(sudo $THISDIR/qemu-ifup)
if [ $? -ne 0 ]; then
        echo "qemu-ifup failed"
        exit 1
fi
echo "tap device: $TAPDEV"

qemu-system-x86_64 \
        -enable-kvm \
        -cpu host \
        -smp sockets=1,cores=2,threads=2 \
        -m 4096 \
        -drive file=$IMAGE,if=virtio \
        -net nic,model=virtio,macaddr=$MACADDR \
        -net tap,ifname=$TAPDEV,script=no,downscript=no \
        -nographic

sudo $THISDIR/qemu-ifdown $TAPDEV
  • create an "up" script called qemu-ifup
#!/bin/bash

source CONFIG

usage() {
        echo "sudo $(basename $0)"
}

checkenv USERID
checkenv GROUPID
checkenv IPBASE
checkenv HOSTIP

findcmd tunctl
findcmd ip
findcmd iptables
findcmd dnsmasq


if [ $EUID -ne 0 ]; then
        echo "Error: This script must be run with root privileges"
        exit 1
fi

if [ $# -ne 0 ]; then
        usage
        exit 1
fi

TAPDEV=$(tunctl -b -u $USERID -g $GROUPID 2>&1)
STATUS=$?
if [ $STATUS -ne 0 ]; then
        echo "tunctl failed:"
        exit 1
fi

ip addr add $HOSTIP/32 broadcast ${IPBASE}255 dev $TAPDEV
ip link set dev $TAPDEV up
ip route add ${IPBASE}0/24 dev $TAPDEV

# setup NAT for tap$n interface to have internet access in QEMU
iptables -t nat -A POSTROUTING -j MASQUERADE -s ${IPBASE}0/24
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/conf/$TAPDEV/proxy_arp
iptables -P FORWARD ACCEPT

# startup dnsmasq
dnsmasq \
        --strict-order \
        --except-interface=lo \
        --interface=$TAPDEV \
        --listen-address=$HOSTIP \
        --bind-interfaces \
        -d \
        -q \
        --dhcp-range=${TAPDEV},${IPBASE}5,${IPBASE}20,255.255.255.0,${IPBASE}255 \
        --conf-file="" \
        > dnsmasq.log 2>&1 &
echo $! > dnsmasq.pid

echo $TAPDEV
exit 0
  • create a "down" script called qemu-ifdown
#!/bin/bash

source CONFIG
checkenv IPBASE
findcmd tunctl
findcmd iptables

usage() {
        echo "sudo $(basename $0) <tap-dev>"
}

if [ $EUID -ne 0 ]; then
        echo "Error: This script (runqemu-ifdown) must be run with root privileges"
        exit 1
fi

if [ $# -ne 1 ]; then
        usage
        exit 1
fi

TAPDEV=$1
tunctl -d $TAPDEV

# cleanup the remaining iptables rules
iptables -t nat -D POSTROUTING -j MASQUERADE -s ${IPBASE}0/24

# kill dnsmasq
if [ -f dnsmasq.pid ]; then
        kill $(cat dnsmasq.pid)
        rm -f dnsmasq.pid
        rm -f dnsmasq.log
fi
  • ??
  • profit!
 This setup is driven by the CONFIG file and the start_vm script. Edit CONFIG to your liking, then run start_vm specifying your image file.

No comments: