network: check accept_ra before enabling ipv6 forwarding

When enabling IPv6 on all interfaces, we may get the host Router
Advertisement routes discarded. To avoid this, the user needs to set
accept_ra to 2 for the interfaces with such routes.

See https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
on this topic.

To avoid user mistakenly losing routes on their hosts, check
accept_ra values before enabling IPv6 forwarding. If a RA route is
detected, but neither the corresponding device nor global accept_ra
is set to 2, the network will fail to start.
This commit is contained in:
Cédric Bosdonnat 2017-03-03 14:14:51 +01:00
parent 5dd607059d
commit 00d28a78b5
4 changed files with 171 additions and 5 deletions

View File

@ -2067,6 +2067,7 @@ virNetDevBridgeSetVlanFiltering;
virNetDevIPAddrAdd; virNetDevIPAddrAdd;
virNetDevIPAddrDel; virNetDevIPAddrDel;
virNetDevIPAddrGet; virNetDevIPAddrGet;
virNetDevIPCheckIPv6Forwarding;
virNetDevIPInfoAddToDev; virNetDevIPInfoAddToDev;
virNetDevIPInfoClear; virNetDevIPInfoClear;
virNetDevIPRouteAdd; virNetDevIPRouteAdd;

View File

@ -61,6 +61,7 @@
#include "virlog.h" #include "virlog.h"
#include "virdnsmasq.h" #include "virdnsmasq.h"
#include "configmake.h" #include "configmake.h"
#include "virnetlink.h"
#include "virnetdev.h" #include "virnetdev.h"
#include "virnetdevip.h" #include "virnetdevip.h"
#include "virnetdevbridge.h" #include "virnetdevbridge.h"
@ -2389,12 +2390,17 @@ networkStartNetworkVirtual(virNetworkDriverStatePtr driver,
} }
/* If forward.type != NONE, turn on global IP forwarding */ /* If forward.type != NONE, turn on global IP forwarding */
if (network->def->forward.type != VIR_NETWORK_FORWARD_NONE && if (network->def->forward.type != VIR_NETWORK_FORWARD_NONE) {
networkEnableIPForwarding(v4present, v6present) < 0) { if (!virNetDevIPCheckIPv6Forwarding())
goto err3; /* Precise error message already provided */
if (networkEnableIPForwarding(v4present, v6present) < 0) {
virReportSystemError(errno, "%s", virReportSystemError(errno, "%s",
_("failed to enable IP forwarding")); _("failed to enable IP forwarding"));
goto err3; goto err3;
} }
}
/* start dnsmasq if there are any IP addresses (v4 or v6) */ /* start dnsmasq if there are any IP addresses (v4 or v6) */

View File

@ -508,6 +508,158 @@ virNetDevIPWaitDadFinish(virSocketAddrPtr *addrs, size_t count)
return ret; return ret;
} }
static int
virNetDevIPGetAcceptRA(const char *ifname)
{
char *path = NULL;
char *buf = NULL;
char *suffix;
int accept_ra = -1;
if (virAsprintf(&path, "/proc/sys/net/ipv6/conf/%s/accept_ra",
ifname ? ifname : "all") < 0)
goto cleanup;
if ((virFileReadAll(path, 512, &buf) < 0) ||
(virStrToLong_i(buf, &suffix, 10, &accept_ra) < 0))
goto cleanup;
cleanup:
VIR_FREE(path);
VIR_FREE(buf);
return accept_ra;
}
struct virNetDevIPCheckIPv6ForwardingData {
bool hasRARoutes;
/* Devices with conflicting accept_ra */
char **devices;
size_t ndevices;
};
static int
virNetDevIPCheckIPv6ForwardingCallback(const struct nlmsghdr *resp,
void *opaque)
{
struct rtmsg *rtmsg = NLMSG_DATA(resp);
int accept_ra = -1;
struct rtattr *rta;
char *ifname = NULL;
struct virNetDevIPCheckIPv6ForwardingData *data = opaque;
int ret = 0;
int len = RTM_PAYLOAD(resp);
int oif = -1;
/* Ignore messages other than route ones */
if (resp->nlmsg_type != RTM_NEWROUTE)
return ret;
/* Extract a few attributes */
for (rta = RTM_RTA(rtmsg); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
switch (rta->rta_type) {
case RTA_OIF:
oif = *(int *)RTA_DATA(rta);
if (!(ifname = virNetDevGetName(oif)))
goto error;
break;
}
}
/* No need to do anything else for non RA routes */
if (rtmsg->rtm_protocol != RTPROT_RA)
goto cleanup;
data->hasRARoutes = true;
/* Check the accept_ra value for the interface */
accept_ra = virNetDevIPGetAcceptRA(ifname);
VIR_DEBUG("Checking route for device %s, accept_ra: %d", ifname, accept_ra);
if (accept_ra != 2 && VIR_APPEND_ELEMENT(data->devices, data->ndevices, ifname) < 0)
goto error;
cleanup:
VIR_FREE(ifname);
return ret;
error:
ret = -1;
goto cleanup;
}
bool
virNetDevIPCheckIPv6Forwarding(void)
{
struct nl_msg *nlmsg = NULL;
bool valid = false;
struct rtgenmsg genmsg;
size_t i;
struct virNetDevIPCheckIPv6ForwardingData data = {
.hasRARoutes = false,
.devices = NULL,
.ndevices = 0
};
/* Prepare the request message */
if (!(nlmsg = nlmsg_alloc_simple(RTM_GETROUTE,
NLM_F_REQUEST | NLM_F_DUMP))) {
virReportOOMError();
goto cleanup;
}
memset(&genmsg, 0, sizeof(genmsg));
genmsg.rtgen_family = AF_INET6;
if (nlmsg_append(nlmsg, &genmsg, sizeof(genmsg), NLMSG_ALIGNTO) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("allocated netlink buffer is too small"));
goto cleanup;
}
/* Send the request and loop over the responses */
if (virNetlinkDumpCommand(nlmsg, virNetDevIPCheckIPv6ForwardingCallback,
0, 0, NETLINK_ROUTE, 0, &data) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Failed to loop over IPv6 routes"));
goto cleanup;
}
valid = !data.hasRARoutes || data.ndevices == 0;
/* Check the global accept_ra if at least one isn't set on a
per-device basis */
if (!valid && data.hasRARoutes) {
int accept_ra = virNetDevIPGetAcceptRA(NULL);
valid = accept_ra == 2;
VIR_DEBUG("Checked global accept_ra: %d", accept_ra);
}
if (!valid) {
virBuffer buf = VIR_BUFFER_INITIALIZER;
for (i = 0; i < data.ndevices; i++) {
virBufferAdd(&buf, data.devices[i], -1);
if (i < data.ndevices - 1)
virBufferAddLit(&buf, ", ");
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Check the host setup: enabling IPv6 forwarding with "
"RA routes without accept_ra set to 2 is likely to cause "
"routes loss. Interfaces to look at: %s"),
virBufferCurrentContent(&buf));
virBufferFreeAndReset(&buf);
}
cleanup:
nlmsg_free(nlmsg);
for (i = 0; i < data.ndevices; i++)
VIR_FREE(data.devices[i]);
return valid;
}
#else /* defined(__linux__) && defined(HAVE_LIBNL) */ #else /* defined(__linux__) && defined(HAVE_LIBNL) */
@ -655,6 +807,12 @@ virNetDevIPWaitDadFinish(virSocketAddrPtr *addrs ATTRIBUTE_UNUSED,
return -1; return -1;
} }
bool
virNetDevIPCheckIPv6Forwarding(void)
{
VIR_WARN("built without libnl: unable to check if IPv6 forwarding can be safely enabled");
return true;
}
#endif /* defined(__linux__) && defined(HAVE_LIBNL) */ #endif /* defined(__linux__) && defined(HAVE_LIBNL) */

View File

@ -83,6 +83,7 @@ int virNetDevIPAddrGet(const char *ifname, virSocketAddrPtr addr)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
int virNetDevIPWaitDadFinish(virSocketAddrPtr *addrs, size_t count) int virNetDevIPWaitDadFinish(virSocketAddrPtr *addrs, size_t count)
ATTRIBUTE_NONNULL(1); ATTRIBUTE_NONNULL(1);
bool virNetDevIPCheckIPv6Forwarding(void);
/* virNetDevIPRoute object */ /* virNetDevIPRoute object */
void virNetDevIPRouteFree(virNetDevIPRoutePtr def); void virNetDevIPRouteFree(virNetDevIPRoutePtr def);