mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-22 03:12:22 +00:00
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:
parent
5dd607059d
commit
00d28a78b5
@ -2067,6 +2067,7 @@ virNetDevBridgeSetVlanFiltering;
|
||||
virNetDevIPAddrAdd;
|
||||
virNetDevIPAddrDel;
|
||||
virNetDevIPAddrGet;
|
||||
virNetDevIPCheckIPv6Forwarding;
|
||||
virNetDevIPInfoAddToDev;
|
||||
virNetDevIPInfoClear;
|
||||
virNetDevIPRouteAdd;
|
||||
|
@ -61,6 +61,7 @@
|
||||
#include "virlog.h"
|
||||
#include "virdnsmasq.h"
|
||||
#include "configmake.h"
|
||||
#include "virnetlink.h"
|
||||
#include "virnetdev.h"
|
||||
#include "virnetdevip.h"
|
||||
#include "virnetdevbridge.h"
|
||||
@ -2389,11 +2390,16 @@ networkStartNetworkVirtual(virNetworkDriverStatePtr driver,
|
||||
}
|
||||
|
||||
/* If forward.type != NONE, turn on global IP forwarding */
|
||||
if (network->def->forward.type != VIR_NETWORK_FORWARD_NONE &&
|
||||
networkEnableIPForwarding(v4present, v6present) < 0) {
|
||||
virReportSystemError(errno, "%s",
|
||||
_("failed to enable IP forwarding"));
|
||||
goto err3;
|
||||
if (network->def->forward.type != VIR_NETWORK_FORWARD_NONE) {
|
||||
if (!virNetDevIPCheckIPv6Forwarding())
|
||||
goto err3; /* Precise error message already provided */
|
||||
|
||||
|
||||
if (networkEnableIPForwarding(v4present, v6present) < 0) {
|
||||
virReportSystemError(errno, "%s",
|
||||
_("failed to enable IP forwarding"));
|
||||
goto err3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -508,6 +508,158 @@ virNetDevIPWaitDadFinish(virSocketAddrPtr *addrs, size_t count)
|
||||
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) */
|
||||
|
||||
@ -655,6 +807,12 @@ virNetDevIPWaitDadFinish(virSocketAddrPtr *addrs ATTRIBUTE_UNUSED,
|
||||
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) */
|
||||
|
||||
|
@ -83,6 +83,7 @@ int virNetDevIPAddrGet(const char *ifname, virSocketAddrPtr addr)
|
||||
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
|
||||
int virNetDevIPWaitDadFinish(virSocketAddrPtr *addrs, size_t count)
|
||||
ATTRIBUTE_NONNULL(1);
|
||||
bool virNetDevIPCheckIPv6Forwarding(void);
|
||||
|
||||
/* virNetDevIPRoute object */
|
||||
void virNetDevIPRouteFree(virNetDevIPRoutePtr def);
|
||||
|
Loading…
x
Reference in New Issue
Block a user