Article List

Notes on qemu-system


Sat Mar 24 2018 07:45:15 GMT+0900 (일본 표준시)
developmentnotespythonqemu-system

Introduction

This is my personal note for the qemu-system. I decided to write this post in order to shorten my time to make and use the qemu image.

Recently, I had to use QEMU for my personal project and there are too many interesting(and useless) features to know and I can't even catch up countless resources swimming on the internet.

HONESTLY, I HAD TO WRITE THIS POST. I DON'T WANT TO TAKE ALL DAY LEARNING UP THE MANUAL.

I hope this post helps other people who are planning to use the qemu-system for the first time.

Installing OS on qemu through SSH

I'm going to use latest debian image in this example.

The following steps will be valid even on future releases. (yes I'm certain!)


  1. Download an installation image wget https://cdimage.debian.org/debian-cd/current/i386/iso-cd/debian-9.4.0-i386-netinst.iso
  2. Create a new image (in qcow2 format) qemu-img create -f qcow2 debian.qcow 5G
  3. Run qemu-system qemu-system-i386 -cdrom debian-9.4.0-i386-netinst.iso -hda debian.qcow -m 1G -boot d -curses
  4. Press ESC button
  5. CLI mode is spawned. Now, Boot with the option install vga=normal fb=false

*The installation process took a lot of time depending on the RAM size given. Installer gets into the low memory mode when QEMU is opened without the additional argument.*

After the installation, boot with -nographic to disable the monitor and remove -boot d, -curses and -cdrom arguments to make the system fully functional.

Host Portforward and Networking tips

Assuming that you want the Host's port 1234 forwarded to VM's port 5555, the most preferred command for you will be -redir tcp:1234::5555.

if you don't want to plan to open your port worldwide, the problem can be solve with tcp:127.0.0.1:1234-:5555.

However, the manual says that the -redir option is deprecated (as of 2018) and will be removed soon.

The qemu-system manual suggests that using -netdev user is highly encouraged.

So the encouraged method should be using arguments like the following:

qemu-system-i386 -netdev user,hostfwd=tcp:1234::1337,id=alicenet,hostname=alice,net=1.3.3.0/24 -device e1000,netdev=alicenet ...

Assigning the MAC address

Setting MAC address is very easy. you just need to add one more option on the -device argument.

To assign a specific MAC address, You can write -device e1000,netdev=alicenet,mac=11:22:33:44:55:66.

Remember, the -device option should be used with -netdev option to make a functional networking interface.

Nested virtualization

It works, but generally it's not preferred and generally not recommneded.

Please don't use the nested virtualization on AWS, conoha or digitalocean.

You can get suspended/banned by the hosting company. It's better to install QEMU and use them on barebone servers.

Please don't even try qemu-system on VPS. you have been warned.

Snapshot

You can make multiple snapshots on qcow2 format and rewind features seems to be available.

But if you're planning to run the current image multiple times and don't want the changes to be commited at all, you can just go ahead with the -snapshot feature.

-snapshot basically keeps your original image and changes done will be written on a temporary space.

Summary

From the tips above, you can now use qemu-system like this:

bash
qemu-system-x86_64 -nographic -snapshot \
-hda image.qcow2 -m 512 \
-netdev user,id=alicenet,hostname=alice,net=172.10.1.0/24 \
-device e1000,netdev=alicenet,mac=$(openssl rand -hex 6 | sed 's/\(..\)/\1:/g; s/.$//')

The command above boots image.qcow2 with 512MB memory space without the graphic, having a network card assigning random MAC address on every run and an IP allocated on 172.10.1.0/24.

Sample Script: QEMU Runner

The following script runs multiple qemu-system-x86_64 instances with different ports assigned respectively. When one of these instances is killed or terminated, the runner starts another QEMU instances, until the user kills the runner.

You can customize the script to develop it further. In my project, I also made the status checker for running qemu instances.

It's not pythonic, and I know it. I don't have enough time to sincerely develop a lot of code. Please consider this one as a concept script.

If you like it, go refactor and customize the code! 😉

Tested on Ubuntu 17.10.

python
#!/usr/bin/python -u
#-*- coding:utf-8 -*-
# Developer: Harold Kim ([email protected])

import time
import os
import sys
import random

class QEMURunner:
    ''' QEMU runner class for debian/ubuntu '''
    qemu_binary = '/usr/bin/qemu-system-x86_64'
    qemu_opt = " -snapshot -nographic -hda image.qcow2 -m 256 "
    qemu_opt += "-netdev user,hostfwd=tcp:127.0.0.1:%s-:8080,id=vnet%s,hostname=vulnhost%s,net=10.1.3.0/24 "
    qemu_opt += "-device e1000,netdev=vnet%s"
    qemu_opt += ",mac=%s &"
    #-redir [tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport

    def __init__(self, maximum_instance):
        self.maximum_instance_count = maximum_instance
        self.kill() # reset instances
        self.running_instance = []

    def count(self):
        ''' count running (ret int) '''
        c = os.popen("ps aux | grep %s | grep -v grep" % (self.qemu_binary,)).read()
        c = c.split("\n")[:-1]
        return len(c)

    def kill(self, ps=None):
        if ps is None:
            c = os.popen("killall -9 %s 2>/dev/null" % (self.qemu_binary,)).read()
        else:
            c = os.popen("ps aux | grep %s | grep %s | grep -v grep | awk '{print $2}'" % (self.qemu_binary, ps,)).read()

    def run(self):
        c = 0
        while self.count() < self.maximum_instance_count:
            c += 1
            count = self.count()
            # generate MAC
            mac = [ random.randint(0, 255) for x in range(0, 5) ]
            mac[0] = (mac[0] & 0xfc) | 0x02
            mac = '37:' + (':'.join(['{0:02x}'.format(x) for x in mac ]))
            # port starts from 2300. (e.g. 2301, 2302, 2303, ...)
            port = str(2301 + c)
            run = self.qemu_binary + self.qemu_opt % (port, count, count, count, mac)
            os.system(run)
            self.running_instance.append(mac)
            time.sleep(1)
        print(self.count())
        return True

    def run_infinite(self):
        while True:
            self.run()
            time.sleep(5)

if __name__ == "__main__":
    # Run 3 QEMU instances, forever.
    qemu = QEMURunner(3)
    qemu.run_infinite()

Added docker runner which does similar thing for docker: https://gist.github.com/stypr/ffe28c8262a8a362be0fd7b1558dc2b4