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.
This commit is contained in:
Laine Stump 2010-12-20 01:14:11 -05:00
parent 6ccce75240
commit 8090a56890
3 changed files with 249 additions and 20 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;