Tue Feb 14 16:02:23 IST 2007 Mark McLoughlin <markmc@redhat.com>

* configure.in: add --disable-bridge-params, check
        for libsysfs and various kernel headers

        * bridge.[ch]: add code for managing bridges

        * qemud/Makefile.am: add bridge.[ch] and link against
        libsysfs if enabled.

        * qemud/conf.c: add support for bridge config.

        * qemud/internal.h: add various bridging bits

        * qemud/qemud.c: implement qemudStartNetworkDaemon()
        and qemudShutdownNetworkDaemon().
This commit is contained in:
Mark McLoughlin 2007-02-14 16:02:40 +00:00
parent e018cbc74f
commit 4e6c38e10f
8 changed files with 1309 additions and 31 deletions

110
ChangeLog
View File

@ -1,16 +1,108 @@
Tue Feb 14 14:58:35 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 16:02:23 IST 2007 Mark McLoughlin <markmc@redhat.com>
* configure.in: add --disable-bridge-params, check
for libsysfs and various kernel headers
* bridge.[ch]: add code for managing bridges
* qemud/Makefile.am: add bridge.[ch] and link against
libsysfs if enabled.
* qemud/conf.c: add support for bridge config.
* qemud/internal.h: add various bridging bits
* qemud/qemud.c: implement qemudStartNetworkDaemon()
and qemudShutdownNetworkDaemon().
Tue Feb 14 15:55:02 IST 2007 Mark McLoughlin <markmc@redhat.com>
* qemud/conf.[ch]: implement parsing and saving network
configs.
* qemud/driver.c: flesh out the stubs
* qemud/internal.h: add networks list etc. to
struct qemud_server
* qemud/qemud.c: add qemudStartNetworkDaemon() and
qemudShutdownNetworkDaemon() stubs.
Tue Feb 14 15:52:34 EST 2007 Mark McLoughlin <markmc@redhat.com>
* qemud/protocol.h: add the protocol for virtual networks
* qemud/dispatch.c: implement the protocol
* qemud/driver.[ch]: add stubs for the driver
* qemud/internal.h: add struct qemud_network
* src/qemu_internal.c: add a virtual networks driver
Tue Feb 14 15:43:28 IST 2007 Mark McLoughlin <markmc@redhat.com>
* src/virsh.c: add the net-* commands.
Tue Feb 14 15:37:17 IST 2007 Mark McLoughlin <markmc@redhat.com>
Note: potential ABI break here, but people should
only really be using virError structs returned from
libvirt itself.
* include/libvirt/virterror.h: add virNetwork
to virError
* src/internal.h, src/virterror.c: add network param
to __virRaiseError()
* src/conf.c, src/hash.c, src/libvirt.c, src/proxy_internal.c,
src/qemu_internal.c, src/sexpr.c, src/test.c, src/xen_internal.c,
src/xend_internal.c, src/xm_internal.c, src/xml.c, src/xmlrpc.c,
src/xs_internal.c: update.
Tue Feb 14 15:33:05 IST 2007 Mark McLoughlin <markmc@redhat.com>
* include/libvirt/libvirt.h.in: add the networks APIs
* include/libvirt/virterror.h: add some error codes
* src/driver.h: add network driver vtable
* src/hash.c: add networks hash
* src/internal.h: add virNetwork
* src/libvirt.c: hook up the APIs to the network
driver
* src/libvirt_sym.version: add the new APIs
* src/virterror.c: handle the new error codes
Tue Feb 14 15:07:26 IST 2007 Mark McLoughlin <markmc@redhat.com>
* src/conf.h: fix merge error - remove the argc argument
from qemudBuildCommandLine()
Tue Feb 14 15:03:22 IST 2007 Mark McLoughlin <markmc@redhat.com>
* src/virsh.c: Re-name some of the VSH_DOMBYFOO stuff
to VSH_BYFOO in order to re-use it for the network stuff.
Tue Feb 14 14:58:35 IST 2007 Mark McLoughlin <markmc@redhat.com>
* src/hash.c, src/internal.h: Re-name virConnect->domains_mux * src/hash.c, src/internal.h: Re-name virConnect->domains_mux
to virConnect->hashes_mux since it will also be used to to virConnect->hashes_mux since it will also be used to
protect the networks hash. protect the networks hash.
Tue Feb 14 14:57:52 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:57:52 IST 2007 Mark McLoughlin <markmc@redhat.com>
* qemud/conf.c: qemudSaveConfig() will always report a * qemud/conf.c: qemudSaveConfig() will always report a
more specific error, so we should avoid overwriting more specific error, so we should avoid overwriting
this error. this error.
Tue Feb 14 14:54:25 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:54:25 IST 2007 Mark McLoughlin <markmc@redhat.com>
* qemud/qemud.c: Re-factor out qemudExec() so that it can * qemud/qemud.c: Re-factor out qemudExec() so that it can
be used to launch dnsmasq. be used to launch dnsmasq.
@ -18,7 +110,7 @@ Tue Feb 14 14:54:25 EST 2007 Mark McLoughlin <markmc@redhat.com
* qemud/conf.c: don't return argc from qemudBuildCommandLine() * qemud/conf.c: don't return argc from qemudBuildCommandLine()
as exec() doesn't need it. as exec() doesn't need it.
Tue Feb 14 14:52:12 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:52:12 IST 2007 Mark McLoughlin <markmc@redhat.com>
* qemud/conf.c: Re-factor bits of conf.c so that: * qemud/conf.c: Re-factor bits of conf.c so that:
@ -28,25 +120,25 @@ Tue Feb 14 14:52:12 EST 2007 Mark McLoughlin <markmc@redhat.com
- split qemudScanConfigDir() out so that qemudScanConfigs() - split qemudScanConfigDir() out so that qemudScanConfigs()
can scan multiple configDirs can scan multiple configDirs
Tue Feb 14 14:50:22 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:50:22 IST 2007 Mark McLoughlin <markmc@redhat.com>
* qemud/conf.c: handle an unspecified MAC address, * qemud/conf.c: handle an unspecified MAC address,
fix the argv freeing code in qemudBuildCommandLine() fix the argv freeing code in qemudBuildCommandLine()
and fix copy and paste error in qemudGenerateXML() and fix copy and paste error in qemudGenerateXML()
Tue Feb 14 14:42:38 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:42:38 IST 2007 Mark McLoughlin <markmc@redhat.com>
* src/internal.h: add virConnect->qemud_fd so that * src/internal.h: add virConnect->qemud_fd so that
xen and qemu don't share the handle member. xen and qemu don't share the handle member.
* src/hash.c, src/qemu_internal.c: update * src/hash.c, src/qemu_internal.c: update
Tue Feb 14 14:40:52 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:40:52 IST 2007 Mark McLoughlin <markmc@redhat.com>
* qemud/conf.c, qemud/dispatch.c, qemud/driver.c, * qemud/conf.c, qemud/dispatch.c, qemud/driver.c,
qemud/qemud.c: include autoconf's config.h qemud/qemud.c: include autoconf's config.h
Tue Feb 14 14:39:18 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:39:18 IST 2007 Mark McLoughlin <markmc@redhat.com>
* conf.[ch]: rename from config.[ch] so we can use * conf.[ch]: rename from config.[ch] so we can use
autoconf's config.h autoconf's config.h
@ -55,7 +147,7 @@ Tue Feb 14 14:39:18 EST 2007 Mark McLoughlin <markmc@redhat.com
* driver.c, qemud.c: upd. * driver.c, qemud.c: upd.
Tue Feb 14 14:33:22 EST 2007 Mark McLoughlin <markmc@redhat.com Tue Feb 14 14:33:22 IST 2007 Mark McLoughlin <markmc@redhat.com>
* autogen.sh: run autoheader * autogen.sh: run autoheader

View File

@ -102,6 +102,28 @@ then
dnl search for the Xen store library dnl search for the Xen store library
AC_SEARCH_LIBS(xs_read, [xenstore], [], [AC_MSG_ERROR([Xen store library not found])]) AC_SEARCH_LIBS(xs_read, [xenstore], [], [AC_MSG_ERROR([Xen store library not found])])
dnl
dnl check for libsyfs (>= 2.0.0); allow disabling bridge parameters support altogether
dnl
AC_ARG_ENABLE(bridge-params,
AC_HELP_STRING([--disable-bridge-params],
[disable support for setting bridge parameters using libsysfs [default=no]]),,
enable_bridge_params=yes)
if test x"$enable_bridge_params" == "xyes"; then
AC_CHECK_LIB(sysfs, sysfs_open_device,
[AC_CHECK_HEADER(sysfs/libsysfs.h,
AC_DEFINE(ENABLE_BRIDGE_PARAMS, , [enable setting bridge parameters using libsysfs])
SYSFS_LIBS="-lsysfs" AC_SUBST(SYSFS_LIBS),
AC_MSG_ERROR([You must install libsysfs in order to compile libvirt]))])
fi
dnl
dnl check for kernel headers required by qemud/bridge.c
dnl
AC_CHECK_HEADERS(linux/param.h linux/sockios.h linux/if_bridge.h linux/if_tun.h,,
AC_MSG_ERROR([You must install kernel-headers in order to compile libvirt]))
dnl ========================================================================== dnl ==========================================================================
dnl find libxml2 library, borrowed from xmlsec dnl find libxml2 library, borrowed from xmlsec
dnl ========================================================================== dnl ==========================================================================

View File

@ -7,13 +7,14 @@ libexec_PROGRAMS = libvirt_qemud
libvirt_qemud_SOURCES = qemud.c internal.h protocol.h \ libvirt_qemud_SOURCES = qemud.c internal.h protocol.h \
driver.c driver.h \ driver.c driver.h \
dispatch.c dispatch.h \ dispatch.c dispatch.h \
conf.c conf.h conf.c conf.h \
bridge.c bridge.h
#-D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D_POSIX_C_SOURCE=199506L #-D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D_POSIX_C_SOURCE=199506L
libvirt_qemud_CFLAGS = \ libvirt_qemud_CFLAGS = \
-I$(top_srcdir)/include -I$(top_builddir)/include $(LIBXML_CFLAGS) \ -I$(top_srcdir)/include -I$(top_builddir)/include $(LIBXML_CFLAGS) \
-Werror -Wall -Wextra -DLOCAL_STATE_DIR="\"$(localstatedir)\"" \ -Werror -Wall -Wextra -DLOCAL_STATE_DIR="\"$(localstatedir)\"" \
-DSYSCONF_DIR="\"$(sysconfdir)\"" -DSYSCONF_DIR="\"$(sysconfdir)\""
libvirt_qemud_LDFLAGS = $(LIBXML_LIBS) libvirt_qemud_LDFLAGS = $(LIBXML_LIBS) $(SYSFS_LIBS)
libvirt_qemud_DEPENDENCIES = libvirt_qemud_DEPENDENCIES =
libvirt_qemud_LDADD = libvirt_qemud_LDADD =

609
qemud/bridge.c Normal file
View File

@ -0,0 +1,609 @@
/*
* Copyright (C) 2007 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors:
* Mark McLoughlin <markmc@redhat.com>
*/
#include <config.h>
#include "bridge.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/param.h> /* HZ */
#include <linux/sockios.h> /* SIOCBRADDBR etc. */
#include <linux/if_bridge.h> /* SYSFS_BRIDGE_ATTR */
#include <linux/if_tun.h> /* IFF_TUN, IFF_NO_PI */
#include "internal.h"
#define MAX_BRIDGE_ID 256
#define JIFFIES_TO_MS(j) (((j)*1000)/HZ)
#define MS_TO_JIFFIES(ms) (((ms)*HZ)/1000)
struct _brControl {
int fd;
};
int
brInit(brControl **ctlp)
{
int fd;
if (!ctlp || *ctlp)
return EINVAL;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
return errno;
*ctlp = (brControl *)malloc(sizeof(struct _brControl));
if (!*ctlp)
return ENOMEM;
(*ctlp)->fd = fd;
return 0;
}
void
brShutdown(brControl *ctl)
{
if (!ctl)
return;
close(ctl->fd);
ctl->fd = 0;
free(ctl);
}
int
brAddBridge(brControl *ctl,
const char *nameOrFmt,
char *name,
int maxlen)
{
int id, subst;
if (!ctl || !ctl->fd || !nameOrFmt || !name)
return EINVAL;
if (maxlen >= BR_IFNAME_MAXLEN)
maxlen = BR_IFNAME_MAXLEN;
subst = id = 0;
if (strstr(nameOrFmt, "%d"))
subst = 1;
do {
char try[BR_IFNAME_MAXLEN];
int len;
if (subst) {
len = snprintf(try, maxlen, nameOrFmt, id);
if (len >= maxlen)
return EADDRINUSE;
} else {
len = strlen(nameOrFmt);
if (len >= maxlen - 1)
return EINVAL;
strncpy(try, nameOrFmt, len);
try[len] = '\0';
}
if (ioctl(ctl->fd, SIOCBRADDBR, try) == 0) {
strncpy(name, try, maxlen);
return 0;
}
id++;
} while (subst && id <= MAX_BRIDGE_ID);
return errno;
}
int
brDeleteBridge(brControl *ctl,
const char *name)
{
if (!ctl || !ctl->fd || !name)
return EINVAL;
return ioctl(ctl->fd, SIOCBRDELBR, name) == 0 ? 0 : errno;
}
static int
brAddDelInterface(brControl *ctl,
int cmd,
const char *bridge,
const char *iface)
{
struct ifreq ifr;
int len;
if (!ctl || !ctl->fd || !bridge || !iface)
return EINVAL;
if ((len = strlen(bridge)) >= BR_IFNAME_MAXLEN)
return EINVAL;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, bridge, len);
ifr.ifr_name[len] = '\0';
if (!(ifr.ifr_ifindex = if_nametoindex(iface)))
return ENODEV;
return ioctl(ctl->fd, cmd, &ifr) == 0 ? 0 : errno;
}
int
brAddInterface(brControl *ctl,
const char *bridge,
const char *iface)
{
return brAddDelInterface(ctl, SIOCBRADDIF, bridge, iface);
}
int
brDeleteInterface(brControl *ctl,
const char *bridge,
const char *iface)
{
return brAddDelInterface(ctl, SIOCBRDELIF, bridge, iface);
}
int
brAddTap(brControl *ctl,
const char *bridge,
const char *ifnameOrFmt,
char *ifname,
int maxlen,
int *tapfd)
{
int id, subst, fd;
if (!ctl || !ctl->fd || !bridge || !ifnameOrFmt || !tapfd)
return EINVAL;
if (!ifname)
maxlen = BR_IFNAME_MAXLEN;
else if (maxlen >= BR_IFNAME_MAXLEN)
maxlen = BR_IFNAME_MAXLEN;
subst = id = 0;
if (strstr(ifnameOrFmt, "%d"))
subst = 1;
if ((fd = open("/dev/net/tun", O_RDWR)) < 0)
return errno;
do {
struct ifreq try;
int len;
memset(&try, 0, sizeof(struct ifreq));
try.ifr_flags = IFF_TAP|IFF_NO_PI;
if (subst) {
len = snprintf(try.ifr_name, maxlen, ifnameOrFmt, id);
if (len >= maxlen) {
errno = EADDRINUSE;
goto error;
}
} else {
len = strlen(ifnameOrFmt);
if (len >= maxlen - 1) {
errno = EINVAL;
goto error;
}
strncpy(try.ifr_name, ifnameOrFmt, len);
try.ifr_name[len] = '\0';
}
if (ioctl(fd, TUNSETIFF, &try) == 0) {
if ((errno = brAddInterface(ctl, bridge, try.ifr_name)))
goto error;
if ((errno = brSetInterfaceUp(ctl, try.ifr_name, 1)))
goto error;
if (ifname)
strncpy(ifname, try.ifr_name, maxlen);
*tapfd = fd;
return 0;
}
id++;
} while (subst && id <= MAX_BRIDGE_ID);
error:
close(fd);
return errno;
}
int
brSetInterfaceUp(brControl *ctl,
const char *ifname,
int up)
{
struct ifreq ifr;
int len;
int flags;
if (!ctl || !ifname)
return EINVAL;
if ((len = strlen(ifname)) >= BR_IFNAME_MAXLEN)
return EINVAL;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, ifname, len);
ifr.ifr_name[len] = '\0';
if (ioctl(ctl->fd, SIOCGIFFLAGS, &ifr) < 0)
return errno;
flags = up ? (ifr.ifr_flags | IFF_UP) : (ifr.ifr_flags & ~IFF_UP);
if (ifr.ifr_flags != flags) {
ifr.ifr_flags = flags;
if (ioctl(ctl->fd, SIOCSIFFLAGS, &ifr) < 0)
return errno;
}
return 0;
}
int
brGetInterfaceUp(brControl *ctl,
const char *ifname,
int *up)
{
struct ifreq ifr;
int len;
if (!ctl || !ifname)
return EINVAL;
if ((len = strlen(ifname)) >= BR_IFNAME_MAXLEN)
return EINVAL;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, ifname, len);
ifr.ifr_name[len] = '\0';
if (ioctl(ctl->fd, SIOCGIFFLAGS, &ifr) < 0)
return errno;
*up = (ifr.ifr_flags & IFF_UP) ? 1 : 0;
return 0;
}
static int
brSetInetAddr(brControl *ctl,
const char *ifname,
int cmd,
const char *addr)
{
struct ifreq ifr;
struct in_addr inaddr;
int len, ret;
if (!ctl || !ctl->fd || !ifname || !addr)
return EINVAL;
if ((len = strlen(ifname)) >= BR_IFNAME_MAXLEN)
return EINVAL;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, ifname, len);
ifr.ifr_name[len] = '\0';
if ((ret = inet_pton(AF_INET, addr, &inaddr)) < 0)
return errno;
else if (ret == 0)
return EINVAL;
((struct sockaddr_in *)&ifr.ifr_addr)->sin_family = AF_INET;
((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr = inaddr;
if (ioctl(ctl->fd, cmd, &ifr) < 0)
return errno;
return 0;
}
static int
brGetInetAddr(brControl *ctl,
const char *ifname,
int cmd,
char *addr,
int maxlen)
{
struct ifreq ifr;
struct in_addr *inaddr;
int len;
if (!ctl || !ctl->fd || !ifname || !addr)
return EINVAL;
if ((len = strlen(ifname)) >= BR_IFNAME_MAXLEN)
return EINVAL;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, ifname, len);
ifr.ifr_name[len] = '\0';
if (ioctl(ctl->fd, cmd, &ifr) < 0)
return errno;
if (maxlen < BR_INET_ADDR_MAXLEN || ifr.ifr_addr.sa_family != AF_INET)
return EFAULT;
inaddr = &((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;
if (!inet_ntop(AF_INET, inaddr, addr, maxlen))
return errno;
return 0;
}
int
brSetInetAddress(brControl *ctl,
const char *ifname,
const char *addr)
{
return brSetInetAddr(ctl, ifname, SIOCSIFADDR, addr);
}
int
brGetInetAddress(brControl *ctl,
const char *ifname,
char *addr,
int maxlen)
{
return brGetInetAddr(ctl, ifname, SIOCGIFADDR, addr, maxlen);
}
int
brSetInetNetmask(brControl *ctl,
const char *ifname,
const char *addr)
{
return brSetInetAddr(ctl, ifname, SIOCSIFNETMASK, addr);
}
int
brGetInetNetmask(brControl *ctl,
const char *ifname,
char *addr,
int maxlen)
{
return brGetInetAddr(ctl, ifname, SIOCGIFNETMASK, addr, maxlen);
}
#ifdef ENABLE_BRIDGE_PARAMS
#include <sysfs/libsysfs.h>
static int
brSysfsPrep(struct sysfs_class_device **dev,
struct sysfs_attribute **attr,
const char *bridge,
const char *attrname)
{
*dev = NULL;
*attr = NULL;
if (!(*dev = sysfs_open_class_device("net", bridge)))
return errno;
if (!(*attr = sysfs_get_classdev_attr(*dev, attrname))) {
int err = errno;
sysfs_close_class_device(*dev);
*dev = NULL;
return err;
}
return 0;
}
static int
brSysfsWriteInt(struct sysfs_attribute *attr,
int value)
{
char buf[32];
int len;
len = snprintf(buf, sizeof(buf), "%d\n", value);
if (len > (int)sizeof(buf))
len = sizeof(buf); /* paranoia, shouldn't happen */
return sysfs_write_attribute(attr, buf, len) == 0 ? 0 : errno;
}
int
brSetForwardDelay(brControl *ctl,
const char *bridge,
int delay)
{
struct sysfs_class_device *dev;
struct sysfs_attribute *attr;
int err = 0;
if (!ctl || !bridge)
return EINVAL;
if ((err = brSysfsPrep(&dev, &attr, bridge, SYSFS_BRIDGE_ATTR "/forward_delay")))
return err;
err = brSysfsWriteInt(attr, MS_TO_JIFFIES(delay));
sysfs_close_class_device(dev);
return err;
}
int
brGetForwardDelay(brControl *ctl,
const char *bridge,
int *delayp)
{
struct sysfs_class_device *dev;
struct sysfs_attribute *attr;
int err = 0;
if (!ctl || !bridge || !delayp)
return EINVAL;
if ((err = brSysfsPrep(&dev, &attr, bridge, SYSFS_BRIDGE_ATTR "/forward_delay")))
return err;
*delayp = strtoul(attr->value, NULL, 0);
if (errno != ERANGE) {
*delayp = JIFFIES_TO_MS(*delayp);
} else {
err = errno;
}
sysfs_close_class_device(dev);
return err;
}
int
brSetEnableSTP(brControl *ctl,
const char *bridge,
int enable)
{
struct sysfs_class_device *dev;
struct sysfs_attribute *attr;
int err = 0;
if (!ctl || !bridge)
return EINVAL;
if ((err = brSysfsPrep(&dev, &attr, bridge, SYSFS_BRIDGE_ATTR "/stp_state")))
return err;
err = brSysfsWriteInt(attr, (enable == 0) ? 0 : 1);
sysfs_close_class_device(dev);
return err;
}
int
brGetEnableSTP(brControl *ctl,
const char *bridge,
int *enablep)
{
struct sysfs_class_device *dev;
struct sysfs_attribute *attr;
int err = 0;
if (!ctl || !bridge || !enablep)
return EINVAL;
if ((err = brSysfsPrep(&dev, &attr, bridge, SYSFS_BRIDGE_ATTR "/stp_state")))
return err;
*enablep = strtoul(attr->value, NULL, 0);
if (errno != ERANGE) {
*enablep = (*enablep == 0) ? 0 : 1;
} else {
err = errno;
}
sysfs_close_class_device(dev);
return err;
}
#else /* ENABLE_BRIDGE_PARAMS */
int
brSetForwardDelay(brControl *ctl ATTRIBUTE_UNUSED,
const char *bridge ATTRIBUTE_UNUSED,
int delay ATTRIBUTE_UNUSED)
{
return 0;
}
int
brGetForwardDelay(brControl *ctl ATTRIBUTE_UNUSED,
const char *bridge ATTRIBUTE_UNUSED,
int *delay ATTRIBUTE_UNUSED)
{
return 0;
}
int
brSetEnableSTP(brControl *ctl ATTRIBUTE_UNUSED,
const char *bridge ATTRIBUTE_UNUSED,
int enable ATTRIBUTE_UNUSED)
{
return 0;
}
int
brGetEnableSTP(brControl *ctl ATTRIBUTE_UNUSED,
const char *bridge ATTRIBUTE_UNUSED,
int *enable ATTRIBUTE_UNUSED)
{
return 0;
}
#endif /* ENABLE_BRIDGE_PARAMS */
/*
* Local variables:
* indent-tabs-mode: nil
* c-indent-level: 4
* c-basic-offset: 4
* tab-width: 4
* End:
*/

101
qemud/bridge.h Normal file
View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2007 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors:
* Mark McLoughlin <markmc@redhat.com>
*/
#ifndef __QEMUD_BRIDGE_H__
#define __QEMUD_BRIDGE_H__
#include <net/if.h>
#include <netinet/in.h>
#define BR_IFNAME_MAXLEN IF_NAMESIZE
#define BR_INET_ADDR_MAXLEN INET_ADDRSTRLEN
typedef struct _brControl brControl;
int brInit (brControl **ctl);
void brShutdown (brControl *ctl);
int brAddBridge (brControl *ctl,
const char *nameOrFmt,
char *name,
int maxlen);
int brDeleteBridge (brControl *ctl,
const char *name);
int brAddInterface (brControl *ctl,
const char *bridge,
const char *iface);
int brDeleteInterface (brControl *ctl,
const char *bridge,
const char *iface);
int brAddTap (brControl *ctl,
const char *bridge,
const char *ifnameOrFmt,
char *ifname,
int maxlen,
int *tapfd);
int brSetInterfaceUp (brControl *ctl,
const char *ifname,
int up);
int brGetInterfaceUp (brControl *ctl,
const char *ifname,
int *up);
int brSetInetAddress (brControl *ctl,
const char *ifname,
const char *addr);
int brGetInetAddress (brControl *ctl,
const char *ifname,
char *addr,
int maxlen);
int brSetInetNetmask (brControl *ctl,
const char *ifname,
const char *netmask);
int brGetInetNetmask (brControl *ctl,
const char *ifname,
char *netmask,
int maxlen);
int brSetForwardDelay (brControl *ctl,
const char *bridge,
int delay);
int brGetForwardDelay (brControl *ctl,
const char *bridge,
int *delay);
int brSetEnableSTP (brControl *ctl,
const char *bridge,
int enable);
int brGetEnableSTP (brControl *ctl,
const char *bridge,
int *enable);
#endif /* __QEMUD_BRIDGE_H__ */
/*
* Local variables:
* indent-tabs-mode: nil
* c-indent-level: 4
* c-basic-offset: 4
* tab-width: 4
* End:
*/

View File

@ -1098,17 +1098,263 @@ struct qemud_vm *qemudLoadConfigXML(struct qemud_server *server,
} }
void qemudFreeNetwork(struct qemud_network *network) {
free(network);
}
static int qemudSaveNetworkConfig(struct qemud_server *server,
struct qemud_network *network) {
char *xml;
int fd, ret = -1;
int towrite;
if (!(xml = qemudGenerateNetworkXML(server, network))) {
return -1;
}
if (qemudEnsureConfigDir(server, server->networkConfigDir) < 0) {
goto cleanup;
}
if ((fd = open(network->configFile,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR )) < 0) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot create config file %s", network->configFile);
goto cleanup;
}
towrite = strlen(xml);
if (write(fd, xml, towrite) != towrite) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot write config file %s", network->configFile);
goto cleanup;
}
if (close(fd) < 0) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot save config file %s", network->configFile);
goto cleanup;
}
ret = 0;
cleanup:
free(xml);
return ret;
}
static int qemudParseBridgeXML(struct qemud_server *server ATTRIBUTE_UNUSED,
struct qemud_network *network,
xmlNodePtr node) {
xmlChar *name, *stp, *delay;
name = xmlGetProp(node, BAD_CAST "name");
if (name != NULL) {
strncpy(network->def.bridge, (const char *)name, IF_NAMESIZE-1);
network->def.bridge[IF_NAMESIZE-1] = '\0';
xmlFree(name);
name = NULL;
}
stp = xmlGetProp(node, BAD_CAST "stp");
if (stp != NULL) {
if (xmlStrEqual(stp, BAD_CAST "off")) {
network->def.disableSTP = 1;
}
xmlFree(stp);
stp = NULL;
}
delay = xmlGetProp(node, BAD_CAST "delay");
if (delay != NULL) {
network->def.forwardDelay = strtol((const char *)delay, NULL, 10);
xmlFree(delay);
delay = NULL;
}
return 1;
}
static int qemudParseInetXML(struct qemud_server *server ATTRIBUTE_UNUSED,
struct qemud_network *network,
xmlNodePtr node) {
xmlChar *address, *netmask;
address = xmlGetProp(node, BAD_CAST "address");
if (address != NULL) {
strncpy(network->def.ipAddress, (const char *)address, BR_INET_ADDR_MAXLEN-1);
network->def.ipAddress[BR_INET_ADDR_MAXLEN-1] = '\0';
xmlFree(address);
address = NULL;
}
netmask = xmlGetProp(node, BAD_CAST "netmask");
if (netmask != NULL) {
strncpy(network->def.netmask, (const char *)netmask, BR_INET_ADDR_MAXLEN-1);
network->def.netmask[BR_INET_ADDR_MAXLEN-1] = '\0';
xmlFree(netmask);
netmask = NULL;
}
return 1;
}
static int qemudParseNetworkXML(struct qemud_server *server,
xmlDocPtr xml,
struct qemud_network *network) {
xmlNodePtr root = NULL;
xmlXPathContextPtr ctxt = NULL;
xmlXPathObjectPtr obj = NULL;
/* Prepare parser / xpath context */
root = xmlDocGetRootElement(xml);
if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "network"))) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "incorrect root element");
goto error;
}
ctxt = xmlXPathNewContext(xml);
if (ctxt == NULL) {
qemudReportError(server, VIR_ERR_NO_MEMORY, "xmlXPathContext");
goto error;
}
/* Extract network name */
obj = xmlXPathEval(BAD_CAST "string(/network/name[1])", ctxt);
if ((obj == NULL) || (obj->type != XPATH_STRING) ||
(obj->stringval == NULL) || (obj->stringval[0] == 0)) {
qemudReportError(server, VIR_ERR_NO_NAME, NULL);
goto error;
}
if (strlen((const char *)obj->stringval) >= (QEMUD_MAX_NAME_LEN-1)) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "network name length too long");
goto error;
}
strcpy(network->def.name, (const char *)obj->stringval);
xmlXPathFreeObject(obj);
/* Extract network uuid */
obj = xmlXPathEval(BAD_CAST "string(/network/uuid[1])", ctxt);
if ((obj == NULL) || (obj->type != XPATH_STRING) ||
(obj->stringval == NULL) || (obj->stringval[0] == 0)) {
/* XXX auto-generate a UUID */
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "missing uuid element");
goto error;
}
if (qemudParseUUID((const char *)obj->stringval, network->def.uuid) < 0) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed uuid element");
goto error;
}
xmlXPathFreeObject(obj);
/* Parse bridge information */
obj = xmlXPathEval(BAD_CAST "/network/bridge[1]", ctxt);
if ((obj != NULL) && (obj->type == XPATH_NODESET) &&
(obj->nodesetval != NULL) && (obj->nodesetval->nodeNr > 0)) {
if (!qemudParseBridgeXML(server, network, obj->nodesetval->nodeTab[0])) {
goto error;
}
}
xmlXPathFreeObject(obj);
/* Parse IP information */
obj = xmlXPathEval(BAD_CAST "/network/ip[1]", ctxt);
if ((obj != NULL) && (obj->type == XPATH_NODESET) &&
(obj->nodesetval != NULL) && (obj->nodesetval->nodeNr > 0)) {
if (!qemudParseInetXML(server, network, obj->nodesetval->nodeTab[0])) {
goto error;
}
}
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctxt);
return 0;
error:
/* XXX free all the stuff in the qemud_network struct, or leave it upto
the caller ? */
if (obj)
xmlXPathFreeObject(obj);
if (ctxt)
xmlXPathFreeContext(ctxt);
return -1;
}
struct qemud_network *qemudLoadNetworkConfigXML(struct qemud_server *server,
const char *file,
const char *doc,
int save) {
struct qemud_network *network = NULL;
xmlDocPtr xml;
if (!(xml = xmlReadDoc(BAD_CAST doc, file ? file : "network.xml", NULL,
XML_PARSE_NOENT | XML_PARSE_NONET |
XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) {
qemudReportError(server, VIR_ERR_XML_ERROR, NULL);
return NULL;
}
if (!(network = calloc(1, sizeof(struct qemud_network)))) {
qemudReportError(server, VIR_ERR_NO_MEMORY, "network");
return NULL;
}
if (qemudParseNetworkXML(server, xml, network) < 0) {
xmlFreeDoc(xml);
qemudFreeNetwork(network);
return NULL;
}
xmlFreeDoc(xml);
if (qemudFindNetworkByUUID(server, network->def.uuid) ||
qemudFindNetworkByName(server, network->def.name)) {
qemudReportError(server, VIR_ERR_NETWORK_EXIST, network->def.name);
qemudFreeNetwork(network);
return NULL;
}
if (file) {
strncpy(network->configFile, file, PATH_MAX);
network->configFile[PATH_MAX-1] = '\0';
} else {
if (save) {
if (qemudMakeConfigPath(server->networkConfigDir, network->def.name, ".xml", network->configFile, PATH_MAX) < 0) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot construct config file path");
qemudFreeNetwork(network);
return NULL;
}
if (qemudSaveNetworkConfig(server, network) < 0) {
qemudFreeNetwork(network);
return NULL;
}
} else {
network->configFile[0] = '\0';
}
}
return network;
}
/* Load a guest from its persistent config file */ /* Load a guest from its persistent config file */
static void qemudLoadConfig(struct qemud_server *server, static void qemudLoadConfig(struct qemud_server *server,
const char *file) { const char *file,
int isGuest) {
FILE *fh; FILE *fh;
struct stat st; struct stat st;
struct qemud_vm *vm;
char xml[QEMUD_MAX_XML_LEN]; char xml[QEMUD_MAX_XML_LEN];
int ret; int ret;
if (!(fh = fopen(file, "r"))) { if (!(fh = fopen(file, "r"))) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot open guest config file %s", file); qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot open config file %s", file);
return; return;
} }
@ -1118,7 +1364,7 @@ static void qemudLoadConfig(struct qemud_server *server,
} }
if (st.st_size >= QEMUD_MAX_XML_LEN) { if (st.st_size >= QEMUD_MAX_XML_LEN) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "guest config too large in file %s", file); qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "config too large in file %s", file);
goto cleanup; goto cleanup;
} }
@ -1128,10 +1374,20 @@ static void qemudLoadConfig(struct qemud_server *server,
} }
xml[st.st_size] = '\0'; xml[st.st_size] = '\0';
if ((vm = qemudLoadConfigXML(server, file, xml, 1))) { if (isGuest) {
vm->next = server->inactivevms; struct qemud_vm *vm;
server->inactivevms = vm; if ((vm = qemudLoadConfigXML(server, file, xml, 1))) {
server->ninactivevms++; vm->next = server->inactivevms;
server->inactivevms = vm;
server->ninactivevms++;
}
} else {
struct qemud_network *network;
if ((network = qemudLoadNetworkConfigXML(server, file, xml, 1))) {
network->next = server->inactivenetworks;
server->inactivenetworks = network;
server->ninactivenetworks++;
}
} }
cleanup: cleanup:
@ -1141,7 +1397,8 @@ static void qemudLoadConfig(struct qemud_server *server,
static static
int qemudScanConfigDir(struct qemud_server *server, int qemudScanConfigDir(struct qemud_server *server,
const char *configDir) { const char *configDir,
int isGuest) {
DIR *dir; DIR *dir;
struct dirent *entry; struct dirent *entry;
@ -1159,7 +1416,7 @@ int qemudScanConfigDir(struct qemud_server *server,
if (qemudMakeConfigPath(configDir, entry->d_name, NULL, file, PATH_MAX) < 0) if (qemudMakeConfigPath(configDir, entry->d_name, NULL, file, PATH_MAX) < 0)
continue; continue;
qemudLoadConfig(server, file); qemudLoadConfig(server, file, isGuest);
} }
closedir(dir); closedir(dir);
@ -1169,7 +1426,9 @@ int qemudScanConfigDir(struct qemud_server *server,
/* Scan for all guest and network config files */ /* Scan for all guest and network config files */
int qemudScanConfigs(struct qemud_server *server) { int qemudScanConfigs(struct qemud_server *server) {
return qemudScanConfigDir(server, server->configDir); if (qemudScanConfigDir(server, server->configDir, 0) < 0)
return -1;
return qemudScanConfigDir(server, server->networkConfigDir, 1);
} }
/* Simple grow-on-demand string buffer */ /* Simple grow-on-demand string buffer */
@ -1424,19 +1683,76 @@ char *qemudGenerateXML(struct qemud_server *server, struct qemud_vm *vm) {
} }
int qemudDeleteConfigXML(struct qemud_server *server, struct qemud_vm *vm) { char *qemudGenerateNetworkXML(struct qemud_server *server,
if (!vm->configFile[0]) { struct qemud_network *network) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "no config file for guest %s", vm->def.name); struct qemudBuffer buf;
unsigned char *uuid;
buf.len = QEMUD_MAX_XML_LEN;
buf.used = 0;
buf.data = malloc(buf.len);
if (qemudBufferPrintf(&buf, "<network>\n") < 0)
goto no_memory;
if (qemudBufferPrintf(&buf, " <name>%s</name>\n", network->def.name) < 0)
goto no_memory;
uuid = network->def.uuid;
if (qemudBufferPrintf(&buf, " <uuid>%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x</uuid>\n",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11],
uuid[12], uuid[13], uuid[14], uuid[15]) < 0)
goto no_memory;
if (qemudBufferPrintf(&buf, " <bridge name='%s' stp='%s' delay='%d' />\n",
network->def.bridge,
network->def.disableSTP ? "off" : "on",
network->def.forwardDelay) < 0)
goto no_memory;
if (network->def.ipAddress[0] || network->def.netmask[0]) {
if (qemudBufferAdd(&buf, " <ip") < 0)
goto no_memory;
if (network->def.ipAddress[0] &&
qemudBufferPrintf(&buf, " address='%s'", network->def.ipAddress) < 0)
goto no_memory;
if (network->def.netmask[0] &&
qemudBufferPrintf(&buf, " netmask='%s'", network->def.netmask) < 0)
goto no_memory;
if (qemudBufferAdd(&buf, "/>\n") < 0)
goto no_memory;
}
if (qemudBufferAdd(&buf, "</network>\n") < 0)
goto no_memory;
return buf.data;
no_memory:
qemudReportError(server, VIR_ERR_NO_MEMORY, "xml");
free(buf.data);
return NULL;
}
int qemudDeleteConfig(struct qemud_server *server,
const char *configFile,
const char *name) {
if (!configFile[0]) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "no config file for %s", name);
return -1; return -1;
} }
if (unlink(vm->configFile) < 0) { if (unlink(configFile) < 0) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot remove config for guest %s", vm->def.name); qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot remove config for %s", name);
return -1; return -1;
} }
vm->configFile[0] = '\0';
return 0; return 0;
} }

View File

@ -30,6 +30,7 @@
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include "protocol.h" #include "protocol.h"
#include "bridge.h"
#ifdef __GNUC__ #ifdef __GNUC__
#ifdef HAVE_ANSIDECL_H #ifdef HAVE_ANSIDECL_H
@ -203,6 +204,13 @@ struct qemud_vm {
struct qemud_network_def { struct qemud_network_def {
unsigned char uuid[QEMUD_UUID_RAW_LEN]; unsigned char uuid[QEMUD_UUID_RAW_LEN];
char name[QEMUD_MAX_NAME_LEN]; char name[QEMUD_MAX_NAME_LEN];
char bridge[BR_IFNAME_MAXLEN];
int disableSTP;
int forwardDelay;
char ipAddress[BR_INET_ADDR_MAXLEN];
char netmask[BR_INET_ADDR_MAXLEN];
}; };
/* Virtual Network runtime state */ /* Virtual Network runtime state */
@ -210,6 +218,11 @@ struct qemud_network {
char configFile[PATH_MAX]; char configFile[PATH_MAX];
struct qemud_network_def def; struct qemud_network_def def;
char bridge[BR_IFNAME_MAXLEN];
unsigned int active : 1;
struct qemud_network *next; struct qemud_network *next;
}; };
@ -249,6 +262,7 @@ struct qemud_server {
struct qemud_network *activenetworks; struct qemud_network *activenetworks;
int ninactivenetworks; int ninactivenetworks;
struct qemud_network *inactivenetworks; struct qemud_network *inactivenetworks;
brControl *brctl;
char configDir[PATH_MAX]; char configDir[PATH_MAX];
char networkConfigDir[PATH_MAX]; char networkConfigDir[PATH_MAX];
char errorMessage[QEMUD_MAX_ERROR_LEN]; char errorMessage[QEMUD_MAX_ERROR_LEN];

View File

@ -706,14 +706,115 @@ static int qemudDispatchVMFailure(struct qemud_server *server, struct qemud_vm *
int qemudStartNetworkDaemon(struct qemud_server *server, int qemudStartNetworkDaemon(struct qemud_server *server,
struct qemud_network *network) { struct qemud_network *network) {
server = NULL; network = NULL; const char *name;
int err;
if (network->active) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR,
"network is already active");
return -1;
}
if (!server->brctl && (err = brInit(&server->brctl))) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR,
"cannot initialize bridge support: %s", strerror(err));
return -1;
}
if (network->def.bridge[0] == '\0' ||
strchr(network->def.bridge, '%')) {
name = "vnet%d";
} else {
name = network->def.bridge;
}
if ((err = brAddBridge(server->brctl, name, network->bridge, sizeof(network->bridge)))) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR,
"cannot create bridge '%s' : %s", name, strerror(err));
return -1;
}
if (network->def.ipAddress[0] &&
(err = brSetInetAddress(server->brctl, network->bridge, network->def.ipAddress))) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR,
"cannot set IP address on bridge '%s' to '%s' : %s\n",
network->bridge, network->def.ipAddress, strerror(err));
goto err_delbr;
}
if (network->def.netmask[0] &&
(err = brSetInetNetmask(server->brctl, network->bridge, network->def.netmask))) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR,
"cannot set netmask on bridge '%s' to '%s' : %s\n",
network->bridge, network->def.netmask, strerror(err));
goto err_delbr;
}
if (network->def.ipAddress[0] &&
(err = brSetInterfaceUp(server->brctl, network->bridge, 1))) {
qemudReportError(server, VIR_ERR_INTERNAL_ERROR,
"failed to bring the bridge '%s' up : %s\n",
network->bridge, strerror(err));
goto err_delbr;
}
network->active = 1;
return 0; return 0;
err_delbr:
if ((err = brDeleteBridge(server->brctl, network->bridge))) {
printf("Damn! Couldn't delete bridge '%s' : %s\n",
network->bridge, strerror(err));
}
return -1;
} }
int qemudShutdownNetworkDaemon(struct qemud_server *server, int qemudShutdownNetworkDaemon(struct qemud_server *server,
struct qemud_network *network) { struct qemud_network *network) {
server = NULL; network = NULL; struct qemud_network *prev, *curr;
int err;
if (!network->active)
return 0;
if (network->def.ipAddress[0] &&
(err = brSetInterfaceUp(server->brctl, network->bridge, 0))) {
printf("Damn! Failed to bring down bridge '%s' : %s\n",
network->bridge, strerror(err));
}
if ((err = brDeleteBridge(server->brctl, network->bridge))) {
printf("Damn! Failed to delete bridge '%s' : %s\n",
network->bridge, strerror(err));
}
/* Move it to inactive networks list */
prev = NULL;
curr = server->activenetworks;
while (curr) {
if (curr == network) {
if (prev) {
prev->next = curr->next;
} else {
server->activenetworks = curr->next;
}
server->nactivenetworks--;
curr->next = server->inactivenetworks;
server->inactivenetworks = curr;
server->ninactivenetworks++;
break;
}
prev = curr;
curr = curr->next;
}
network->bridge[0] = '\0';
network->active = 0;
return 0; return 0;
} }
@ -723,6 +824,7 @@ static int qemudDispatchPoll(struct qemud_server *server, struct pollfd *fds) {
struct qemud_client *client = server->clients; struct qemud_client *client = server->clients;
struct qemud_vm *vm = server->activevms; struct qemud_vm *vm = server->activevms;
struct qemud_vm *tmp; struct qemud_vm *tmp;
struct qemud_network *network, *prevnet;
int ret = 0; int ret = 0;
int fd = 0; int fd = 0;
@ -806,6 +908,25 @@ static int qemudDispatchPoll(struct qemud_server *server, struct pollfd *fds) {
} }
} }
/* Cleanup any networks too */
network = server->inactivenetworks;
prevnet = NULL;
while (network) {
if (!network->configFile[0]) {
struct qemud_network *next = network->next;
if (prevnet) {
prevnet->next = next;
} else {
server->inactivenetworks = next;
}
qemudFreeNetwork(network);
network = next;
} else {
prevnet = network;
network = network->next;
}
}
return ret; return ret;
} }
@ -896,6 +1017,8 @@ static void qemudCleanup(struct qemud_server *server) {
close(sock->fd); close(sock->fd);
sock = sock->next; sock = sock->next;
} }
if (server->brctl)
brShutdown(server->brctl);
free(server); free(server);
} }