On 03/03/2017 10:00 AM, Cédric Bosdonnat wrote:
> 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 loosing routes on their hosts, check
s/loosing/losing/
> 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.
Is there any safe way to deal with it automatically? (apologies if I missed the
end of any discussion - I recall your initial inquiry on the subject, but
nothing after that)
> ---
> src/network/bridge_driver.c | 178
> ++++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 173 insertions(+), 5 deletions(-)
>
> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
> index 3f6561055..1ac837f7f 100644
> --- a/src/network/bridge_driver.c
> +++ b/src/network/bridge_driver.c
> @@ -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"
> @@ -2067,6 +2068,168 @@ networkReloadFirewallRules(virNetworkDriverStatePtr
> driver)
> NULL);
> }
>
> +static int
> +networkGetAcceptRA(const char *ifname)
> +{
> + char *path = NULL;
> + char *buf = NULL;
> + char *suffix;
> + int accept_ra = -1;
> +
> + if (virAsprintf(&path, SYSCTL_PATH "/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;
> +}
Although there is some sysfs-specific stuff in the network directory already,
I think I'd prefer to have this in util/virnetdevip.c.
> +
> +#if defined(__linux__) && defined(HAVE_LIBNL)
> +struct networkIPv6CheckData {
> + bool hasRARoutes;
> +
> + /* Devices with conflicting accept_ra */
> + char **devices;
> + size_t ndevices;
> +};
> +
> +static int
> +networkCheckIPv6ForwardingCallback(const struct nlmsghdr *resp,
> + void *opaque)
> +{
> + struct rtmsg *rtmsg = NLMSG_DATA(resp);
> + int accept_ra = -1;
> + struct rtattr *rta;
> + char *ifname = NULL;
> + char name[IFNAMSIZ];
> + struct networkIPv6CheckData *data = opaque;
> + int ret = 0;
I understand why you defaulted ret = 0 instead of our normal -1. It does force
me to actually think about each goto cleanup though...
> + int len = RTM_PAYLOAD(resp);
> + int oif = -1;
> +
> + /* Ignore messages other than route ones */
> + if (resp->nlmsg_type != RTM_NEWROUTE)
> + return ret;
> +
> + memset(&name, 0, sizeof(name));
> +
> + /* 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 (!if_indextoname(oif, name) || VIR_STRDUP(ifname, name) < 0)
> + VIR_DEBUG("Failed to convert OIF to a device name");
Do you really want to stop looking, but also still return success? Maybe it
should fail? Or maybe it's okay to succeed, but the VIR_DEBUG() should be a
VIR_WARN. (I'm not sure, that's why I'm asking :-)
(The one situation where I've seen an ifindex from the kernel fail to resolve
into an interface name was when something "went wrong" in the kernel and
macvtap interfaces showed themselves as attached to an ifindex that was no
longer visible as a named network device. We never did figure out why (it only
happened on a host that was part of a financial hosting service, so our
customer wasn't allowed to give us access, and also couldn't install debug
builds, so our troubleshooting capabilities were *very* limited), but the one
certain thing is that the networking that involved these "nameless" interfaces
didn't work (although other interfaces on the system did).
Also, if VIR_STRDUP() fails you will have logged an error but still returned
success. Maybe you should check that separately and return error (assuming
Finally, maybe we should wrap if_indextoname() in a virNetDevGetName()
(...FromIndex() ?) both for symmetry with virNetDevGetIndex() and for possible
future use by other functions.
> + 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 = networkGetAcceptRA(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)
> + ret = -1;
Okay, so this callback is gathering a list of all interfaces that have a route
but accept_ra != 2.
> +
> + cleanup:
> + VIR_FREE(ifname);
> + return ret;
> +}
> +
> +static bool
> +networkCheckIPv6Forwarding(void)
> +{
> + struct nl_msg *nlmsg = NULL;
> + bool valid = false;
> + struct rtgenmsg genmsg;
> + size_t i;
> + struct networkIPv6CheckData 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, networkCheckIPv6ForwardingCallback, 0,
> 0,
> + NETLINK_ROUTE, 0, &data) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Failed to loop over 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 = networkGetAcceptRA(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) */
> +
> +static bool
> +networkCheckIPv6Forwarding(void)
> +{
> + VIR_WARN("built without libnl: unable to check if IPv6 forwarding can be
> safely enabled");
> + return true;
> +}
> +#endif /* defined(__linux__) && defined(HAVE_LIBNL) */
> +
> /* Enable IP Forwarding. Return 0 for success, -1 for failure. */
> static int
> networkEnableIPForwarding(bool enableIPv4, bool enableIPv6)
> @@ -2377,11 +2540,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 (!networkCheckIPv6Forwarding())
> + goto err3; /* Precise error message already provided */
> +
> +
> + if (networkEnableIPForwarding(v4present, v6present) < 0) {
> + virReportSystemError(errno, "%s",
> + _("failed to enable IP forwarding"));
> + goto err3;
> + }
> }
>
>
I generally understand the logic, which looks fine. As I said above, I think it
would be better to have most of it in virnetdevip.c (yes, I think the existing
stuff that plays with ip_forward might also be better moved into utility
functions in virnetdevip.c, but that's not your problem :-)
--
libvir-list mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/libvir-list