diff mbox series

[PATCHv4,2/3] runqemu: Add support for multiple tap devices

Message ID 20190703170223.6228-2-anibal.limon@linaro.org
State New
Headers show
Series [PATCHv4,1/3] runqemu: Allow to store more than one lock for network interfaces | expand

Commit Message

Anibal Limon July 3, 2019, 5:02 p.m. UTC
Add the ability to set more than one tap devices into the
same qemu instance,

The code was modified to detect multiple @TAP@ and @MAC@ in the
QA_TAP_OPT and QA_NETWORK_DEVICE respectively, it handles the
attach/creation of multiple tap devices and stores into a list
for lock/unlock.

Configure the first interface because Kernel IP Configuration
only takes care of the first device.

This patch was tested using qemux86, kvm-vhost and NFS rootfs.

Example of the configuration:

QB_TAP_OPT = "-netdev tap,id=net0,ifname=@TAP@,script=no,downscript=no \
              -netdev tap,id=net1,ifname=@TAP@,script=no,downscript=no"
QB_NETWORK_DEVICE = "-device virtio-net-pci,netdev=net0,mac=@MAC@ \
                     -device virtio-net-pci,netdev=net1,mac=@MAC@"

Signed-off-by: Aníbal Limón <anibal.limon@linaro.org>
---
 scripts/runqemu | 141 ++++++++++++++++++++++++++++--------------------
 1 file changed, 83 insertions(+), 58 deletions(-)

Comments

Anibal Limon July 3, 2019, 5:05 p.m. UTC | #1
On Wed, 3 Jul 2019 at 12:01, Aníbal Limón <anibal.limon@linaro.org> wrote:

> Add the ability to set more than one tap devices into the

> same qemu instance,

>

> The code was modified to detect multiple @TAP@ and @MAC@ in the

> QA_TAP_OPT and QA_NETWORK_DEVICE respectively, it handles the

> attach/creation of multiple tap devices and stores into a list

> for lock/unlock.

>

> Configure the first interface because Kernel IP Configuration

> only takes care of the first device.

>

> This patch was tested using qemux86, kvm-vhost and NFS rootfs.

>

> Example of the configuration:

>

> QB_TAP_OPT = "-netdev tap,id=net0,ifname=@TAP@,script=no,downscript=no \

>               -netdev tap,id=net1,ifname=@TAP@,script=no,downscript=no"

> QB_NETWORK_DEVICE = "-device virtio-net-pci,netdev=net0,mac=@MAC@ \

>                      -device virtio-net-pci,netdev=net1,mac=@MAC@"

>

> Signed-off-by: Aníbal Limón <anibal.limon@linaro.org>

> ---

>  scripts/runqemu | 141 ++++++++++++++++++++++++++++--------------------

>  1 file changed, 83 insertions(+), 58 deletions(-)

>

> diff --git a/scripts/runqemu b/scripts/runqemu

> index 38dd1c30d9..0134f86b4c 100755

> --- a/scripts/runqemu

> +++ b/scripts/runqemu

> @@ -1006,64 +1006,88 @@ class BaseConfig(object):

>              except FileExistsError:

>                  pass

>

> -        cmd = (ip, 'link')

> -        logger.debug('Running %s...' % str(cmd))

> -        ip_link = subprocess.check_output(cmd).decode('utf-8')

> -        # Matches line like: 6: tap0: <foo>

> -        possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link,

> re.M)

> -        tap = ""

> -        for p in possibles:

> -            lockfile = os.path.join(lockdir, p)

> -            if os.path.exists('%s.skip' % lockfile):

> -                logger.info('Found %s.skip, skipping %s' % (lockfile, p))

> -                continue

> -            lock = lockfile + '.lock'

> -            if self.acquire_lock(lock, error=False):

> -                tap = p

> -                logger.info("Using preconfigured tap device %s" % tap)

> -                logger.info("If this is not intended, touch %s.skip to

> make runqemu skip %s." %(lockfile, tap))

> -                break

> -

> -        if not tap:

> -            if os.path.exists(nosudo_flag):

> -                logger.error("Error: There are no available tap devices

> to use for networking,")

> -                logger.error("and I see %s exists, so I am not going to

> try creating" % nosudo_flag)

> -                raise RunQemuError("a new one with sudo.")

> -

> -            gid = os.getgid()

> -            uid = os.getuid()

> -            logger.info("Setting up tap interface under sudo")

> -            cmd = ('sudo', self.qemuifup, str(uid), str(gid),

> self.bindir_native)

> -            tap = subprocess.check_output(cmd).decode('utf-8').strip()

> -            lockfile = os.path.join(lockdir, tap)

> -            lock = lockfile + '.lock'

> -            self.acquire_lock(lock)

> -            self.cleantap = True

> -            logger.debug('Created tap: %s' % tap)

> -

> -        if not tap:

> -            logger.error("Failed to setup tap device. Run

> runqemu-gen-tapdevs to manually create.")

> -            return 1

> -        self.tap = tap

> -        tapnum = int(tap[3:])

> -        gateway = tapnum * 2 + 1

> -        client = gateway + 1

> -        if self.fstype == 'nfs':

> -            self.setup_nfs()

> -        netconf = "192.168.7.%s::192.168.7.%s:255.255.255.0" % (client,

> gateway)

> -        logger.info("Network configuration: %s", netconf)

> -        self.kernel_cmdline_script += " ip=%s" % netconf

> -        mac = "%s%02x" % (self.mac_tap, client)

> -        qb_tap_opt = self.get('QB_TAP_OPT')

> -        if qb_tap_opt:

> -            qemu_tap_opt = qb_tap_opt.replace('@TAP@', tap)

> -        else:

> -            qemu_tap_opt = "-netdev

> tap,id=net0,ifname=%s,script=no,downscript=no" % (self.tap)

> +        self.taps = []

> +        qemu_tap_opt = self.get('QB_TAP_OPT')

> +        if not qemu_tap_opt:

> +            qemu_tap_opt = '-netdev tap,id=net0,ifname=@TAP@

> ,script=no,downscript=no'

>

>          if self.vhost_enabled:

> -            qemu_tap_opt += ',vhost=on'

> +            opts = []

> +            for tap_opt in qemu_tap_opt.split():

> +                if 'tap' in tap_opt:

> +                    tap_opt += ',vhost=on'

> +                    opts.append(tap_opt)

> +                else:

> +                    opts.append(tap_opt)

> +            qemu_tap_opt = ' '.join(opts)

> +

> +        tap_no = qemu_tap_opt.count('@TAP@')

> +        for tap_idx in range(tap_no):

> +            cmd = (ip, 'link')

> +            logger.debug('Running %s...' % str(cmd))

> +            ip_link = subprocess.check_output(cmd).decode('utf-8')

> +            # Matches line like: 6: tap0: <foo>

> +            possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link,

> re.M)

> +            tap = ""

> +            for p in possibles:

> +                if p in self.taps:

> +                    continue

> +

> +                lockfile = os.path.join(lockdir, p)

> +                if os.path.exists('%s.skip' % lockfile):

> +                    logger.info('Found %s.skip, skipping %s' %

> (lockfile, p))

> +                    continue

> +                lock = lockfile + '.lock'

> +                if self.acquire_lock(lock, error=False):

> +                    tap = p

> +                    logger.info("Using preconfigured tap device %s" %

> tap)

> +                    logger.info("If this is not intended, touch %s.skip

> to make runqemu skip %s." %(lockfile, tap))

> +                    break

>

> -        self.set('NETWORK_CMD', '%s %s' %

> (self.network_device.replace('@MAC@', mac), qemu_tap_opt))

> +            if not tap:

> +                if os.path.exists(nosudo_flag):

> +                    logger.error("Error: There are no available tap

> devices to use for networking,")

> +                    logger.error("and I see %s exists, so I am not going

> to try creating" % nosudo_flag)

> +                    raise RunQemuError("a new one with sudo.")

> +

> +                gid = os.getgid()

> +                uid = os.getuid()

> +                logger.info("Setting up tap interface under sudo")

> +                cmd = ('sudo', self.qemuifup, str(uid), str(gid),

> self.bindir_native)

> +                tap = subprocess.check_output(cmd).decode('utf-8').strip()

> +                lockfile = os.path.join(lockdir, tap)

> +                lock = lockfile + '.lock'

> +                self.acquire_lock(lock)

> +                self.cleantap = True

> +                logger.info('Created tap: %s' % tap)

> +

> +            if not tap:

> +                logger.error("Failed to setup tap device. Run

> runqemu-gen-tapdevs to manually create.")

> +                return 1

> +            self.taps.append(tap)

> +            tapnum = int(tap[3:])

> +            gateway = tapnum * 2 + 1

> +            client = gateway + 1

> +

>


Hi Chen,

For some reason Linux isn't not configuring the NIC when eth0 is specified
in the cmdline for qemuppc and qemuarm, I added this workaround
that not pass ethN to ip= in kernel cmdline when only one tap device is
requested.

Regards,
Anibal


> +            # XXX: Linux qemuarm and qemuppc dosen't configure the

> interface

> +            # if device is specified in ip (ethN), so if only one tap

> device is

> +            # requested don't specify ethN.

> +            if tap_no == 1:

> +                netconf = "192.168.7.%s::192.168.7.%s:255.255.255.0" %

> (client, gateway)

> +                logger.info("Network configuration: %s", netconf)

> +                self.kernel_cmdline_script += " ip=%s" % netconf

> +            elif tap_idx == 0:

> +                netconf = "192.168.7.%s::192.168.7.%s:255.255.255.0::eth%d"

> % (client, gateway, tap_idx)

> +                logger.info("Network configuration: %s", netconf)

> +                self.kernel_cmdline_script += " ip=%s" % netconf

> +

> +            mac = "%s%02x" % (self.mac_tap, client)

> +            qemu_tap_opt = qemu_tap_opt.replace('@TAP@', tap, 1)

> +            self.network_device = self.network_device.replace('@MAC@',

> mac, 1)

> +

> +        self.set('NETWORK_CMD', '%s %s' % (self.network_device,

> qemu_tap_opt))

> +        if self.fstype == 'nfs':

> +            self.setup_nfs()

>

>      def setup_network(self):

>          if self.get('QB_NET') == 'none':

> @@ -1289,9 +1313,10 @@ class BaseConfig(object):

>

>          logger.info("Cleaning up")

>          if self.cleantap:

> -            cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native)

> -            logger.debug('Running %s' % str(cmd))

> -            subprocess.check_call(cmd)

> +            for tap in self.taps:

> +                cmd = ('sudo', self.qemuifdown, tap, self.bindir_native)

> +                logger.debug('Running %s' % str(cmd))

> +                subprocess.check_call(cmd)

>          for lock in self.lock_descriptors.keys():

>              self.release_lock(lock)

>

> --

> 2.20.1

>

>
<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, 3 Jul 2019 at 12:01, Aníbal Limón &lt;<a href="mailto:anibal.limon@linaro.org">anibal.limon@linaro.org</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Add the ability to set more than one tap devices into the<br>
same qemu instance,<br>
<br>
The code was modified to detect multiple @TAP@ and @MAC@ in the<br>
QA_TAP_OPT and QA_NETWORK_DEVICE respectively, it handles the<br>
attach/creation of multiple tap devices and stores into a list<br>
for lock/unlock.<br>
<br>
Configure the first interface because Kernel IP Configuration<br>
only takes care of the first device.<br>
<br>
This patch was tested using qemux86, kvm-vhost and NFS rootfs.<br>
<br>
Example of the configuration:<br>
<br>
QB_TAP_OPT = &quot;-netdev tap,id=net0,ifname=@TAP@,script=no,downscript=no \<br>
              -netdev tap,id=net1,ifname=@TAP@,script=no,downscript=no&quot;<br>
QB_NETWORK_DEVICE = &quot;-device virtio-net-pci,netdev=net0,mac=@MAC@ \<br>
                     -device virtio-net-pci,netdev=net1,mac=@MAC@&quot;<br>
<br>
Signed-off-by: Aníbal Limón &lt;<a href="mailto:anibal.limon@linaro.org" target="_blank">anibal.limon@linaro.org</a>&gt;<br>

---<br>
 scripts/runqemu | 141 ++++++++++++++++++++++++++++--------------------<br>
 1 file changed, 83 insertions(+), 58 deletions(-)<br>
<br>
diff --git a/scripts/runqemu b/scripts/runqemu<br>
index 38dd1c30d9..0134f86b4c 100755<br>
--- a/scripts/runqemu<br>
+++ b/scripts/runqemu<br>
@@ -1006,64 +1006,88 @@ class BaseConfig(object):<br>
             except FileExistsError:<br>
                 pass<br>
<br>
-        cmd = (ip, &#39;link&#39;)<br>
-        logger.debug(&#39;Running %s...&#39; % str(cmd))<br>
-        ip_link = subprocess.check_output(cmd).decode(&#39;utf-8&#39;)<br>
-        # Matches line like: 6: tap0: &lt;foo&gt;<br>
-        possibles = re.findall(&#39;^[0-9]+: +(tap[0-9]+): &lt;.*&#39;, ip_link, re.M)<br>
-        tap = &quot;&quot;<br>
-        for p in possibles:<br>
-            lockfile = os.path.join(lockdir, p)<br>
-            if os.path.exists(&#39;%s.skip&#39; % lockfile):<br>
-                <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&#39;Found %s.skip, skipping %s&#39; % (lockfile, p))<br>
-                continue<br>
-            lock = lockfile + &#39;.lock&#39;<br>
-            if self.acquire_lock(lock, error=False):<br>
-                tap = p<br>
-                <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Using preconfigured tap device %s&quot; % tap)<br>
-                <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;If this is not intended, touch %s.skip to make runqemu skip %s.&quot; %(lockfile, tap))<br>
-                break<br>
-<br>
-        if not tap:<br>
-            if os.path.exists(nosudo_flag):<br>
-                logger.error(&quot;Error: There are no available tap devices to use for networking,&quot;)<br>
-                logger.error(&quot;and I see %s exists, so I am not going to try creating&quot; % nosudo_flag)<br>
-                raise RunQemuError(&quot;a new one with sudo.&quot;)<br>
-<br>
-            gid = os.getgid()<br>
-            uid = os.getuid()<br>
-            <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Setting up tap interface under sudo&quot;)<br>
-            cmd = (&#39;sudo&#39;, self.qemuifup, str(uid), str(gid), self.bindir_native)<br>
-            tap = subprocess.check_output(cmd).decode(&#39;utf-8&#39;).strip()<br>
-            lockfile = os.path.join(lockdir, tap)<br>
-            lock = lockfile + &#39;.lock&#39;<br>
-            self.acquire_lock(lock)<br>
-            self.cleantap = True<br>
-            logger.debug(&#39;Created tap: %s&#39; % tap)<br>
-<br>
-        if not tap:<br>
-            logger.error(&quot;Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.&quot;)<br>
-            return 1<br>
-        self.tap = tap<br>
-        tapnum = int(tap[3:])<br>
-        gateway = tapnum * 2 + 1<br>
-        client = gateway + 1<br>
-        if self.fstype == &#39;nfs&#39;:<br>
-            self.setup_nfs()<br>
-        netconf = &quot;192.168.7.%s::192.168.7.%s:255.255.255.0&quot; % (client, gateway)<br>
-        <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Network configuration: %s&quot;, netconf)<br>
-        self.kernel_cmdline_script += &quot; ip=%s&quot; % netconf<br>
-        mac = &quot;%s%02x&quot; % (self.mac_tap, client)<br>
-        qb_tap_opt = self.get(&#39;QB_TAP_OPT&#39;)<br>
-        if qb_tap_opt:<br>
-            qemu_tap_opt = qb_tap_opt.replace(&#39;@TAP@&#39;, tap)<br>
-        else:<br>
-            qemu_tap_opt = &quot;-netdev tap,id=net0,ifname=%s,script=no,downscript=no&quot; % (self.tap)<br>
+        self.taps = []<br>
+        qemu_tap_opt = self.get(&#39;QB_TAP_OPT&#39;)<br>
+        if not qemu_tap_opt:<br>
+            qemu_tap_opt = &#39;-netdev tap,id=net0,ifname=@TAP@,script=no,downscript=no&#39;<br>
<br>
         if self.vhost_enabled:<br>
-            qemu_tap_opt += &#39;,vhost=on&#39;<br>
+            opts = []<br>
+            for tap_opt in qemu_tap_opt.split():<br>
+                if &#39;tap&#39; in tap_opt:<br>
+                    tap_opt += &#39;,vhost=on&#39;<br>
+                    opts.append(tap_opt)<br>
+                else:<br>
+                    opts.append(tap_opt)<br>
+            qemu_tap_opt = &#39; &#39;.join(opts)<br>
+<br>
+        tap_no = qemu_tap_opt.count(&#39;@TAP@&#39;)<br>
+        for tap_idx in range(tap_no):<br>
+            cmd = (ip, &#39;link&#39;)<br>
+            logger.debug(&#39;Running %s...&#39; % str(cmd))<br>
+            ip_link = subprocess.check_output(cmd).decode(&#39;utf-8&#39;)<br>
+            # Matches line like: 6: tap0: &lt;foo&gt;<br>
+            possibles = re.findall(&#39;^[0-9]+: +(tap[0-9]+): &lt;.*&#39;, ip_link, re.M)<br>
+            tap = &quot;&quot;<br>
+            for p in possibles:<br>
+                if p in self.taps:<br>
+                    continue<br>
+<br>
+                lockfile = os.path.join(lockdir, p)<br>
+                if os.path.exists(&#39;%s.skip&#39; % lockfile):<br>
+                    <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&#39;Found %s.skip, skipping %s&#39; % (lockfile, p))<br>
+                    continue<br>
+                lock = lockfile + &#39;.lock&#39;<br>
+                if self.acquire_lock(lock, error=False):<br>
+                    tap = p<br>
+                    <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Using preconfigured tap device %s&quot; % tap)<br>
+                    <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;If this is not intended, touch %s.skip to make runqemu skip %s.&quot; %(lockfile, tap))<br>
+                    break<br>
<br>
-        self.set(&#39;NETWORK_CMD&#39;, &#39;%s %s&#39; % (self.network_device.replace(&#39;@MAC@&#39;, mac), qemu_tap_opt))<br>
+            if not tap:<br>
+                if os.path.exists(nosudo_flag):<br>
+                    logger.error(&quot;Error: There are no available tap devices to use for networking,&quot;)<br>
+                    logger.error(&quot;and I see %s exists, so I am not going to try creating&quot; % nosudo_flag)<br>
+                    raise RunQemuError(&quot;a new one with sudo.&quot;)<br>
+<br>
+                gid = os.getgid()<br>
+                uid = os.getuid()<br>
+                <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Setting up tap interface under sudo&quot;)<br>
+                cmd = (&#39;sudo&#39;, self.qemuifup, str(uid), str(gid), self.bindir_native)<br>
+                tap = subprocess.check_output(cmd).decode(&#39;utf-8&#39;).strip()<br>
+                lockfile = os.path.join(lockdir, tap)<br>
+                lock = lockfile + &#39;.lock&#39;<br>
+                self.acquire_lock(lock)<br>
+                self.cleantap = True<br>
+                <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&#39;Created tap: %s&#39; % tap)<br>
+<br>
+            if not tap:<br>
+                logger.error(&quot;Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.&quot;)<br>
+                return 1<br>
+            self.taps.append(tap)<br>
+            tapnum = int(tap[3:])<br>
+            gateway = tapnum * 2 + 1<br>
+            client = gateway + 1<br>
+<br></blockquote><div><br></div><div>Hi Chen,</div><div><br></div><div>For some reason Linux isn&#39;t not configuring the NIC when eth0 is specified in the cmdline for qemuppc and qemuarm, I added this workaround</div><div>that not pass ethN to ip= in kernel cmdline when only one tap device is requested.</div><div><br></div><div>Regards,</div><div>Anibal</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+            # XXX: Linux qemuarm and qemuppc dosen&#39;t configure the interface <br>
+            # if device is specified in ip (ethN), so if only one tap device is<br>
+            # requested don&#39;t specify ethN.<br>
+            if tap_no == 1:<br>
+                netconf = &quot;192.168.7.%s::192.168.7.%s:255.255.255.0&quot; % (client, gateway)<br>
+                <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Network configuration: %s&quot;, netconf)<br>
+                self.kernel_cmdline_script += &quot; ip=%s&quot; % netconf<br>
+            elif tap_idx == 0:<br>
+                netconf = &quot;192.168.7.%s::192.168.7.%s:255.255.255.0::eth%d&quot; % (client, gateway, tap_idx)<br>
+                <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Network configuration: %s&quot;, netconf)<br>
+                self.kernel_cmdline_script += &quot; ip=%s&quot; % netconf<br>
+<br>
+            mac = &quot;%s%02x&quot; % (self.mac_tap, client)<br>
+            qemu_tap_opt = qemu_tap_opt.replace(&#39;@TAP@&#39;, tap, 1)<br>
+            self.network_device = self.network_device.replace(&#39;@MAC@&#39;, mac, 1)<br>
+<br>
+        self.set(&#39;NETWORK_CMD&#39;, &#39;%s %s&#39; % (self.network_device, qemu_tap_opt))<br>
+        if self.fstype == &#39;nfs&#39;:<br>
+            self.setup_nfs()<br>
<br>
     def setup_network(self):<br>
         if self.get(&#39;QB_NET&#39;) == &#39;none&#39;:<br>
@@ -1289,9 +1313,10 @@ class BaseConfig(object):<br>
<br>
         <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(&quot;Cleaning up&quot;)<br>
         if self.cleantap:<br>
-            cmd = (&#39;sudo&#39;, self.qemuifdown, self.tap, self.bindir_native)<br>
-            logger.debug(&#39;Running %s&#39; % str(cmd))<br>
-            subprocess.check_call(cmd)<br>
+            for tap in self.taps:<br>
+                cmd = (&#39;sudo&#39;, self.qemuifdown, tap, self.bindir_native)<br>
+                logger.debug(&#39;Running %s&#39; % str(cmd))<br>
+                subprocess.check_call(cmd)<br>
         for lock in self.lock_descriptors.keys():<br>
             self.release_lock(lock)<br>
<br>
-- <br>
2.20.1<br>
<br>
</blockquote></div></div>
-- 
_______________________________________________
Openembedded-core mailing list
Openembedded-core@lists.openembedded.org
http://lists.openembedded.org/mailman/listinfo/openembedded-core
diff mbox series

Patch

diff --git a/scripts/runqemu b/scripts/runqemu
index 38dd1c30d9..0134f86b4c 100755
--- a/scripts/runqemu
+++ b/scripts/runqemu
@@ -1006,64 +1006,88 @@  class BaseConfig(object):
             except FileExistsError:
                 pass
 
-        cmd = (ip, 'link')
-        logger.debug('Running %s...' % str(cmd))
-        ip_link = subprocess.check_output(cmd).decode('utf-8')
-        # Matches line like: 6: tap0: <foo>
-        possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M)
-        tap = ""
-        for p in possibles:
-            lockfile = os.path.join(lockdir, p)
-            if os.path.exists('%s.skip' % lockfile):
-                logger.info('Found %s.skip, skipping %s' % (lockfile, p))
-                continue
-            lock = lockfile + '.lock'
-            if self.acquire_lock(lock, error=False):
-                tap = p
-                logger.info("Using preconfigured tap device %s" % tap)
-                logger.info("If this is not intended, touch %s.skip to make runqemu skip %s." %(lockfile, tap))
-                break
-
-        if not tap:
-            if os.path.exists(nosudo_flag):
-                logger.error("Error: There are no available tap devices to use for networking,")
-                logger.error("and I see %s exists, so I am not going to try creating" % nosudo_flag)
-                raise RunQemuError("a new one with sudo.")
-
-            gid = os.getgid()
-            uid = os.getuid()
-            logger.info("Setting up tap interface under sudo")
-            cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native)
-            tap = subprocess.check_output(cmd).decode('utf-8').strip()
-            lockfile = os.path.join(lockdir, tap)
-            lock = lockfile + '.lock'
-            self.acquire_lock(lock)
-            self.cleantap = True
-            logger.debug('Created tap: %s' % tap)
-
-        if not tap:
-            logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
-            return 1
-        self.tap = tap
-        tapnum = int(tap[3:])
-        gateway = tapnum * 2 + 1
-        client = gateway + 1
-        if self.fstype == 'nfs':
-            self.setup_nfs()
-        netconf = "192.168.7.%s::192.168.7.%s:255.255.255.0" % (client, gateway)
-        logger.info("Network configuration: %s", netconf)
-        self.kernel_cmdline_script += " ip=%s" % netconf
-        mac = "%s%02x" % (self.mac_tap, client)
-        qb_tap_opt = self.get('QB_TAP_OPT')
-        if qb_tap_opt:
-            qemu_tap_opt = qb_tap_opt.replace('@TAP@', tap)
-        else:
-            qemu_tap_opt = "-netdev tap,id=net0,ifname=%s,script=no,downscript=no" % (self.tap)
+        self.taps = []
+        qemu_tap_opt = self.get('QB_TAP_OPT')
+        if not qemu_tap_opt:
+            qemu_tap_opt = '-netdev tap,id=net0,ifname=@TAP@,script=no,downscript=no'
 
         if self.vhost_enabled:
-            qemu_tap_opt += ',vhost=on'
+            opts = []
+            for tap_opt in qemu_tap_opt.split():
+                if 'tap' in tap_opt:
+                    tap_opt += ',vhost=on'
+                    opts.append(tap_opt)
+                else:
+                    opts.append(tap_opt)
+            qemu_tap_opt = ' '.join(opts)
+
+        tap_no = qemu_tap_opt.count('@TAP@')
+        for tap_idx in range(tap_no):
+            cmd = (ip, 'link')
+            logger.debug('Running %s...' % str(cmd))
+            ip_link = subprocess.check_output(cmd).decode('utf-8')
+            # Matches line like: 6: tap0: <foo>
+            possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M)
+            tap = ""
+            for p in possibles:
+                if p in self.taps:
+                    continue
+
+                lockfile = os.path.join(lockdir, p)
+                if os.path.exists('%s.skip' % lockfile):
+                    logger.info('Found %s.skip, skipping %s' % (lockfile, p))
+                    continue
+                lock = lockfile + '.lock'
+                if self.acquire_lock(lock, error=False):
+                    tap = p
+                    logger.info("Using preconfigured tap device %s" % tap)
+                    logger.info("If this is not intended, touch %s.skip to make runqemu skip %s." %(lockfile, tap))
+                    break
 
-        self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt))
+            if not tap:
+                if os.path.exists(nosudo_flag):
+                    logger.error("Error: There are no available tap devices to use for networking,")
+                    logger.error("and I see %s exists, so I am not going to try creating" % nosudo_flag)
+                    raise RunQemuError("a new one with sudo.")
+
+                gid = os.getgid()
+                uid = os.getuid()
+                logger.info("Setting up tap interface under sudo")
+                cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native)
+                tap = subprocess.check_output(cmd).decode('utf-8').strip()
+                lockfile = os.path.join(lockdir, tap)
+                lock = lockfile + '.lock'
+                self.acquire_lock(lock)
+                self.cleantap = True
+                logger.info('Created tap: %s' % tap)
+
+            if not tap:
+                logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
+                return 1
+            self.taps.append(tap)
+            tapnum = int(tap[3:])
+            gateway = tapnum * 2 + 1
+            client = gateway + 1
+
+            # XXX: Linux qemuarm and qemuppc dosen't configure the interface 
+            # if device is specified in ip (ethN), so if only one tap device is
+            # requested don't specify ethN.
+            if tap_no == 1:
+                netconf = "192.168.7.%s::192.168.7.%s:255.255.255.0" % (client, gateway)
+                logger.info("Network configuration: %s", netconf)
+                self.kernel_cmdline_script += " ip=%s" % netconf
+            elif tap_idx == 0:
+                netconf = "192.168.7.%s::192.168.7.%s:255.255.255.0::eth%d" % (client, gateway, tap_idx)
+                logger.info("Network configuration: %s", netconf)
+                self.kernel_cmdline_script += " ip=%s" % netconf
+
+            mac = "%s%02x" % (self.mac_tap, client)
+            qemu_tap_opt = qemu_tap_opt.replace('@TAP@', tap, 1)
+            self.network_device = self.network_device.replace('@MAC@', mac, 1)
+
+        self.set('NETWORK_CMD', '%s %s' % (self.network_device, qemu_tap_opt))
+        if self.fstype == 'nfs':
+            self.setup_nfs()
 
     def setup_network(self):
         if self.get('QB_NET') == 'none':
@@ -1289,9 +1313,10 @@  class BaseConfig(object):
 
         logger.info("Cleaning up")
         if self.cleantap:
-            cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native)
-            logger.debug('Running %s' % str(cmd))
-            subprocess.check_call(cmd)
+            for tap in self.taps:
+                cmd = ('sudo', self.qemuifdown, tap, self.bindir_native)
+                logger.debug('Running %s' % str(cmd))
+                subprocess.check_call(cmd)
         for lock in self.lock_descriptors.keys():
             self.release_lock(lock)