diff --git a/src/util/virnetdevip.c b/src/util/virnetdevip.c index 7bd5a75f85..4887ccfcf1 100644 --- a/src/util/virnetdevip.c +++ b/src/util/virnetdevip.c @@ -43,8 +43,11 @@ #ifdef __linux__ # include # include +# include #endif +#define PROC_NET_IPV6_ROUTE "/proc/net/ipv6_route" + #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("util.netdevip"); @@ -370,203 +373,6 @@ virNetDevIPRouteAdd(const char *ifname, } -static int -virNetDevIPGetAcceptRA(const char *ifname) -{ - g_autofree char *path = NULL; - g_autofree char *buf = NULL; - char *suffix; - int accept_ra = -1; - - path = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/accept_ra", - ifname ? ifname : "all"); - - if ((virFileReadAll(path, 512, &buf) < 0) || - (virStrToLong_i(buf, &suffix, 10, &accept_ra) < 0)) - return -1; - - return accept_ra; -} - -struct virNetDevIPCheckIPv6ForwardingData { - bool hasRARoutes; - - /* Devices with conflicting accept_ra */ - char **devices; - size_t ndevices; -}; - - -static int -virNetDevIPCheckIPv6ForwardingAddIF(struct virNetDevIPCheckIPv6ForwardingData *data, - char **ifname) -{ - size_t i; - - /* add ifname to the array if it's not already there - * (ifname is char** so VIR_APPEND_ELEMENT() will move the - * original pointer out of the way and avoid having it freed) - */ - for (i = 0; i < data->ndevices; i++) { - if (STREQ(data->devices[i], *ifname)) - return 0; - } - return VIR_APPEND_ELEMENT(data->devices, data->ndevices, *ifname); -} - - -static int -virNetDevIPCheckIPv6ForwardingCallback(struct nlmsghdr *resp, - void *opaque) -{ - struct rtmsg *rtmsg = NLMSG_DATA(resp); - struct virNetDevIPCheckIPv6ForwardingData *data = opaque; - struct rtattr *rta_attr; - int accept_ra = -1; - int ifindex = -1; - g_autofree char *ifname = NULL; - - /* Ignore messages other than route ones */ - if (resp->nlmsg_type != RTM_NEWROUTE) - return 0; - - /* No need to do anything else for non RA routes */ - if (rtmsg->rtm_protocol != RTPROT_RA) - return 0; - - rta_attr = (struct rtattr *)nlmsg_find_attr(resp, sizeof(struct rtmsg), RTA_OIF); - if (rta_attr) { - /* This is a single path route, with interface used to reach - * nexthop in the RTA_OIF attribute. - */ - ifindex = *(int *)RTA_DATA(rta_attr); - ifname = virNetDevGetName(ifindex); - - if (!ifname) - return -1; - - accept_ra = virNetDevIPGetAcceptRA(ifname); - - VIR_DEBUG("Checking route for device %s (%d), accept_ra: %d", - ifname, ifindex, accept_ra); - - if (accept_ra != 2 && virNetDevIPCheckIPv6ForwardingAddIF(data, &ifname) < 0) - return -1; - - data->hasRARoutes = true; - return 0; - } - - /* if no RTA_OIF was found, see if this is a multipath route (one - * which has an array of nexthops, each with its own interface) - */ - - rta_attr = (struct rtattr *)nlmsg_find_attr(resp, sizeof(struct rtmsg), RTA_MULTIPATH); - if (rta_attr) { - /* The data of the attribute is an array of rtnexthop */ - struct rtnexthop *nh = RTA_DATA(rta_attr); - size_t len = RTA_PAYLOAD(rta_attr); - - /* validate the attribute array length */ - len = MIN(len, ((char *)resp + NLMSG_PAYLOAD(resp, 0) - (char *)rta_attr)); - - while (len >= sizeof(*nh) && len >= nh->rtnh_len) { - /* check accept_ra for the interface of each nexthop */ - - ifname = virNetDevGetName(nh->rtnh_ifindex); - - if (!ifname) - return -1; - - accept_ra = virNetDevIPGetAcceptRA(ifname); - - VIR_DEBUG("Checking multipath route nexthop device %s (%d), accept_ra: %d", - ifname, nh->rtnh_ifindex, accept_ra); - - if (accept_ra != 2 && virNetDevIPCheckIPv6ForwardingAddIF(data, &ifname) < 0) - return -1; - - VIR_FREE(ifname); - data->hasRARoutes = true; - - len -= NLMSG_ALIGN(nh->rtnh_len); - VIR_WARNINGS_NO_CAST_ALIGN - nh = RTNH_NEXT(nh); - VIR_WARNINGS_RESET - } - } - - return 0; -} - -bool -virNetDevIPCheckIPv6Forwarding(void) -{ - bool valid = false; - struct rtgenmsg genmsg; - size_t i; - struct virNetDevIPCheckIPv6ForwardingData data = { - .hasRARoutes = false, - .devices = NULL, - .ndevices = 0 - }; - g_autoptr(virNetlinkMsg) nlmsg = NULL; - - - /* 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) { - g_auto(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)); - } - - cleanup: - virStringListFreeCount(data.devices, data.ndevices); - return valid; -} - #else /* defined(__linux__) && defined(WITH_LIBNL) */ @@ -688,15 +494,132 @@ virNetDevIPRouteAdd(const char *ifname, return 0; } +#endif /* defined(__linux__) && defined(HAVE_LIBNL) */ + + +#if defined(__linux__) + +static int +virNetDevIPGetAcceptRA(const char *ifname) +{ + g_autofree char *path = NULL; + g_autofree char *buf = NULL; + char *suffix; + int accept_ra = -1; + + path = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/accept_ra", + ifname ? ifname : "all"); + + if ((virFileReadAll(path, 512, &buf) < 0) || + (virStrToLong_i(buf, &suffix, 10, &accept_ra) < 0)) + return -1; + + return accept_ra; +} + +/** + * virNetDevIPCheckIPv6Forwarding + * + * This function checks if IPv6 routes have the RTF_ADDRCONF flag set, + * indicating they have been created by the kernel's RA configuration + * handling. These routes are subject to being flushed when ipv6 + * forwarding is enabled unless accept_ra is explicitly set to "2". + * This will most likely result in ipv6 networking being broken. + * + * Returns: true if it is safe to enable forwarding, or false if + * breakable routes are found. + * + **/ +bool +virNetDevIPCheckIPv6Forwarding(void) +{ + int len; + char *cur; + g_autofree char *buf = NULL; + /* lines are 150 chars */ + enum {MAX_ROUTE_SIZE = 150*100000}; + + /* This is /proc/sys/net/ipv6/conf/all/accept_ra */ + int all_accept_ra = virNetDevIPGetAcceptRA(NULL); + + /* Read ipv6 routes */ + if ((len = virFileReadAll(PROC_NET_IPV6_ROUTE, + MAX_ROUTE_SIZE, &buf)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to read %s " + "for ipv6 forwarding checks"), PROC_NET_IPV6_ROUTE); + return false; + } + + /* Dropping the last character to stop the loop */ + if (len > 0) + buf[len-1] = '\0'; + + cur = buf; + while (cur) { + char route[33], flags[9], iface[9]; + unsigned int flags_val; + char *iface_val; + int num; + char *nl = strchr(cur, '\n'); + + if (nl) + *nl++ = '\0'; + + num = sscanf(cur, "%32s %*s %*s %*s %*s %*s %*s %*s %8s %8s", + route, flags, iface); + + cur = nl; + if (num != 3) { + VIR_DEBUG("Failed to parse route line: %s", cur); + continue; + } + + if (virStrToLong_ui(flags, NULL, 16, &flags_val)) { + VIR_DEBUG("Failed to parse flags: %s", flags); + continue; + } + + /* This is right justified, strip leading spaces */ + iface_val = &iface[0]; + while (*iface_val && g_ascii_isspace(*iface_val)) + iface_val++; + + VIR_DEBUG("%s iface %s flags %s : RTF_ADDRCONF %sset", + route, iface_val, flags, + (flags_val & RTF_ADDRCONF ? "" : "not ")); + + if (flags_val & RTF_ADDRCONF) { + int ret = virNetDevIPGetAcceptRA(iface_val); + VIR_DEBUG("%s reports accept_ra of %d", + iface_val, ret); + /* If the interface for this autoconfigured route + * has accept_ra == 1, or it is default and the "all" + * value of accept_ra == 1, it will be subject to + * flushing if forwarding is enabled. + */ + if (ret == 1 || (ret == 0 && all_accept_ra == 1)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Check the host setup: interface %s has kernel " + "autoconfigured IPv6 routes and enabling forwarding " + " without accept_ra set to 2 will cause the kernel " + "to flush them, breaking networking."), iface_val); + return false; + } + } + } + return true; +} +#else bool virNetDevIPCheckIPv6Forwarding(void) { - VIR_WARN("built without libnl: unable to check if IPv6 forwarding can be safely enabled"); + VIR_DEBUG("No checks for IPv6 forwarding issues on non-Linux systems"); return true; } -#endif /* defined(__linux__) && defined(WITH_LIBNL) */ +#endif /* defined(__linux__) */ /**