On 03/15/2017 10:45 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 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.

Since I already asked the question and didn't hear a positive response,
I'm guessing "no news is bad news", i.e. there isn't a reliable way to
fix this problem automatically. Assuming that, reporting the problem and
failing seems like the next best (least worse) thing...


> ---
>  src/libvirt_private.syms    |   1 +
>  src/network/bridge_driver.c |  16 +++--
>  src/util/virnetdevip.c      | 158 
> ++++++++++++++++++++++++++++++++++++++++++++
>  src/util/virnetdevip.h      |   1 +
>  4 files changed, 171 insertions(+), 5 deletions(-)
> 
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index 0fe88c3fa..ec6553520 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -2056,6 +2056,7 @@ virNetDevBridgeSetVlanFiltering;
>  virNetDevIPAddrAdd;
>  virNetDevIPAddrDel;
>  virNetDevIPAddrGet;
> +virNetDevIPCheckIPv6Forwarding;
>  virNetDevIPInfoAddToDev;
>  virNetDevIPInfoClear;
>  virNetDevIPRouteAdd;
> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
> index 3f6561055..d02cd19f9 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"
> @@ -2377,11 +2378,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;
> +        }
>      }
>  
>  
> diff --git a/src/util/virnetdevip.c b/src/util/virnetdevip.c
> index 42fbba1eb..a4d382427 100644
> --- a/src/util/virnetdevip.c
> +++ b/src/util/virnetdevip.c
> @@ -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;
> +}


Thanks for remembering this stub (I usually forget it).

>  
>  #endif /* defined(__linux__) && defined(HAVE_LIBNL) */
>  
> diff --git a/src/util/virnetdevip.h b/src/util/virnetdevip.h
> index b7abdf94d..cc466ca25 100644
> --- a/src/util/virnetdevip.h
> +++ b/src/util/virnetdevip.h
> @@ -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);
> 

ACK. Nicely done! +++ Would review again :-)

--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list

Reply via email to