Fix refcounting leaks in IPv6 MLD code leading to loss of IPv6
connectivity. Looking at past changes in this area like r337866, some refcounting bugs have been introduced, one by one. For example like calling in6m_disconnect() and in6m_rele_locked() in mld_v1_process_group_timer() where previously no disconnect nor refcount decrement was done. Calling in6m_disconnect() when it shouldn't causes IPv6 solitation to no longer work, because all the multicast addresses receiving the solitation messages are now deleted from the network interface. This patch reverts some recent changes while improving the MLD refcounting and concurrency model after the MLD code was converted to using EPOCH(9). List changes: - All CK_STAILQ_FOREACH() macros are now properly enclosed into EPOCH(9) sections. This simplifies assertion of locking inside in6m_ifmultiaddr_get_inm(). - Corrected bad use of in6m_disconnect() leading to loss of IPv6 connectivity for MLD v1. - Factored out checks for valid inm structure into in6m_ifmultiaddr_get_inm(). PR: 233535 Differential Revision: https://reviews.freebsd.org/D18887 Reviewed by: bz (net) Tested by: ae MFC after: 1 week Sponsored by: Mellanox Technologies
This commit is contained in:
parent
dea72f062a
commit
2cd6ad766e
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=343395
@ -854,36 +854,15 @@ in6_tmpaddrtimer(void *arg)
|
|||||||
static void
|
static void
|
||||||
in6_purgemaddrs(struct ifnet *ifp)
|
in6_purgemaddrs(struct ifnet *ifp)
|
||||||
{
|
{
|
||||||
struct in6_multi_head purgeinms;
|
struct in6_multi_head inmh;
|
||||||
struct in6_multi *inm;
|
|
||||||
struct ifmultiaddr *ifma, *next;
|
|
||||||
|
|
||||||
SLIST_INIT(&purgeinms);
|
SLIST_INIT(&inmh);
|
||||||
IN6_MULTI_LOCK();
|
IN6_MULTI_LOCK();
|
||||||
IN6_MULTI_LIST_LOCK();
|
IN6_MULTI_LIST_LOCK();
|
||||||
IF_ADDR_WLOCK(ifp);
|
mld_ifdetach(ifp, &inmh);
|
||||||
/*
|
|
||||||
* Extract list of in6_multi associated with the detaching ifp
|
|
||||||
* which the PF_INET6 layer is about to release.
|
|
||||||
*/
|
|
||||||
restart:
|
|
||||||
CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
|
|
||||||
if (ifma->ifma_addr->sa_family != AF_INET6 ||
|
|
||||||
ifma->ifma_protospec == NULL)
|
|
||||||
continue;
|
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
|
||||||
in6m_disconnect(inm);
|
|
||||||
in6m_rele_locked(&purgeinms, inm);
|
|
||||||
if (__predict_false(ifma6_restart)) {
|
|
||||||
ifma6_restart = false;
|
|
||||||
goto restart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IF_ADDR_WUNLOCK(ifp);
|
|
||||||
mld_ifdetach(ifp);
|
|
||||||
IN6_MULTI_LIST_UNLOCK();
|
IN6_MULTI_LIST_UNLOCK();
|
||||||
IN6_MULTI_UNLOCK();
|
IN6_MULTI_UNLOCK();
|
||||||
in6m_release_list_deferred(&purgeinms);
|
in6m_release_list_deferred(&inmh);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure all multicast deletions invoking if_ioctl() are
|
* Make sure all multicast deletions invoking if_ioctl() are
|
||||||
|
@ -190,7 +190,6 @@ static SYSCTL_NODE(_net_inet6_ip6_mcast, OID_AUTO, filters,
|
|||||||
CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_ip6_mcast_filters,
|
CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_ip6_mcast_filters,
|
||||||
"Per-interface stack-wide source filters");
|
"Per-interface stack-wide source filters");
|
||||||
|
|
||||||
int ifma6_restart = 0;
|
|
||||||
#ifdef KTR
|
#ifdef KTR
|
||||||
/*
|
/*
|
||||||
* Inline function which wraps assertions for a valid ifp.
|
* Inline function which wraps assertions for a valid ifp.
|
||||||
@ -405,6 +404,7 @@ static int
|
|||||||
in6_getmulti(struct ifnet *ifp, const struct in6_addr *group,
|
in6_getmulti(struct ifnet *ifp, const struct in6_addr *group,
|
||||||
struct in6_multi **pinm)
|
struct in6_multi **pinm)
|
||||||
{
|
{
|
||||||
|
struct epoch_tracker et;
|
||||||
struct sockaddr_in6 gsin6;
|
struct sockaddr_in6 gsin6;
|
||||||
struct ifmultiaddr *ifma;
|
struct ifmultiaddr *ifma;
|
||||||
struct in6_multi *inm;
|
struct in6_multi *inm;
|
||||||
@ -420,7 +420,10 @@ in6_getmulti(struct ifnet *ifp, const struct in6_addr *group,
|
|||||||
IN6_MULTI_LOCK_ASSERT();
|
IN6_MULTI_LOCK_ASSERT();
|
||||||
IN6_MULTI_LIST_LOCK();
|
IN6_MULTI_LIST_LOCK();
|
||||||
IF_ADDR_WLOCK(ifp);
|
IF_ADDR_WLOCK(ifp);
|
||||||
|
NET_EPOCH_ENTER(et);
|
||||||
inm = in6m_lookup_locked(ifp, group);
|
inm = in6m_lookup_locked(ifp, group);
|
||||||
|
NET_EPOCH_EXIT(et);
|
||||||
|
|
||||||
if (inm != NULL) {
|
if (inm != NULL) {
|
||||||
/*
|
/*
|
||||||
* If we already joined this group, just bump the
|
* If we already joined this group, just bump the
|
||||||
@ -593,7 +596,7 @@ in6m_release_wait(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
in6m_disconnect(struct in6_multi *inm)
|
in6m_disconnect_locked(struct in6_multi_head *inmh, struct in6_multi *inm)
|
||||||
{
|
{
|
||||||
struct ifnet *ifp;
|
struct ifnet *ifp;
|
||||||
struct ifaddr *ifa;
|
struct ifaddr *ifa;
|
||||||
@ -601,10 +604,12 @@ in6m_disconnect(struct in6_multi *inm)
|
|||||||
struct in6_multi_mship *imm, *imm_tmp;
|
struct in6_multi_mship *imm, *imm_tmp;
|
||||||
struct ifmultiaddr *ifma, *ll_ifma;
|
struct ifmultiaddr *ifma, *ll_ifma;
|
||||||
|
|
||||||
ifp = inm->in6m_ifp;
|
IN6_MULTI_LIST_LOCK_ASSERT();
|
||||||
|
|
||||||
|
ifp = inm->in6m_ifp;
|
||||||
if (ifp == NULL)
|
if (ifp == NULL)
|
||||||
return;
|
return; /* already called */
|
||||||
|
|
||||||
inm->in6m_ifp = NULL;
|
inm->in6m_ifp = NULL;
|
||||||
IF_ADDR_WLOCK_ASSERT(ifp);
|
IF_ADDR_WLOCK_ASSERT(ifp);
|
||||||
ifma = inm->in6m_ifma;
|
ifma = inm->in6m_ifma;
|
||||||
@ -623,7 +628,6 @@ in6m_disconnect(struct in6_multi *inm)
|
|||||||
MPASS(ll_ifma->ifma_llifma == NULL);
|
MPASS(ll_ifma->ifma_llifma == NULL);
|
||||||
MPASS(ll_ifma->ifma_ifp == ifp);
|
MPASS(ll_ifma->ifma_ifp == ifp);
|
||||||
if (--ll_ifma->ifma_refcount == 0) {
|
if (--ll_ifma->ifma_refcount == 0) {
|
||||||
ifma6_restart = true;
|
|
||||||
if (ll_ifma->ifma_flags & IFMA_F_ENQUEUED) {
|
if (ll_ifma->ifma_flags & IFMA_F_ENQUEUED) {
|
||||||
CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifmultiaddr, ifma_link);
|
CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifmultiaddr, ifma_link);
|
||||||
ll_ifma->ifma_flags &= ~IFMA_F_ENQUEUED;
|
ll_ifma->ifma_flags &= ~IFMA_F_ENQUEUED;
|
||||||
@ -641,28 +645,12 @@ in6m_disconnect(struct in6_multi *inm)
|
|||||||
if (inm == imm->i6mm_maddr) {
|
if (inm == imm->i6mm_maddr) {
|
||||||
LIST_REMOVE(imm, i6mm_chain);
|
LIST_REMOVE(imm, i6mm_chain);
|
||||||
free(imm, M_IP6MADDR);
|
free(imm, M_IP6MADDR);
|
||||||
|
in6m_rele_locked(inmh, inm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
in6m_release_deferred(struct in6_multi *inm)
|
|
||||||
{
|
|
||||||
struct in6_multi_head tmp;
|
|
||||||
|
|
||||||
IN6_MULTI_LIST_LOCK_ASSERT();
|
|
||||||
KASSERT(inm->in6m_refcount > 0, ("refcount == %d inm: %p", inm->in6m_refcount, inm));
|
|
||||||
if (--inm->in6m_refcount == 0) {
|
|
||||||
MPASS(inm->in6m_ifp == NULL);
|
|
||||||
SLIST_INIT(&tmp);
|
|
||||||
inm->in6m_ifma->ifma_protospec = NULL;
|
|
||||||
MPASS(inm->in6m_ifma->ifma_llifma == NULL);
|
|
||||||
SLIST_INSERT_HEAD(&tmp, inm, in6m_nrele);
|
|
||||||
in6m_release_list_deferred(&tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
in6m_release_task(void *arg __unused)
|
in6m_release_task(void *arg __unused)
|
||||||
{
|
{
|
||||||
@ -1262,6 +1250,7 @@ in6_joingroup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr,
|
|||||||
/*const*/ struct in6_mfilter *imf, struct in6_multi **pinm,
|
/*const*/ struct in6_mfilter *imf, struct in6_multi **pinm,
|
||||||
const int delay)
|
const int delay)
|
||||||
{
|
{
|
||||||
|
struct in6_multi_head inmh;
|
||||||
struct in6_mfilter timf;
|
struct in6_mfilter timf;
|
||||||
struct in6_multi *inm;
|
struct in6_multi *inm;
|
||||||
struct ifmultiaddr *ifma;
|
struct ifmultiaddr *ifma;
|
||||||
@ -1321,6 +1310,7 @@ in6_joingroup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
out_in6m_release:
|
out_in6m_release:
|
||||||
|
SLIST_INIT(&inmh);
|
||||||
if (error) {
|
if (error) {
|
||||||
struct epoch_tracker et;
|
struct epoch_tracker et;
|
||||||
|
|
||||||
@ -1332,13 +1322,14 @@ in6_joingroup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in6m_disconnect(inm);
|
in6m_disconnect_locked(&inmh, inm);
|
||||||
in6m_release_deferred(inm);
|
in6m_rele_locked(&inmh, inm);
|
||||||
NET_EPOCH_EXIT(et);
|
NET_EPOCH_EXIT(et);
|
||||||
} else {
|
} else {
|
||||||
*pinm = inm;
|
*pinm = inm;
|
||||||
}
|
}
|
||||||
IN6_MULTI_LIST_UNLOCK();
|
IN6_MULTI_LIST_UNLOCK();
|
||||||
|
in6m_release_list_deferred(&inmh);
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1372,6 +1363,7 @@ in6_leavegroup(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
|
|||||||
int
|
int
|
||||||
in6_leavegroup_locked(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
|
in6_leavegroup_locked(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
|
||||||
{
|
{
|
||||||
|
struct in6_multi_head inmh;
|
||||||
struct in6_mfilter timf;
|
struct in6_mfilter timf;
|
||||||
struct ifnet *ifp;
|
struct ifnet *ifp;
|
||||||
int error;
|
int error;
|
||||||
@ -1421,13 +1413,15 @@ in6_leavegroup_locked(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
|
|||||||
CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm);
|
CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm);
|
||||||
if (ifp)
|
if (ifp)
|
||||||
IF_ADDR_WLOCK(ifp);
|
IF_ADDR_WLOCK(ifp);
|
||||||
if (inm->in6m_refcount == 1 && inm->in6m_ifp != NULL)
|
|
||||||
in6m_disconnect(inm);
|
SLIST_INIT(&inmh);
|
||||||
in6m_release_deferred(inm);
|
if (inm->in6m_refcount == 1)
|
||||||
|
in6m_disconnect_locked(&inmh, inm);
|
||||||
|
in6m_rele_locked(&inmh, inm);
|
||||||
if (ifp)
|
if (ifp)
|
||||||
IF_ADDR_WUNLOCK(ifp);
|
IF_ADDR_WUNLOCK(ifp);
|
||||||
IN6_MULTI_LIST_UNLOCK();
|
IN6_MULTI_LIST_UNLOCK();
|
||||||
|
in6m_release_list_deferred(&inmh);
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1941,6 +1935,7 @@ in6p_lookup_mcast_ifp(const struct inpcb *in6p,
|
|||||||
static int
|
static int
|
||||||
in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
|
in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
|
||||||
{
|
{
|
||||||
|
struct in6_multi_head inmh;
|
||||||
struct group_source_req gsr;
|
struct group_source_req gsr;
|
||||||
sockunion_t *gsa, *ssa;
|
sockunion_t *gsa, *ssa;
|
||||||
struct ifnet *ifp;
|
struct ifnet *ifp;
|
||||||
@ -1951,6 +1946,7 @@ in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
|
|||||||
size_t idx;
|
size_t idx;
|
||||||
int error, is_new;
|
int error, is_new;
|
||||||
|
|
||||||
|
SLIST_INIT(&inmh);
|
||||||
ifp = NULL;
|
ifp = NULL;
|
||||||
imf = NULL;
|
imf = NULL;
|
||||||
lims = NULL;
|
lims = NULL;
|
||||||
@ -2227,7 +2223,7 @@ in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
|
|||||||
inm = imo->im6o_membership[idx];
|
inm = imo->im6o_membership[idx];
|
||||||
if (inm != NULL) {
|
if (inm != NULL) {
|
||||||
IN6_MULTI_LIST_LOCK();
|
IN6_MULTI_LIST_LOCK();
|
||||||
in6m_release_deferred(inm);
|
in6m_rele_locked(&inmh, inm);
|
||||||
IN6_MULTI_LIST_UNLOCK();
|
IN6_MULTI_LIST_UNLOCK();
|
||||||
}
|
}
|
||||||
imo->im6o_membership[idx] = NULL;
|
imo->im6o_membership[idx] = NULL;
|
||||||
@ -2236,6 +2232,7 @@ in6p_join_group(struct inpcb *inp, struct sockopt *sopt)
|
|||||||
|
|
||||||
out_in6p_locked:
|
out_in6p_locked:
|
||||||
INP_WUNLOCK(inp);
|
INP_WUNLOCK(inp);
|
||||||
|
in6m_release_list_deferred(&inmh);
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2880,10 +2877,9 @@ sysctl_ip6_mcast_filters(SYSCTL_HANDLER_ARGS)
|
|||||||
IN6_MULTI_LIST_LOCK();
|
IN6_MULTI_LIST_LOCK();
|
||||||
NET_EPOCH_ENTER(et);
|
NET_EPOCH_ENTER(et);
|
||||||
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
||||||
if (ifma->ifma_addr->sa_family != AF_INET6 ||
|
inm = in6m_ifmultiaddr_get_inm(ifma);
|
||||||
ifma->ifma_protospec == NULL)
|
if (inm == NULL)
|
||||||
continue;
|
continue;
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
|
||||||
if (!IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, &mcaddr))
|
if (!IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, &mcaddr))
|
||||||
continue;
|
continue;
|
||||||
fmode = inm->in6m_st[1].iss_fmode;
|
fmode = inm->in6m_st[1].iss_fmode;
|
||||||
|
@ -645,6 +645,7 @@ struct in6_multi {
|
|||||||
/* New fields for MLDv2 follow. */
|
/* New fields for MLDv2 follow. */
|
||||||
struct mld_ifsoftc *in6m_mli; /* MLD info */
|
struct mld_ifsoftc *in6m_mli; /* MLD info */
|
||||||
SLIST_ENTRY(in6_multi) in6m_nrele; /* to-be-released by MLD */
|
SLIST_ENTRY(in6_multi) in6m_nrele; /* to-be-released by MLD */
|
||||||
|
SLIST_ENTRY(in6_multi) in6m_defer; /* deferred MLDv1 */
|
||||||
struct ip6_msource_tree in6m_srcs; /* tree of sources */
|
struct ip6_msource_tree in6m_srcs; /* tree of sources */
|
||||||
u_long in6m_nsrc; /* # of tree entries */
|
u_long in6m_nsrc; /* # of tree entries */
|
||||||
|
|
||||||
@ -670,8 +671,8 @@ struct in6_multi {
|
|||||||
} in6m_st[2]; /* state at t0, t1 */
|
} in6m_st[2]; /* state at t0, t1 */
|
||||||
};
|
};
|
||||||
|
|
||||||
void in6m_disconnect(struct in6_multi *inm);
|
void in6m_disconnect_locked(struct in6_multi_head *inmh, struct in6_multi *inm);
|
||||||
extern int ifma6_restart;
|
|
||||||
/*
|
/*
|
||||||
* Helper function to derive the filter mode on a source entry
|
* Helper function to derive the filter mode on a source entry
|
||||||
* from its internal counters. Predicates are:
|
* from its internal counters. Predicates are:
|
||||||
@ -713,13 +714,25 @@ extern struct sx in6_multi_sx;
|
|||||||
#define IN6_MULTI_LOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XLOCKED)
|
#define IN6_MULTI_LOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XLOCKED)
|
||||||
#define IN6_MULTI_UNLOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XUNLOCKED)
|
#define IN6_MULTI_UNLOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XUNLOCKED)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the in6_multi pointer from a ifmultiaddr.
|
||||||
|
* Returns NULL if ifmultiaddr is no longer valid.
|
||||||
|
*/
|
||||||
|
static __inline struct in6_multi *
|
||||||
|
in6m_ifmultiaddr_get_inm(struct ifmultiaddr *ifma)
|
||||||
|
{
|
||||||
|
|
||||||
|
NET_EPOCH_ASSERT();
|
||||||
|
|
||||||
|
return ((ifma->ifma_addr->sa_family != AF_INET6 ||
|
||||||
|
(ifma->ifma_flags & IFMA_F_ENQUEUED) == 0) ? NULL :
|
||||||
|
ifma->ifma_protospec);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look up an in6_multi record for an IPv6 multicast address
|
* Look up an in6_multi record for an IPv6 multicast address
|
||||||
* on the interface ifp.
|
* on the interface ifp.
|
||||||
* If no record found, return NULL.
|
* If no record found, return NULL.
|
||||||
*
|
|
||||||
* SMPng: The IN6_MULTI_LOCK and IF_ADDR_LOCK on ifp must be held.
|
|
||||||
*/
|
*/
|
||||||
static __inline struct in6_multi *
|
static __inline struct in6_multi *
|
||||||
in6m_lookup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr)
|
in6m_lookup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr)
|
||||||
@ -727,18 +740,14 @@ in6m_lookup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr)
|
|||||||
struct ifmultiaddr *ifma;
|
struct ifmultiaddr *ifma;
|
||||||
struct in6_multi *inm;
|
struct in6_multi *inm;
|
||||||
|
|
||||||
inm = NULL;
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
||||||
CK_STAILQ_FOREACH(ifma, &((ifp)->if_multiaddrs), ifma_link) {
|
inm = in6m_ifmultiaddr_get_inm(ifma);
|
||||||
if (ifma->ifma_addr->sa_family == AF_INET6) {
|
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
|
||||||
if (inm == NULL)
|
if (inm == NULL)
|
||||||
continue;
|
continue;
|
||||||
if (IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, mcaddr))
|
if (IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, mcaddr))
|
||||||
break;
|
|
||||||
inm = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (inm);
|
return (inm);
|
||||||
|
}
|
||||||
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -809,7 +818,6 @@ void in6m_clear_recorded(struct in6_multi *);
|
|||||||
void in6m_commit(struct in6_multi *);
|
void in6m_commit(struct in6_multi *);
|
||||||
void in6m_print(const struct in6_multi *);
|
void in6m_print(const struct in6_multi *);
|
||||||
int in6m_record_source(struct in6_multi *, const struct in6_addr *);
|
int in6m_record_source(struct in6_multi *, const struct in6_addr *);
|
||||||
void in6m_release_deferred(struct in6_multi *);
|
|
||||||
void in6m_release_list_deferred(struct in6_multi_head *);
|
void in6m_release_list_deferred(struct in6_multi_head *);
|
||||||
void in6m_release_wait(void);
|
void in6m_release_wait(void);
|
||||||
void ip6_freemoptions(struct ip6_moptions *);
|
void ip6_freemoptions(struct ip6_moptions *);
|
||||||
|
@ -110,7 +110,7 @@ static void mli_delete_locked(const struct ifnet *);
|
|||||||
static void mld_dispatch_packet(struct mbuf *);
|
static void mld_dispatch_packet(struct mbuf *);
|
||||||
static void mld_dispatch_queue(struct mbufq *, int);
|
static void mld_dispatch_queue(struct mbufq *, int);
|
||||||
static void mld_final_leave(struct in6_multi *, struct mld_ifsoftc *);
|
static void mld_final_leave(struct in6_multi *, struct mld_ifsoftc *);
|
||||||
static void mld_fasttimo_vnet(void);
|
static void mld_fasttimo_vnet(struct in6_multi_head *inmh);
|
||||||
static int mld_handle_state_change(struct in6_multi *,
|
static int mld_handle_state_change(struct in6_multi *,
|
||||||
struct mld_ifsoftc *);
|
struct mld_ifsoftc *);
|
||||||
static int mld_initial_join(struct in6_multi *, struct mld_ifsoftc *,
|
static int mld_initial_join(struct in6_multi *, struct mld_ifsoftc *,
|
||||||
@ -537,45 +537,48 @@ mli_alloc_locked(/*const*/ struct ifnet *ifp)
|
|||||||
* XXX This routine is also bitten by unlocked ifma_protospec access.
|
* XXX This routine is also bitten by unlocked ifma_protospec access.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
mld_ifdetach(struct ifnet *ifp)
|
mld_ifdetach(struct ifnet *ifp, struct in6_multi_head *inmh)
|
||||||
{
|
{
|
||||||
|
struct epoch_tracker et;
|
||||||
struct mld_ifsoftc *mli;
|
struct mld_ifsoftc *mli;
|
||||||
struct ifmultiaddr *ifma, *next;
|
struct ifmultiaddr *ifma;
|
||||||
struct in6_multi *inm;
|
struct in6_multi *inm;
|
||||||
struct in6_multi_head inmh;
|
|
||||||
|
|
||||||
CTR3(KTR_MLD, "%s: called for ifp %p(%s)", __func__, ifp,
|
CTR3(KTR_MLD, "%s: called for ifp %p(%s)", __func__, ifp,
|
||||||
if_name(ifp));
|
if_name(ifp));
|
||||||
|
|
||||||
SLIST_INIT(&inmh);
|
|
||||||
IN6_MULTI_LIST_LOCK_ASSERT();
|
IN6_MULTI_LIST_LOCK_ASSERT();
|
||||||
MLD_LOCK();
|
MLD_LOCK();
|
||||||
|
|
||||||
mli = MLD_IFINFO(ifp);
|
mli = MLD_IFINFO(ifp);
|
||||||
if (mli->mli_version == MLD_VERSION_2) {
|
|
||||||
IF_ADDR_WLOCK(ifp);
|
IF_ADDR_WLOCK(ifp);
|
||||||
restart:
|
/*
|
||||||
CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
|
* Extract list of in6_multi associated with the detaching ifp
|
||||||
if (ifma->ifma_addr->sa_family != AF_INET6 ||
|
* which the PF_INET6 layer is about to release.
|
||||||
ifma->ifma_protospec == NULL)
|
*/
|
||||||
|
NET_EPOCH_ENTER(et);
|
||||||
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
||||||
|
inm = in6m_ifmultiaddr_get_inm(ifma);
|
||||||
|
if (inm == NULL)
|
||||||
continue;
|
continue;
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
in6m_disconnect_locked(inmh, inm);
|
||||||
if (inm->in6m_state == MLD_LEAVING_MEMBER) {
|
|
||||||
in6m_disconnect(inm);
|
|
||||||
in6m_rele_locked(&inmh, inm);
|
|
||||||
ifma->ifma_protospec = NULL;
|
|
||||||
}
|
|
||||||
in6m_clear_recorded(inm);
|
|
||||||
if (__predict_false(ifma6_restart)) {
|
|
||||||
ifma6_restart = false;
|
|
||||||
goto restart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IF_ADDR_WUNLOCK(ifp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (mli->mli_version == MLD_VERSION_2) {
|
||||||
|
in6m_clear_recorded(inm);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to release the final reference held
|
||||||
|
* for issuing the INCLUDE {}.
|
||||||
|
*/
|
||||||
|
if (inm->in6m_state == MLD_LEAVING_MEMBER) {
|
||||||
|
inm->in6m_state = MLD_NOT_MEMBER;
|
||||||
|
in6m_rele_locked(inmh, inm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NET_EPOCH_EXIT(et);
|
||||||
|
IF_ADDR_WUNLOCK(ifp);
|
||||||
MLD_UNLOCK();
|
MLD_UNLOCK();
|
||||||
in6m_release_list_deferred(&inmh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -709,10 +712,9 @@ mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6,
|
|||||||
CTR2(KTR_MLD, "process v1 general query on ifp %p(%s)",
|
CTR2(KTR_MLD, "process v1 general query on ifp %p(%s)",
|
||||||
ifp, if_name(ifp));
|
ifp, if_name(ifp));
|
||||||
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
||||||
if (ifma->ifma_addr->sa_family != AF_INET6 ||
|
inm = in6m_ifmultiaddr_get_inm(ifma);
|
||||||
ifma->ifma_protospec == NULL)
|
if (inm == NULL)
|
||||||
continue;
|
continue;
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
|
||||||
mld_v1_update_group(inm, timer);
|
mld_v1_update_group(inm, timer);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1324,15 +1326,19 @@ mld_input(struct mbuf *m, int off, int icmp6len)
|
|||||||
void
|
void
|
||||||
mld_fasttimo(void)
|
mld_fasttimo(void)
|
||||||
{
|
{
|
||||||
|
struct in6_multi_head inmh;
|
||||||
VNET_ITERATOR_DECL(vnet_iter);
|
VNET_ITERATOR_DECL(vnet_iter);
|
||||||
|
|
||||||
|
SLIST_INIT(&inmh);
|
||||||
|
|
||||||
VNET_LIST_RLOCK_NOSLEEP();
|
VNET_LIST_RLOCK_NOSLEEP();
|
||||||
VNET_FOREACH(vnet_iter) {
|
VNET_FOREACH(vnet_iter) {
|
||||||
CURVNET_SET(vnet_iter);
|
CURVNET_SET(vnet_iter);
|
||||||
mld_fasttimo_vnet();
|
mld_fasttimo_vnet(&inmh);
|
||||||
CURVNET_RESTORE();
|
CURVNET_RESTORE();
|
||||||
}
|
}
|
||||||
VNET_LIST_RUNLOCK_NOSLEEP();
|
VNET_LIST_RUNLOCK_NOSLEEP();
|
||||||
|
in6m_release_list_deferred(&inmh);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1341,15 +1347,15 @@ mld_fasttimo(void)
|
|||||||
* VIMAGE: Assume caller has set up our curvnet.
|
* VIMAGE: Assume caller has set up our curvnet.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
mld_fasttimo_vnet(void)
|
mld_fasttimo_vnet(struct in6_multi_head *inmh)
|
||||||
{
|
{
|
||||||
|
struct epoch_tracker et;
|
||||||
struct mbufq scq; /* State-change packets */
|
struct mbufq scq; /* State-change packets */
|
||||||
struct mbufq qrq; /* Query response packets */
|
struct mbufq qrq; /* Query response packets */
|
||||||
struct ifnet *ifp;
|
struct ifnet *ifp;
|
||||||
struct mld_ifsoftc *mli;
|
struct mld_ifsoftc *mli;
|
||||||
struct ifmultiaddr *ifma, *next;
|
struct ifmultiaddr *ifma;
|
||||||
struct in6_multi *inm, *tinm;
|
struct in6_multi *inm;
|
||||||
struct in6_multi_head inmh;
|
|
||||||
int uri_fasthz;
|
int uri_fasthz;
|
||||||
|
|
||||||
uri_fasthz = 0;
|
uri_fasthz = 0;
|
||||||
@ -1364,7 +1370,6 @@ mld_fasttimo_vnet(void)
|
|||||||
!V_state_change_timers_running6)
|
!V_state_change_timers_running6)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SLIST_INIT(&inmh);
|
|
||||||
IN6_MULTI_LIST_LOCK();
|
IN6_MULTI_LIST_LOCK();
|
||||||
MLD_LOCK();
|
MLD_LOCK();
|
||||||
|
|
||||||
@ -1410,25 +1415,20 @@ mld_fasttimo_vnet(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
IF_ADDR_WLOCK(ifp);
|
IF_ADDR_WLOCK(ifp);
|
||||||
restart:
|
NET_EPOCH_ENTER(et);
|
||||||
CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
||||||
if (ifma->ifma_addr->sa_family != AF_INET6 ||
|
inm = in6m_ifmultiaddr_get_inm(ifma);
|
||||||
ifma->ifma_protospec == NULL)
|
if (inm == NULL)
|
||||||
continue;
|
continue;
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
|
||||||
switch (mli->mli_version) {
|
switch (mli->mli_version) {
|
||||||
case MLD_VERSION_1:
|
case MLD_VERSION_1:
|
||||||
mld_v1_process_group_timer(&inmh, inm);
|
mld_v1_process_group_timer(inmh, inm);
|
||||||
break;
|
break;
|
||||||
case MLD_VERSION_2:
|
case MLD_VERSION_2:
|
||||||
mld_v2_process_group_timers(&inmh, &qrq,
|
mld_v2_process_group_timers(inmh, &qrq,
|
||||||
&scq, inm, uri_fasthz);
|
&scq, inm, uri_fasthz);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (__predict_false(ifma6_restart)) {
|
|
||||||
ifma6_restart = false;
|
|
||||||
goto restart;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
IF_ADDR_WUNLOCK(ifp);
|
IF_ADDR_WUNLOCK(ifp);
|
||||||
|
|
||||||
@ -1442,9 +1442,8 @@ mld_fasttimo_vnet(void)
|
|||||||
* IF_ADDR_LOCK internally as well as
|
* IF_ADDR_LOCK internally as well as
|
||||||
* ip6_output() to transmit a packet.
|
* ip6_output() to transmit a packet.
|
||||||
*/
|
*/
|
||||||
SLIST_FOREACH_SAFE(inm, &inmh, in6m_nrele, tinm) {
|
while ((inm = SLIST_FIRST(inmh)) != NULL) {
|
||||||
SLIST_REMOVE_HEAD(&inmh,
|
SLIST_REMOVE_HEAD(inmh, in6m_defer);
|
||||||
in6m_nrele);
|
|
||||||
(void)mld_v1_transmit_report(inm,
|
(void)mld_v1_transmit_report(inm,
|
||||||
MLD_LISTENER_REPORT);
|
MLD_LISTENER_REPORT);
|
||||||
}
|
}
|
||||||
@ -1452,14 +1451,9 @@ mld_fasttimo_vnet(void)
|
|||||||
case MLD_VERSION_2:
|
case MLD_VERSION_2:
|
||||||
mld_dispatch_queue(&qrq, 0);
|
mld_dispatch_queue(&qrq, 0);
|
||||||
mld_dispatch_queue(&scq, 0);
|
mld_dispatch_queue(&scq, 0);
|
||||||
|
|
||||||
/*
|
|
||||||
* Free the in_multi reference(s) for
|
|
||||||
* this lifecycle.
|
|
||||||
*/
|
|
||||||
in6m_release_list_deferred(&inmh);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
NET_EPOCH_EXIT(et);
|
||||||
}
|
}
|
||||||
|
|
||||||
out_locked:
|
out_locked:
|
||||||
@ -1499,8 +1493,7 @@ mld_v1_process_group_timer(struct in6_multi_head *inmh, struct in6_multi *inm)
|
|||||||
case MLD_REPORTING_MEMBER:
|
case MLD_REPORTING_MEMBER:
|
||||||
if (report_timer_expired) {
|
if (report_timer_expired) {
|
||||||
inm->in6m_state = MLD_IDLE_MEMBER;
|
inm->in6m_state = MLD_IDLE_MEMBER;
|
||||||
in6m_disconnect(inm);
|
SLIST_INSERT_HEAD(inmh, inm, in6m_defer);
|
||||||
in6m_rele_locked(inmh, inm);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MLD_G_QUERY_PENDING_MEMBER:
|
case MLD_G_QUERY_PENDING_MEMBER:
|
||||||
@ -1624,7 +1617,7 @@ mld_v2_process_group_timers(struct in6_multi_head *inmh,
|
|||||||
if (inm->in6m_state == MLD_LEAVING_MEMBER &&
|
if (inm->in6m_state == MLD_LEAVING_MEMBER &&
|
||||||
inm->in6m_scrv == 0) {
|
inm->in6m_scrv == 0) {
|
||||||
inm->in6m_state = MLD_NOT_MEMBER;
|
inm->in6m_state = MLD_NOT_MEMBER;
|
||||||
in6m_disconnect(inm);
|
in6m_disconnect_locked(inmh, inm);
|
||||||
in6m_rele_locked(inmh, inm);
|
in6m_rele_locked(inmh, inm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1669,10 +1662,11 @@ mld_set_version(struct mld_ifsoftc *mli, const int version)
|
|||||||
static void
|
static void
|
||||||
mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
|
mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
|
||||||
{
|
{
|
||||||
struct ifmultiaddr *ifma, *next;
|
struct epoch_tracker et;
|
||||||
|
struct in6_multi_head inmh;
|
||||||
|
struct ifmultiaddr *ifma;
|
||||||
struct ifnet *ifp;
|
struct ifnet *ifp;
|
||||||
struct in6_multi *inm;
|
struct in6_multi *inm;
|
||||||
struct in6_multi_head inmh;
|
|
||||||
|
|
||||||
CTR3(KTR_MLD, "%s: cancel v2 timers on ifp %p(%s)", __func__,
|
CTR3(KTR_MLD, "%s: cancel v2 timers on ifp %p(%s)", __func__,
|
||||||
mli->mli_ifp, if_name(mli->mli_ifp));
|
mli->mli_ifp, if_name(mli->mli_ifp));
|
||||||
@ -1695,12 +1689,11 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
|
|||||||
ifp = mli->mli_ifp;
|
ifp = mli->mli_ifp;
|
||||||
|
|
||||||
IF_ADDR_WLOCK(ifp);
|
IF_ADDR_WLOCK(ifp);
|
||||||
restart:
|
NET_EPOCH_ENTER(et);
|
||||||
CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
||||||
if (ifma->ifma_addr->sa_family != AF_INET6 ||
|
inm = in6m_ifmultiaddr_get_inm(ifma);
|
||||||
ifma->ifma_protospec == NULL)
|
if (inm == NULL)
|
||||||
continue;
|
continue;
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
|
||||||
switch (inm->in6m_state) {
|
switch (inm->in6m_state) {
|
||||||
case MLD_NOT_MEMBER:
|
case MLD_NOT_MEMBER:
|
||||||
case MLD_SILENT_MEMBER:
|
case MLD_SILENT_MEMBER:
|
||||||
@ -1715,9 +1708,7 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
|
|||||||
* version, we need to release the final
|
* version, we need to release the final
|
||||||
* reference held for issuing the INCLUDE {}.
|
* reference held for issuing the INCLUDE {}.
|
||||||
*/
|
*/
|
||||||
in6m_disconnect(inm);
|
|
||||||
in6m_rele_locked(&inmh, inm);
|
in6m_rele_locked(&inmh, inm);
|
||||||
ifma->ifma_protospec = NULL;
|
|
||||||
/* FALLTHROUGH */
|
/* FALLTHROUGH */
|
||||||
case MLD_G_QUERY_PENDING_MEMBER:
|
case MLD_G_QUERY_PENDING_MEMBER:
|
||||||
case MLD_SG_QUERY_PENDING_MEMBER:
|
case MLD_SG_QUERY_PENDING_MEMBER:
|
||||||
@ -1733,11 +1724,8 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
|
|||||||
mbufq_drain(&inm->in6m_scq);
|
mbufq_drain(&inm->in6m_scq);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (__predict_false(ifma6_restart)) {
|
|
||||||
ifma6_restart = false;
|
|
||||||
goto restart;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
NET_EPOCH_EXIT(et);
|
||||||
IF_ADDR_WUNLOCK(ifp);
|
IF_ADDR_WUNLOCK(ifp);
|
||||||
in6m_release_list_deferred(&inmh);
|
in6m_release_list_deferred(&inmh);
|
||||||
}
|
}
|
||||||
@ -1909,6 +1897,14 @@ mld_change_state(struct in6_multi *inm, const int delay)
|
|||||||
|
|
||||||
error = 0;
|
error = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if the in6_multi has already been disconnected.
|
||||||
|
*/
|
||||||
|
if (inm->in6m_ifp == NULL) {
|
||||||
|
CTR1(KTR_MLD, "%s: inm is disconnected", __func__);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to detect if the upper layer just asked us to change state
|
* Try to detect if the upper layer just asked us to change state
|
||||||
* for an interface which has now gone away.
|
* for an interface which has now gone away.
|
||||||
@ -2019,6 +2015,7 @@ mld_initial_join(struct in6_multi *inm, struct mld_ifsoftc *mli,
|
|||||||
if (mli->mli_version == MLD_VERSION_2 &&
|
if (mli->mli_version == MLD_VERSION_2 &&
|
||||||
inm->in6m_state == MLD_LEAVING_MEMBER) {
|
inm->in6m_state == MLD_LEAVING_MEMBER) {
|
||||||
inm->in6m_refcount--;
|
inm->in6m_refcount--;
|
||||||
|
MPASS(inm->in6m_refcount > 0);
|
||||||
}
|
}
|
||||||
inm->in6m_state = MLD_REPORTING_MEMBER;
|
inm->in6m_state = MLD_REPORTING_MEMBER;
|
||||||
|
|
||||||
@ -3023,11 +3020,9 @@ mld_v2_dispatch_general_query(struct mld_ifsoftc *mli)
|
|||||||
|
|
||||||
NET_EPOCH_ENTER(et);
|
NET_EPOCH_ENTER(et);
|
||||||
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
||||||
if (ifma->ifma_addr->sa_family != AF_INET6 ||
|
inm = in6m_ifmultiaddr_get_inm(ifma);
|
||||||
ifma->ifma_protospec == NULL)
|
if (inm == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
inm = (struct in6_multi *)ifma->ifma_protospec;
|
|
||||||
KASSERT(ifp == inm->in6m_ifp,
|
KASSERT(ifp == inm->in6m_ifp,
|
||||||
("%s: inconsistent ifp", __func__));
|
("%s: inconsistent ifp", __func__));
|
||||||
|
|
||||||
|
@ -160,12 +160,13 @@ struct mld_ifsoftc {
|
|||||||
#define MLD_IFINFO(ifp) \
|
#define MLD_IFINFO(ifp) \
|
||||||
(((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->mld_ifinfo)
|
(((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->mld_ifinfo)
|
||||||
|
|
||||||
|
struct in6_multi_head;
|
||||||
int mld_change_state(struct in6_multi *, const int);
|
int mld_change_state(struct in6_multi *, const int);
|
||||||
struct mld_ifsoftc *
|
struct mld_ifsoftc *
|
||||||
mld_domifattach(struct ifnet *);
|
mld_domifattach(struct ifnet *);
|
||||||
void mld_domifdetach(struct ifnet *);
|
void mld_domifdetach(struct ifnet *);
|
||||||
void mld_fasttimo(void);
|
void mld_fasttimo(void);
|
||||||
void mld_ifdetach(struct ifnet *);
|
void mld_ifdetach(struct ifnet *, struct in6_multi_head *);
|
||||||
int mld_input(struct mbuf *, int, int);
|
int mld_input(struct mbuf *, int, int);
|
||||||
void mld_slowtimo(void);
|
void mld_slowtimo(void);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user