[PATCH 2/4] qemu:net: Add TAP support for RX filtering on Linux

Alex Williamson
2009-02-10T21:31:10Z

The Linux tap driver provides an ioctl to set a TX filter. Setting
this restricts the packets received onto the vlan. We provide a
hotplug callback to clear the filter when a new device gets added.
The new rxfilter=off option can be used to disable exporting this
feature to backend drivers.

Signed-off-by: Alex Williamson <alex.williamson@hp.com>
---

net.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++------
qemu-doc.texi | 6 ++++--
vl.c | 3 ++-
3 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/net.c b/net.c
index e68cb40..973efea 100644
--- a/net.c
+++ b/net.c
@@ -724,6 +724,38 @@ static ssize_t tap_receive_iov(void *opaque, const struct iovec *iov,
}
#endif

+#ifdef TUNSETTXFILTER
+static int tap_rxfilter(void *opaque, unsigned int flags, int count,
+ uint8_t *list)
+{
+ TAPState *s = opaque;
+ struct tun_filter *filter;
+ int ret;
+
+ if (flags & QEMU_NET_PROMISC)
+ count = 0;
+
+ filter = qemu_mallocz(sizeof(*filter) + (count * ETH_ALEN));
+
+ memcpy(filter->addr, list, count * ETH_ALEN);
+ filter->count = count;
+
+ if (flags & QEMU_NET_ALLMULTI)
+ filter->flags |= TUN_FLT_ALLMULTI;
+
+ ret = ioctl(s->fd, TUNSETTXFILTER, filter);
+
+ qemu_free(filter);
+
+ return ret;
+}
+
+static void tap_vlan_client_added(void *opaque)
+{
+ tap_rxfilter(opaque, 0, 0, NULL);
+}
+#endif
+
static void tap_receive(void *opaque, const uint8_t *buf, int size)
{
TAPState *s = opaque;
@@ -762,7 +794,7 @@ static void tap_send(void *opaque)
static TAPState *net_tap_fd_init(VLANState *vlan,
const char *model,
const char *name,
- int fd)
+ int fd, int rxfilter)
{
TAPState *s;

@@ -772,6 +804,12 @@ static TAPState *net_tap_fd_init(VLANState *vlan,
#ifdef HAVE_IOVEC
s->vc->fd_readv = tap_receive_iov;
#endif
+#ifdef TUNSETTXFILTER
+ if (rxfilter) {
+ s->vc->fd_rxfilter = tap_rxfilter;
+ s->vc->vlan_client_added = tap_vlan_client_added;
+ }
+#endif
qemu_set_fd_handler(s->fd, tap_send, NULL, s);
snprintf(s->vc->info_str, sizeof(s->vc->info_str), "fd=%d", fd);
return s;
@@ -1005,7 +1043,8 @@ static int launch_script(const char *setup_script, const char *ifname, int fd)

static int net_tap_init(VLANState *vlan, const char *model,
const char *name, const char *ifname1,
- const char *setup_script, const char *down_script)
+ const char *setup_script, const char *down_script,
+ int rxfilter)
{
TAPState *s;
int fd;
@@ -1025,7 +1064,7 @@ static int net_tap_init(VLANState *vlan, const char *model,
if (launch_script(setup_script, ifname, fd))
return -1;
}
- s = net_tap_fd_init(vlan, model, name, fd);
+ s = net_tap_fd_init(vlan, model, name, fd, rxfilter);
if (!s)
return -1;
snprintf(s->vc->info_str, sizeof(s->vc->info_str),
@@ -1663,13 +1702,18 @@ int net_client_init(const char *device, const char *p)
if (!strcmp(device, "tap")) {
char ifname[64];
char setup_script[1024], down_script[1024];
- int fd;
+ int fd, rxfilter = 1;
vlan->nb_host_devs++;
+
+ if (get_param_value(buf, sizeof(buf), "rxfilter", p) > 0)
+ if (!strcmp(buf, "off"))
+ rxfilter = 0;
+
if (get_param_value(buf, sizeof(buf), "fd", p) > 0) {
fd = strtol(buf, NULL, 0);
fcntl(fd, F_SETFL, O_NONBLOCK);
ret = -1;
- if (net_tap_fd_init(vlan, device, name, fd))
+ if (net_tap_fd_init(vlan, device, name, fd, rxfilter))
ret = 0;
} else {
if (get_param_value(ifname, sizeof(ifname), "ifname", p) <= 0) {
@@ -1681,7 +1725,7 @@ int net_client_init(const char *device, const char *p)
if (get_param_value(down_script, sizeof(down_script), "downscript", p) == 0) {
pstrcpy(down_script, sizeof(down_script), DEFAULT_NETWORK_DOWN_SCRIPT);
}
- ret = net_tap_init(vlan, device, name, ifname, setup_script, down_script);
+ ret = net_tap_init(vlan, device, name, ifname, setup_script, down_script, rxfilter);
}
} else
#endif
diff --git a/qemu-doc.texi b/qemu-doc.texi
index efb88d2..9a3957c 100644
--- a/qemu-doc.texi
+++ b/qemu-doc.texi
@@ -643,7 +643,7 @@ Use the user mode network stack which requires no administrator
privilege to run. @option{hostname=name} can be used to specify the client
hostname reported by the builtin DHCP server.

-@item -net tap[,vlan=@var{n}][,name=@var{name}][,fd=@var{h}][,ifname=@var{name}][,script=@var{file}][,downscript=@var{dfile}]
+@item -net tap[,vlan=@var{n}][,name=@var{name}][,fd=@var{h}][,ifname=@var{name}][,script=@var{file}][,downscript=@var{dfile}][,rxfilter=on|off]
Connect the host TAP network interface @var{name} to VLAN @var{n}, use
the network script @var{file} to configure it and the network script
@var{dfile} to deconfigure it. If @var{name} is not provided, the OS
@@ -651,7 +651,9 @@ automatically provides one. @option{fd}=@var{h} can be used to specify
the handle of an already opened host TAP interface. The default network
configure script is @file{/etc/qemu-ifup} and the default network
deconfigure script is @file{/etc/qemu-ifdown}. Use @option{script=no}
-or @option{downscript=no} to disable script execution. Example:
+or @option{downscript=no} to disable script execution. The rxfilter
+(Linux-only) option enables or disables availability of the tap device
+MAC filtering (default on). Example:

@example
qemu linux.img -net nic -net tap
diff --git a/vl.c b/vl.c
index aff2b2c..96ab696 100644
--- a/vl.c
+++ b/vl.c
@@ -3909,12 +3909,13 @@ static void help(int exitcode)
"-net tap[,vlan=n][,name=str],ifname=name\n"
" connect the host TAP network interface to VLAN 'n'\n"
#else
- "-net tap[,vlan=n][,name=str][,fd=h][,ifname=name][,script=file][,downscript=dfile]\n"
+ "-net tap[,vlan=n][,name=str][,fd=h][,ifname=name][,script=file][,downscript=dfile][,rxfilter=on|off]\n"
" connect the host TAP network interface to VLAN 'n' and use the\n"
" network scripts 'file' (default=%s)\n"
" and 'dfile' (default=%s);\n"
" use '[down]script=no' to disable script execution;\n"
" use 'fd=h' to connect to an already opened TAP interface\n"
+ " rxfilter enables|disables use of the tap MAC filter (default on)\n"
#endif
"-net socket[,vlan=n][,name=str][,fd=h][,listen=[host]:port][,connect=host:port]\n"
" connect the vlan 'n' to another VLAN using a socket connection\n"