Updated diff, I reworked the logic to handle the if_get/if_put dance in vxlan_multicast_join(), and fixed an uninitialized variable.
Ok ? Index: net/if_vxlan.c =================================================================== RCS file: /cvs/src/sys/net/if_vxlan.c,v retrieving revision 1.51 diff -u -p -r1.51 if_vxlan.c --- net/if_vxlan.c 25 Oct 2016 16:31:08 -0000 1.51 +++ net/if_vxlan.c 5 Nov 2016 11:36:02 -0000 @@ -47,6 +47,8 @@ #include <netinet/udp_var.h> #include <netinet/in_pcb.h> +#include <netinet6/in6_var.h> + #if NPF > 0 #include <net/pfvar.h> #endif @@ -61,7 +63,12 @@ struct vxlan_softc { struct arpcom sc_ac; struct ifmedia sc_media; - struct ip_moptions sc_imo; + union { + struct ip_moptions u_imo; + struct ip6_moptions u_imo6; + } sc_imu; +#define sc_imo sc_imu.u_imo +#define sc_im6o sc_imu.u_imo6 void *sc_ahcookie; void *sc_lhcookie; void *sc_dhcookie; @@ -129,10 +136,6 @@ vxlan_clone_create(struct if_clone *ifc, M_DEVBUF, M_NOWAIT|M_ZERO)) == NULL) return (ENOMEM); - sc->sc_imo.imo_membership = malloc( - (sizeof(struct in_multi *) * IP_MIN_MEMBERSHIPS), M_IPMOPTS, - M_WAITOK|M_ZERO); - sc->sc_imo.imo_max_memberships = IP_MIN_MEMBERSHIPS; sc->sc_dstport = htons(VXLAN_PORT); sc->sc_vnetid = VXLAN_VNI_UNSET; @@ -190,7 +193,6 @@ vxlan_clone_destroy(struct ifnet *ifp) ifmedia_delete_instance(&sc->sc_media, IFM_INST_ANY); ether_ifdetach(ifp); if_detach(ifp); - free(sc->sc_imo.imo_membership, M_IPMOPTS, 0); free(sc, M_DEVBUF, sizeof(*sc)); return (0); @@ -199,11 +201,33 @@ vxlan_clone_destroy(struct ifnet *ifp) void vxlan_multicast_cleanup(struct ifnet *ifp) { - struct vxlan_softc *sc = (struct vxlan_softc *)ifp->if_softc; - struct ip_moptions *imo = &sc->sc_imo; - struct ifnet *mifp; + struct vxlan_softc *sc = (struct vxlan_softc *)ifp->if_softc; + struct ip_moptions *imo; + struct in_multi **imm; + struct ip6_moptions *im6o; + struct in6_multi_mship *im6m, *im6m_next; + struct ifnet *mifp = NULL; + + switch (sc->sc_dst.ss_family) { + case AF_INET: + imo = &sc->sc_imo; + mifp = if_get(imo->imo_ifidx); + imm = imo->imo_membership; + while (imo->imo_num_memberships > 0) + in_delmulti(imm[--imo->imo_num_memberships]); + free(imm, M_IPMOPTS, + sizeof(struct in_multi *) * imo->imo_num_memberships); + break; + case AF_INET6: + im6o = &sc->sc_im6o; + mifp = if_get(im6o->im6o_ifidx); + LIST_FOREACH_SAFE(im6m, &im6o->im6o_memberships, i6mm_chain, + im6m_next) + in6_leavegroup(im6m); + break; + } + bzero(&sc->sc_imu, sizeof(sc->sc_imu)); - mifp = if_get(imo->imo_ifidx); if (mifp != NULL) { if (sc->sc_ahcookie != NULL) { hook_disestablish(mifp->if_addrhooks, sc->sc_ahcookie); @@ -219,14 +243,9 @@ vxlan_multicast_cleanup(struct ifnet *if sc->sc_dhcookie); sc->sc_dhcookie = NULL; } - - if_put(mifp); } - if (imo->imo_num_memberships > 0) { - in_delmulti(imo->imo_membership[--imo->imo_num_memberships]); - imo->imo_ifidx = 0; - } + if_put(mifp); } int @@ -234,55 +253,141 @@ vxlan_multicast_join(struct ifnet *ifp, struct sockaddr *dst) { struct vxlan_softc *sc = ifp->if_softc; - struct ip_moptions *imo = &sc->sc_imo; + struct ip_moptions *imo; + struct ip6_moptions *im6o; + struct in6_multi_mship *im6m; struct sockaddr_in *src4, *dst4; #ifdef INET6 - struct sockaddr_in6 *dst6; + struct sockaddr_in6 *src6, *dst6; #endif /* INET6 */ struct ifaddr *ifa; - struct ifnet *mifp; + struct ifnet *mifp = NULL, *m6ifp = NULL; + struct rtentry *rt; + int error; switch (dst->sa_family) { case AF_INET: dst4 = satosin(dst); + src4 = satosin(src); if (!IN_MULTICAST(dst4->sin_addr.s_addr)) return (0); + if (src4->sin_addr.s_addr == INADDR_ANY || + IN_MULTICAST(src4->sin_addr.s_addr)) + return (EINVAL); + if ((ifa = ifa_ifwithaddr(src, sc->sc_rdomain)) == NULL || + (mifp = ifa->ifa_ifp) == NULL || + (mifp->if_flags & IFF_MULTICAST) == 0) + return (EADDRNOTAVAIL); + + imo = &sc->sc_imo; + imo->imo_membership = mallocarray(sizeof(struct in_multi *), + IP_MIN_MEMBERSHIPS, M_IPMOPTS, M_WAITOK|M_ZERO); + if (imo->imo_membership == NULL || + (imo->imo_membership[0] = + in_addmulti(&dst4->sin_addr, mifp)) == NULL) { + free(imo->imo_membership, M_IPMOPTS, + sizeof(struct in_multi *) * IP_MIN_MEMBERSHIPS); + return (ENOBUFS); + } + imo->imo_max_memberships = IP_MIN_MEMBERSHIPS; + imo->imo_num_memberships++; + imo->imo_ifidx = mifp->if_index; + if (sc->sc_ttl > 0) + imo->imo_ttl = sc->sc_ttl; + else + imo->imo_ttl = IP_DEFAULT_MULTICAST_TTL; + imo->imo_loop = 0; break; #ifdef INET6 case AF_INET6: dst6 = satosin6(dst); + src6 = satosin6(src); if (!IN6_IS_ADDR_MULTICAST(&dst6->sin6_addr)) return (0); + if (IN6_IS_ADDR_UNSPECIFIED(&src6->sin6_addr) || + IN6_IS_ADDR_MULTICAST(&src6->sin6_addr)) + return (EINVAL); + if (IN6_IS_ADDR_MC_LINKLOCAL(&dst6->sin6_addr) || + IN6_IS_ADDR_MC_INTFACELOCAL(&dst6->sin6_addr)) { + /* Scoped mcast destination : get index from dst6 */ + if (dst6->sin6_scope_id) + m6ifp = if_get(dst6->sin6_scope_id); + } else { + /* + * unscoped multicast destination : get ifindex + * from src6, or routing table as fallack + */ + if (src6->sin6_scope_id) + m6ifp = if_get(src6->sin6_scope_id); + if (m6ifp == NULL) { + rt = rtalloc(dst, RT_RESOLVE, sc->sc_rdomain); + if (rt) + m6ifp = if_get(rt->rt_ifidx); + rtfree(rt); + } + } + if (!m6ifp || !ISSET(m6ifp->if_flags, IFF_MULTICAST)) { + if_put(m6ifp); + return (EADDRNOTAVAIL); + } + + if ((error = in6_embedscope(&src6->sin6_addr, src6, NULL)) || + (error = in6_embedscope(&dst6->sin6_addr, dst6, NULL))) { + if_put(m6ifp); + return (error); + } + /* do we own this address ? */ + rt = rtalloc(src, 0, sc->sc_rdomain); + if (!rtisvalid(rt) || !ISSET(rt->rt_flags, RTF_LOCAL)) { + if_put(m6ifp); + rtfree(rt); + return (EADDRNOTAVAIL); + } + rtfree(rt); - /* Multicast mode is currently not supported for IPv6 */ - return (EAFNOSUPPORT); + /* + * If src6 has a global scope, then we do no further checks + * because whichever interface we reach on the remote + * endpoint, the EP will have all it needs to reach us back on + * unicast should it decide to do so (think dynamic and + * multipoint). + * + * However, if the source address has a scope smaller than + * global, we must ensure that the source address is inside + * the destination address' zone. + */ + if (IN6_IS_ADDR_LINKLOCAL(&src6->sin6_addr) || + IN6_IS_ADDR_SITELOCAL(&src6->sin6_addr)) { + /* + * As a first shot, let's say that sin6_scope_id + * must be equal. + */ + if (src6->sin6_scope_id != dst6->sin6_scope_id) { + if_put(m6ifp); + return (EADDRNOTAVAIL); + } + } + + im6o = &sc->sc_im6o; + im6o->im6o_ifidx = m6ifp->if_index; + im6m = in6_joingroup(m6ifp, &dst6->sin6_addr, &error); + if (!im6m) { + if_put(m6ifp); + return (error); + } + LIST_INSERT_HEAD(&im6o->im6o_memberships, im6m, i6mm_chain); + if (sc->sc_ttl > 0) + im6o->im6o_hlim = sc->sc_ttl; + else + im6o->im6o_hlim = ip6_defmcasthlim; + im6o->im6o_loop = 0; + mifp = m6ifp; + break; #endif /* INET6 */ default: return (EAFNOSUPPORT); } - src4 = satosin(src); - dst4 = satosin(dst); - - if (src4->sin_addr.s_addr == INADDR_ANY || - IN_MULTICAST(src4->sin_addr.s_addr)) - return (EINVAL); - if ((ifa = ifa_ifwithaddr(src, sc->sc_rdomain)) == NULL || - (mifp = ifa->ifa_ifp) == NULL || - (mifp->if_flags & IFF_MULTICAST) == 0) - return (EADDRNOTAVAIL); - - if ((imo->imo_membership[0] = - in_addmulti(&dst4->sin_addr, mifp)) == NULL) - return (ENOBUFS); - - imo->imo_num_memberships++; - imo->imo_ifidx = mifp->if_index; - if (sc->sc_ttl > 0) - imo->imo_ttl = sc->sc_ttl; - else - imo->imo_ttl = IP_DEFAULT_MULTICAST_TTL; - imo->imo_loop = 0; /* * Use interface hooks to track any changes on the interface @@ -297,6 +402,8 @@ vxlan_multicast_join(struct ifnet *ifp, panic("%s: cannot allocate interface hook", mifp->if_xname); + if_put(m6ifp); + return (0); } @@ -902,7 +1009,8 @@ vxlan_output(struct ifnet *ifp, struct m break; #ifdef INET6 case AF_INET6: - error = ip6_output(m, 0, NULL, IPV6_MINMTU, 0, NULL); + error = ip6_output(m, 0, NULL, IPV6_MINMTU, + &sc->sc_im6o, NULL); break; #endif /* INET6 */ default: