diff options
Diffstat (limited to 'freebsd/sys/netinet6/mld6.c')
-rw-r--r-- | freebsd/sys/netinet6/mld6.c | 229 |
1 files changed, 120 insertions, 109 deletions
diff --git a/freebsd/sys/netinet6/mld6.c b/freebsd/sys/netinet6/mld6.c index b00f03ef..cc946f67 100644 --- a/freebsd/sys/netinet6/mld6.c +++ b/freebsd/sys/netinet6/mld6.c @@ -112,7 +112,7 @@ static void mli_delete_locked(const struct ifnet *); static void mld_dispatch_packet(struct mbuf *); static void mld_dispatch_queue(struct mbufq *, int); 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 *, struct mld_ifsoftc *); static int mld_initial_join(struct in6_multi *, struct mld_ifsoftc *, @@ -141,14 +141,15 @@ static int mld_v2_enqueue_group_record(struct mbufq *, struct in6_multi *, const int, const int, const int, const int); static int mld_v2_input_query(struct ifnet *, const struct ip6_hdr *, - struct mbuf *, const int, const int); + struct mbuf *, struct mldv2_query *, const int, const int); static int mld_v2_merge_state_changes(struct in6_multi *, struct mbufq *); static void mld_v2_process_group_timers(struct in6_multi_head *, struct mbufq *, struct mbufq *, struct in6_multi *, const int); static int mld_v2_process_group_query(struct in6_multi *, - struct mld_ifsoftc *mli, int, struct mbuf *, const int); + struct mld_ifsoftc *mli, int, struct mbuf *, + struct mldv2_query *, const int); static int sysctl_mld_gsr(SYSCTL_HANDLER_ARGS); static int sysctl_mld_ifinfo(SYSCTL_HANDLER_ARGS); @@ -245,6 +246,10 @@ static int mld_v1enable = 1; SYSCTL_INT(_net_inet6_mld, OID_AUTO, v1enable, CTLFLAG_RWTUN, &mld_v1enable, 0, "Enable fallback to MLDv1"); +static int mld_v2enable = 1; +SYSCTL_INT(_net_inet6_mld, OID_AUTO, v2enable, CTLFLAG_RWTUN, + &mld_v2enable, 0, "Enable MLDv2"); + static int mld_use_allow = 1; SYSCTL_INT(_net_inet6_mld, OID_AUTO, use_allow, CTLFLAG_RWTUN, &mld_use_allow, 0, "Use ALLOW/BLOCK for RFC 4604 SSM joins/leaves"); @@ -535,45 +540,48 @@ out: * XXX This routine is also bitten by unlocked ifma_protospec access. */ 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 ifmultiaddr *ifma, *next; + struct ifmultiaddr *ifma; struct in6_multi *inm; - struct in6_multi_head inmh; CTR3(KTR_MLD, "%s: called for ifp %p(%s)", __func__, ifp, if_name(ifp)); - SLIST_INIT(&inmh); IN6_MULTI_LIST_LOCK_ASSERT(); MLD_LOCK(); mli = MLD_IFINFO(ifp); - if (mli->mli_version == MLD_VERSION_2) { - IF_ADDR_WLOCK(ifp); - 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; - if (inm->in6m_state == MLD_LEAVING_MEMBER) { - in6m_disconnect(inm); - in6m_rele_locked(&inmh, inm); - ifma->ifma_protospec = NULL; - } + IF_ADDR_WLOCK(ifp); + /* + * Extract list of in6_multi associated with the detaching ifp + * which the PF_INET6 layer is about to release. + */ + NET_EPOCH_ENTER(et); + CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) + continue; + in6m_disconnect_locked(inmh, inm); + + if (mli->mli_version == MLD_VERSION_2) { in6m_clear_recorded(inm); - if (__predict_false(ifma6_restart)) { - ifma6_restart = false; - goto restart; + + /* + * 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); } } - IF_ADDR_WUNLOCK(ifp); } - + NET_EPOCH_EXIT(et); + IF_ADDR_WUNLOCK(ifp); MLD_UNLOCK(); - in6m_release_list_deferred(&inmh); } /* @@ -630,6 +638,7 @@ static int mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, /*const*/ struct mld_hdr *mld) { + struct epoch_tracker et; struct ifmultiaddr *ifma; struct mld_ifsoftc *mli; struct in6_multi *inm; @@ -697,7 +706,7 @@ mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, if (timer == 0) timer = 1; - IF_ADDR_RLOCK(ifp); + NET_EPOCH_ENTER(et); if (is_general_query) { /* * For each reporting group joined on this @@ -706,10 +715,9 @@ mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, CTR2(KTR_MLD, "process v1 general query on ifp %p(%s)", ifp, if_name(ifp)); CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) continue; - inm = (struct in6_multi *)ifma->ifma_protospec; mld_v1_update_group(inm, timer); } } else { @@ -729,7 +737,7 @@ mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, in6_clearscope(&mld->mld_addr); } - IF_ADDR_RUNLOCK(ifp); + NET_EPOCH_EXIT(et); MLD_UNLOCK(); IN6_MULTI_LIST_UNLOCK(); @@ -799,16 +807,16 @@ mld_v1_update_group(struct in6_multi *inm, const int timer) * Process a received MLDv2 general, group-specific or * group-and-source-specific query. * - * Assumes that the query header has been pulled up to sizeof(mldv2_query). + * Assumes that mld points to a struct mldv2_query which is stored in + * contiguous memory. * * Return 0 if successful, otherwise an appropriate error code is returned. */ static int mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, - struct mbuf *m, const int off, const int icmp6len) + struct mbuf *m, struct mldv2_query *mld, const int off, const int icmp6len) { struct mld_ifsoftc *mli; - struct mldv2_query *mld; struct in6_multi *inm; uint32_t maxdelay, nsrc, qqi; int is_general_query; @@ -818,7 +826,12 @@ mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, char ip6tbuf[INET6_ADDRSTRLEN]; #endif - is_general_query = 0; + if (!mld_v2enable) { + CTR3(KTR_MLD, "ignore v2 query src %s on ifp %p(%s)", + ip6_sprintf(ip6tbuf, &ip6->ip6_src), + ifp, if_name(ifp)); + return (0); + } /* * RFC3810 Section 6.2: MLD queries must originate from @@ -831,9 +844,9 @@ mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, return (0); } - CTR2(KTR_MLD, "input v2 query on ifp %p(%s)", ifp, if_name(ifp)); + is_general_query = 0; - mld = (struct mldv2_query *)(mtod(m, uint8_t *) + off); + CTR2(KTR_MLD, "input v2 query on ifp %p(%s)", ifp, if_name(ifp)); maxdelay = ntohs(mld->mld_maxdelay); /* in 1/10ths of a second */ if (maxdelay >= 32768) { @@ -926,6 +939,8 @@ mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, V_interface_timers_running6 = 1; } } else { + struct epoch_tracker et; + /* * MLDv2 Group-specific or Group-and-source-specific Query. * @@ -934,10 +949,10 @@ mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, * Queries for groups we are not a member of on this * link are simply ignored. */ - IF_ADDR_RLOCK(ifp); + NET_EPOCH_ENTER(et); inm = in6m_lookup_locked(ifp, &mld->mld_addr); if (inm == NULL) { - IF_ADDR_RUNLOCK(ifp); + NET_EPOCH_EXIT(et); goto out_locked; } if (nsrc > 0) { @@ -945,7 +960,7 @@ mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, &V_mld_gsrdelay)) { CTR1(KTR_MLD, "%s: GS query throttled.", __func__); - IF_ADDR_RUNLOCK(ifp); + NET_EPOCH_EXIT(et); goto out_locked; } } @@ -959,11 +974,11 @@ mld_v2_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6, * group-specific or group-and-source query. */ if (mli->mli_v2_timer == 0 || mli->mli_v2_timer >= timer) - mld_v2_process_group_query(inm, mli, timer, m, off); + mld_v2_process_group_query(inm, mli, timer, m, mld, off); /* XXX Clear embedded scope ID as userland won't expect it. */ in6_clearscope(&mld->mld_addr); - IF_ADDR_RUNLOCK(ifp); + NET_EPOCH_EXIT(et); } out_locked: @@ -980,9 +995,8 @@ out_locked: */ static int mld_v2_process_group_query(struct in6_multi *inm, struct mld_ifsoftc *mli, - int timer, struct mbuf *m0, const int off) + int timer, struct mbuf *m0, struct mldv2_query *mld, const int off) { - struct mldv2_query *mld; int retval; uint16_t nsrc; @@ -990,7 +1004,6 @@ mld_v2_process_group_query(struct in6_multi *inm, struct mld_ifsoftc *mli, MLD_LOCK_ASSERT(); retval = 0; - mld = (struct mldv2_query *)(mtod(m0, uint8_t *) + off); switch (inm->in6m_state) { case MLD_NOT_MEMBER: @@ -1010,6 +1023,15 @@ mld_v2_process_group_query(struct in6_multi *inm, struct mld_ifsoftc *mli, nsrc = ntohs(mld->mld_numsrc); + /* Length should be checked by calling function. */ + KASSERT((m0->m_flags & M_PKTHDR) == 0 || + m0->m_pkthdr.len >= off + sizeof(struct mldv2_query) + + nsrc * sizeof(struct in6_addr), + ("mldv2 packet is too short: (%d bytes < %zd bytes, m=%p)", + m0->m_pkthdr.len, off + sizeof(struct mldv2_query) + + nsrc * sizeof(struct in6_addr), m0)); + + /* * Deal with group-specific queries upfront. * If any group query is already pending, purge any recorded @@ -1051,28 +1073,20 @@ mld_v2_process_group_query(struct in6_multi *inm, struct mld_ifsoftc *mli, * report for those sources. */ if (inm->in6m_nsrc > 0) { - struct mbuf *m; - uint8_t *sp; + struct in6_addr srcaddr; int i, nrecorded; int soff; - m = m0; soff = off + sizeof(struct mldv2_query); nrecorded = 0; for (i = 0; i < nsrc; i++) { - sp = mtod(m, uint8_t *) + soff; - retval = in6m_record_source(inm, - (const struct in6_addr *)sp); + m_copydata(m0, soff, sizeof(struct in6_addr), + (caddr_t)&srcaddr); + retval = in6m_record_source(inm, &srcaddr); if (retval < 0) break; nrecorded += retval; soff += sizeof(struct in6_addr); - if (soff >= m->m_len) { - soff = soff - m->m_len; - m = m->m_next; - if (m == NULL) - break; - } } if (nrecorded > 0) { CTR1(KTR_MLD, @@ -1098,6 +1112,7 @@ mld_v1_input_report(struct ifnet *ifp, const struct ip6_hdr *ip6, /*const*/ struct mld_hdr *mld) { struct in6_addr src, dst; + struct epoch_tracker et; struct in6_ifaddr *ia; struct in6_multi *inm; #ifdef KTR @@ -1173,7 +1188,7 @@ mld_v1_input_report(struct ifnet *ifp, const struct ip6_hdr *ip6, IN6_MULTI_LIST_LOCK(); MLD_LOCK(); - IF_ADDR_RLOCK(ifp); + NET_EPOCH_ENTER(et); /* * MLDv1 report suppression. @@ -1221,7 +1236,7 @@ mld_v1_input_report(struct ifnet *ifp, const struct ip6_hdr *ip6, } out_locked: - IF_ADDR_RUNLOCK(ifp); + NET_EPOCH_EXIT(et); MLD_UNLOCK(); IN6_MULTI_LIST_UNLOCK(); @@ -1281,8 +1296,8 @@ mld_input(struct mbuf *m, int off, int icmp6len) if (mld_v1_input_query(ifp, ip6, mld) != 0) return (0); } else if (icmp6len >= sizeof(struct mldv2_query)) { - if (mld_v2_input_query(ifp, ip6, m, off, - icmp6len) != 0) + if (mld_v2_input_query(ifp, ip6, m, + (struct mldv2_query *)mld, off, icmp6len) != 0) return (0); } break; @@ -1311,15 +1326,19 @@ mld_input(struct mbuf *m, int off, int icmp6len) void mld_fasttimo(void) { + struct in6_multi_head inmh; VNET_ITERATOR_DECL(vnet_iter); + SLIST_INIT(&inmh); + VNET_LIST_RLOCK_NOSLEEP(); VNET_FOREACH(vnet_iter) { CURVNET_SET(vnet_iter); - mld_fasttimo_vnet(); + mld_fasttimo_vnet(&inmh); CURVNET_RESTORE(); } VNET_LIST_RUNLOCK_NOSLEEP(); + in6m_release_list_deferred(&inmh); } /* @@ -1328,15 +1347,15 @@ mld_fasttimo(void) * VIMAGE: Assume caller has set up our curvnet. */ 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 qrq; /* Query response packets */ struct ifnet *ifp; struct mld_ifsoftc *mli; - struct ifmultiaddr *ifma, *next; - struct in6_multi *inm, *tinm; - struct in6_multi_head inmh; + struct ifmultiaddr *ifma; + struct in6_multi *inm; int uri_fasthz; uri_fasthz = 0; @@ -1351,7 +1370,6 @@ mld_fasttimo_vnet(void) !V_state_change_timers_running6) return; - SLIST_INIT(&inmh); IN6_MULTI_LIST_LOCK(); MLD_LOCK(); @@ -1397,25 +1415,20 @@ mld_fasttimo_vnet(void) } IF_ADDR_WLOCK(ifp); - restart: - CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - 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; - inm = (struct in6_multi *)ifma->ifma_protospec; switch (mli->mli_version) { case MLD_VERSION_1: - mld_v1_process_group_timer(&inmh, inm); + mld_v1_process_group_timer(inmh, inm); break; case MLD_VERSION_2: - mld_v2_process_group_timers(&inmh, &qrq, + mld_v2_process_group_timers(inmh, &qrq, &scq, inm, uri_fasthz); break; } - if (__predict_false(ifma6_restart)) { - ifma6_restart = false; - goto restart; - } } IF_ADDR_WUNLOCK(ifp); @@ -1429,9 +1442,8 @@ mld_fasttimo_vnet(void) * IF_ADDR_LOCK internally as well as * ip6_output() to transmit a packet. */ - SLIST_FOREACH_SAFE(inm, &inmh, in6m_nrele, tinm) { - SLIST_REMOVE_HEAD(&inmh, - in6m_nrele); + while ((inm = SLIST_FIRST(inmh)) != NULL) { + SLIST_REMOVE_HEAD(inmh, in6m_defer); (void)mld_v1_transmit_report(inm, MLD_LISTENER_REPORT); } @@ -1439,14 +1451,9 @@ mld_fasttimo_vnet(void) case MLD_VERSION_2: mld_dispatch_queue(&qrq, 0); mld_dispatch_queue(&scq, 0); - - /* - * Free the in_multi reference(s) for - * this lifecycle. - */ - in6m_release_list_deferred(&inmh); break; } + NET_EPOCH_EXIT(et); } out_locked: @@ -1486,8 +1493,7 @@ mld_v1_process_group_timer(struct in6_multi_head *inmh, struct in6_multi *inm) case MLD_REPORTING_MEMBER: if (report_timer_expired) { inm->in6m_state = MLD_IDLE_MEMBER; - in6m_disconnect(inm); - in6m_rele_locked(inmh, inm); + SLIST_INSERT_HEAD(inmh, inm, in6m_defer); } break; case MLD_G_QUERY_PENDING_MEMBER: @@ -1611,7 +1617,7 @@ mld_v2_process_group_timers(struct in6_multi_head *inmh, if (inm->in6m_state == MLD_LEAVING_MEMBER && inm->in6m_scrv == 0) { inm->in6m_state = MLD_NOT_MEMBER; - in6m_disconnect(inm); + in6m_disconnect_locked(inmh, inm); in6m_rele_locked(inmh, inm); } } @@ -1656,10 +1662,11 @@ mld_set_version(struct mld_ifsoftc *mli, const int version) static void 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 in6_multi *inm; - struct in6_multi_head inmh; CTR3(KTR_MLD, "%s: cancel v2 timers on ifp %p(%s)", __func__, mli->mli_ifp, if_name(mli->mli_ifp)); @@ -1682,12 +1689,11 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli) ifp = mli->mli_ifp; IF_ADDR_WLOCK(ifp); - restart: - CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - 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; - inm = (struct in6_multi *)ifma->ifma_protospec; switch (inm->in6m_state) { case MLD_NOT_MEMBER: case MLD_SILENT_MEMBER: @@ -1702,9 +1708,9 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli) * version, we need to release the final * reference held for issuing the INCLUDE {}. */ - in6m_disconnect(inm); + if (inm->in6m_refcount == 1) + in6m_disconnect_locked(&inmh, inm); in6m_rele_locked(&inmh, inm); - ifma->ifma_protospec = NULL; /* FALLTHROUGH */ case MLD_G_QUERY_PENDING_MEMBER: case MLD_SG_QUERY_PENDING_MEMBER: @@ -1720,11 +1726,8 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli) mbufq_drain(&inm->in6m_scq); break; } - if (__predict_false(ifma6_restart)) { - ifma6_restart = false; - goto restart; - } } + NET_EPOCH_EXIT(et); IF_ADDR_WUNLOCK(ifp); in6m_release_list_deferred(&inmh); } @@ -1897,6 +1900,14 @@ mld_change_state(struct in6_multi *inm, const int delay) 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 * for an interface which has now gone away. */ @@ -2006,6 +2017,7 @@ mld_initial_join(struct in6_multi *inm, struct mld_ifsoftc *mli, if (mli->mli_version == MLD_VERSION_2 && inm->in6m_state == MLD_LEAVING_MEMBER) { inm->in6m_refcount--; + MPASS(inm->in6m_refcount > 0); } inm->in6m_state = MLD_REPORTING_MEMBER; @@ -2985,6 +2997,7 @@ mld_v2_merge_state_changes(struct in6_multi *inm, struct mbufq *scq) static void mld_v2_dispatch_general_query(struct mld_ifsoftc *mli) { + struct epoch_tracker et; struct ifmultiaddr *ifma; struct ifnet *ifp; struct in6_multi *inm; @@ -3007,13 +3020,11 @@ mld_v2_dispatch_general_query(struct mld_ifsoftc *mli) ifp = mli->mli_ifp; - IF_ADDR_RLOCK(ifp); + NET_EPOCH_ENTER(et); CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) continue; - - inm = (struct in6_multi *)ifma->ifma_protospec; KASSERT(ifp == inm->in6m_ifp, ("%s: inconsistent ifp", __func__)); @@ -3038,7 +3049,7 @@ mld_v2_dispatch_general_query(struct mld_ifsoftc *mli) break; } } - IF_ADDR_RUNLOCK(ifp); + NET_EPOCH_EXIT(et); send: mld_dispatch_queue(&mli->mli_gq, MLD_MAX_RESPONSE_BURST); |