From 8090a56890457b192ff453a65f220c4b6617dc27 Mon Sep 17 00:00:00 2001 From: Laine Stump Date: Mon, 20 Dec 2010 01:14:11 -0500 Subject: [PATCH] Run radvd for virtual networks with IPv6 addresses Running an instance of the router advertisement daemon (radvd) allows guests using the virtual network to automatically acquire an IPv6 address and default route. Note that acquiring an address only works for networks with a prefix length of exactly 64 - radvd is still run in other circumstances, and still advertises routes, but autoconf will not work because it requires exactly 64 bits of address info from the network prefix. This patch avoids a race condition with the pidfile by manually daemonizing radvd rather than allowing it to daemonize itself, then creating our own pidfile (in addition to radvd's own file, which is unnecessary, but there is no way to tell radvd to not create it). This is accomplished by exec'ing it with "--debug 1" in the commandline, and using virCommand's features to fork, create a pidfile, and detach from the newly forked process. --- configure.ac | 4 + src/conf/network_conf.h | 1 + src/network/bridge_driver.c | 264 +++++++++++++++++++++++++++++++++--- 3 files changed, 249 insertions(+), 20 deletions(-) diff --git a/configure.ac b/configure.ac index c507891feb..90fe145bbf 100644 --- a/configure.ac +++ b/configure.ac @@ -135,6 +135,8 @@ dnl detect them, in which case we'll search for the program dnl along the $PATH at runtime and fail if it's not there. AC_PATH_PROG([DNSMASQ], [dnsmasq], [dnsmasq], [/sbin:/usr/sbin:/usr/local/sbin:$PATH]) +AC_PATH_PROG([RADVD], [radvd], [radvd], + [/sbin:/usr/sbin:/usr/local/sbin:$PATH]) AC_PATH_PROG([BRCTL], [brctl], [brctl], [/sbin:/usr/sbin:/usr/local/sbin:$PATH]) AC_PATH_PROG([UDEVADM], [udevadm], [], @@ -146,6 +148,8 @@ AC_PATH_PROG([MODPROBE], [modprobe], [], AC_DEFINE_UNQUOTED([DNSMASQ],["$DNSMASQ"], [Location or name of the dnsmasq program]) +AC_DEFINE_UNQUOTED([RADVD],["$RADVD"], + [Location or name of the radvd program]) AC_DEFINE_UNQUOTED([BRCTL],["$BRCTL"], [Location or name of the brctl program (see bridge-utils)]) if test -n "$UDEVADM"; then diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 6c6bbe3d0e..fd96c367b1 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -106,6 +106,7 @@ struct _virNetworkObj { virMutex lock; pid_t dnsmasqPid; + pid_t radvdPid; unsigned int active : 1; unsigned int autostart : 1; unsigned int persistent : 1; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 07791f152a..7d43ef59ce 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -65,6 +65,7 @@ #define NETWORK_STATE_DIR LOCALSTATEDIR "/lib/libvirt/network" #define DNSMASQ_STATE_DIR LOCALSTATEDIR "/lib/libvirt/dnsmasq" +#define RADVD_STATE_DIR LOCALSTATEDIR "/lib/libvirt/radvd" #define VIR_FROM_THIS VIR_FROM_NETWORK @@ -107,6 +108,25 @@ static void networkReloadIptablesRules(struct network_driver *driver); static struct network_driver *driverState = NULL; +static char * +networkRadvdPidfileBasename(const char *netname) +{ + /* this is simple but we want to be sure it's consistently done */ + char *pidfilebase; + + virAsprintf(&pidfilebase, "%s-radvd", netname); + return pidfilebase; +} + +static char * +networkRadvdConfigFileName(const char *netname) +{ + char *configfile; + + virAsprintf(&configfile, RADVD_STATE_DIR "/%s-radvd.conf", + netname); + return configfile; +} static void networkFindActiveConfigs(struct network_driver *driver) { @@ -144,26 +164,43 @@ networkFindActiveConfigs(struct network_driver *driver) { brHasBridge(driver->brctl, obj->def->bridge) == 0) { obj->active = 1; - /* Finally try and read dnsmasq pid if any */ - if (obj->def->ips && (obj->def->nips > 0) && - virFileReadPid(NETWORK_PID_DIR, obj->def->name, - &obj->dnsmasqPid) == 0) { + /* Try and read dnsmasq/radvd pids if any */ + if (obj->def->ips && (obj->def->nips > 0)) { + char *pidpath, *radvdpidbase; - /* Check its still alive */ - if (kill(obj->dnsmasqPid, 0) != 0) - obj->dnsmasqPid = -1; + if (virFileReadPid(NETWORK_PID_DIR, obj->def->name, + &obj->dnsmasqPid) == 0) { + /* Check that it's still alive */ + if (kill(obj->dnsmasqPid, 0) != 0) + obj->dnsmasqPid = -1; + if (virAsprintf(&pidpath, "/proc/%d/exe", obj->dnsmasqPid) < 0) { + virReportOOMError(); + goto cleanup; + } + if (virFileLinkPointsTo(pidpath, DNSMASQ) == 0) + obj->dnsmasqPid = -1; + VIR_FREE(pidpath); + } -#ifdef __linux__ - char *pidpath; - - if (virAsprintf(&pidpath, "/proc/%d/exe", obj->dnsmasqPid) < 0) { + if (!(radvdpidbase = networkRadvdPidfileBasename(obj->def->name))) { virReportOOMError(); goto cleanup; } - if (virFileLinkPointsTo(pidpath, DNSMASQ) == 0) - obj->dnsmasqPid = -1; - VIR_FREE(pidpath); -#endif + if (virFileReadPid(NETWORK_PID_DIR, radvdpidbase, + &obj->radvdPid) == 0) { + /* Check that it's still alive */ + if (kill(obj->radvdPid, 0) != 0) + obj->radvdPid = -1; + if (virAsprintf(&pidpath, "/proc/%d/exe", obj->radvdPid) < 0) { + virReportOOMError(); + VIR_FREE(radvdpidbase); + goto cleanup; + } + if (virFileLinkPointsTo(pidpath, RADVD) == 0) + obj->radvdPid = -1; + VIR_FREE(pidpath); + } + VIR_FREE(radvdpidbase); } } @@ -586,6 +623,136 @@ cleanup: return ret; } +static int +networkStartRadvd(virNetworkObjPtr network) +{ + char *pidfile = NULL; + char *radvdpidbase = NULL; + virBuffer configbuf = VIR_BUFFER_INITIALIZER;; + char *configstr = NULL; + char *configfile = NULL; + virCommandPtr cmd = NULL; + int ret = -1, err, ii; + virNetworkIpDefPtr ipdef; + + network->radvdPid = -1; + + if ((err = virFileMakePath(NETWORK_PID_DIR)) != 0) { + virReportSystemError(err, + _("cannot create directory %s"), + NETWORK_PID_DIR); + goto cleanup; + } + if ((err = virFileMakePath(RADVD_STATE_DIR)) != 0) { + virReportSystemError(err, + _("cannot create directory %s"), + RADVD_STATE_DIR); + goto cleanup; + } + + /* construct pidfile name */ + if (!(radvdpidbase = networkRadvdPidfileBasename(network->def->name))) { + virReportOOMError(); + goto cleanup; + } + if (!(pidfile = virFilePid(NETWORK_PID_DIR, radvdpidbase))) { + virReportOOMError(); + goto cleanup; + } + + /* create radvd config file appropriate for this network */ + virBufferVSprintf(&configbuf, "interface %s\n" + "{\n" + " AdvSendAdvert on;\n" + " AdvManagedFlag off;\n" + " AdvOtherConfigFlag off;\n" + "\n", + network->def->bridge); + for (ii = 0; + (ipdef = virNetworkDefGetIpByIndex(network->def, AF_INET6, ii)); + ii++) { + int prefix; + char *netaddr; + + prefix = virNetworkIpDefPrefix(ipdef); + if (prefix < 0) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("bridge '%s' has an invalid prefix"), + network->def->bridge); + goto cleanup; + } + if (!(netaddr = virSocketFormatAddr(&ipdef->address))) + goto cleanup; + virBufferVSprintf(&configbuf, + " prefix %s/%d\n" + " {\n" + " AdvOnLink on;\n" + " AdvAutonomous on;\n" + " AdvRouterAddr off;\n" + " };\n", + netaddr, prefix); + VIR_FREE(netaddr); + } + + virBufferAddLit(&configbuf, "};\n"); + + if (virBufferError(&configbuf)) { + virReportOOMError(); + goto cleanup; + } + if (!(configstr = virBufferContentAndReset(&configbuf))) { + virReportOOMError(); + goto cleanup; + } + + /* construct the filename */ + if (!(configfile = networkRadvdConfigFileName(network->def->name))) { + virReportOOMError(); + goto cleanup; + } + /* write the file */ + if (virFileWriteStr(configfile, configstr, 0600) < 0) { + virReportSystemError(errno, + _("couldn't write radvd config file '%s'"), + configfile); + goto cleanup; + } + + /* prevent radvd from daemonizing itself with "--debug 1", and use + * a dummy pidfile name - virCommand will create the pidfile we + * want to use (this is necessary because radvd's internal + * daemonization and pidfile creation causes a race, and the + * virFileReadPid() below will fail if we use them). + * Unfortunately, it isn't possible to tell radvd to not create + * its own pidfile, so we just let it do so, with a slightly + * different name. Unused, but harmless. + */ + cmd = virCommandNewArgList(RADVD, "--debug", "1", + "--config", configfile, + "--pidfile", NULL); + virCommandAddArgFormat(cmd, "%s-bin", pidfile); + + virCommandSetPidFile(cmd, pidfile); + virCommandDaemonize(cmd); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (virFileReadPid(NETWORK_PID_DIR, radvdpidbase, + &network->radvdPid) < 0) + goto cleanup; + + ret = 0; +cleanup: + virCommandFree(cmd); + VIR_FREE(configfile); + VIR_FREE(configstr); + virBufferFreeAndReset(&configbuf); + VIR_FREE(radvdpidbase); + VIR_FREE(pidfile); + return ret; +} + static int networkAddMasqueradingIptablesRules(struct network_driver *driver, virNetworkObjPtr network, @@ -1154,9 +1321,14 @@ networkReloadIptablesRules(struct network_driver *driver) /* Enable IP Forwarding. Return 0 for success, -1 for failure. */ static int -networkEnableIpForwarding(void) +networkEnableIpForwarding(bool enableIPv4, bool enableIPv6) { - return virFileWriteStr("/proc/sys/net/ipv4/ip_forward", "1\n", 0); + int ret = 0; + if (enableIPv4) + ret = virFileWriteStr("/proc/sys/net/ipv4/ip_forward", "1\n", 0); + if (enableIPv6 && ret == 0) + ret = virFileWriteStr("/proc/sys/net/ipv6/conf/all/forwarding", "1\n", 0); + return ret; } #define SYSCTL_PATH "/proc/sys" @@ -1433,7 +1605,7 @@ networkStartNetworkDaemon(struct network_driver *driver, /* If forwardType != NONE, turn on global IP forwarding */ if (network->def->forwardType != VIR_NETWORK_FORWARD_NONE && - networkEnableIpForwarding() < 0) { + networkEnableIpForwarding(v4present, v6present) < 0) { virReportSystemError(errno, "%s", _("failed to enable IP forwarding")); goto err3; @@ -1444,15 +1616,28 @@ networkStartNetworkDaemon(struct network_driver *driver, if (v4present && networkStartDhcpDaemon(network) < 0) goto err3; + /* start radvd if there are any ipv6 addresses */ + if (v6present && networkStartRadvd(network) < 0) + goto err4; + /* Persist the live configuration now we have bridge info */ if (virNetworkSaveConfig(NETWORK_STATE_DIR, network->def) < 0) { - goto err4; + goto err5; } network->active = 1; return 0; + err5: + if (!save_err) + save_err = virSaveLastError(); + + if (network->radvdPid > 0) { + kill(network->radvdPid, SIGTERM); + network->radvdPid = -1; + } + err4: if (!save_err) save_err = virSaveLastError(); @@ -1511,6 +1696,19 @@ static int networkShutdownNetworkDaemon(struct network_driver *driver, unlink(stateFile); VIR_FREE(stateFile); + if (network->radvdPid > 0) { + char *radvdpidbase; + + kill(network->radvdPid, SIGTERM); + /* attempt to delete the pidfile we created */ + if (!(radvdpidbase = networkRadvdPidfileBasename(network->def->name))) { + virReportOOMError(); + } else { + virFileDeletePid(NETWORK_PID_DIR, radvdpidbase); + VIR_FREE(radvdpidbase); + } + } + if (network->dnsmasqPid > 0) kill(network->dnsmasqPid, SIGTERM); @@ -1531,8 +1729,13 @@ static int networkShutdownNetworkDaemon(struct network_driver *driver, if (network->dnsmasqPid > 0 && (kill(network->dnsmasqPid, 0) == 0)) kill(network->dnsmasqPid, SIGKILL); - network->dnsmasqPid = -1; + + if (network->radvdPid > 0 && + (kill(network->radvdPid, 0) == 0)) + kill(network->radvdPid, SIGKILL); + network->radvdPid = -1; + network->active = 0; if (network->newDef) { @@ -1893,6 +2096,27 @@ static int networkUndefine(virNetworkPtr net) { dnsmasqContextFree(dctx); } + if (v6present) { + char *configfile = networkRadvdConfigFileName(network->def->name); + + if (!configfile) { + virReportOOMError(); + goto cleanup; + } + unlink(configfile); + VIR_FREE(configfile); + + char *radvdpidbase = networkRadvdPidfileBasename(network->def->name); + + if (!(radvdpidbase)) { + virReportOOMError(); + goto cleanup; + } + virFileDeletePid(NETWORK_PID_DIR, radvdpidbase); + VIR_FREE(radvdpidbase); + + } + virNetworkRemoveInactive(&driver->networks, network); network = NULL;