FreeBSD/sys/netinet/in_mcast.c
/*
 * Leave an IPv4 multicast group on an inpcb, possibly with a source.
 */
Leave an IPv4 multicast group on an inpcb, possibly with a source.
Leave an IPv4 multicast group on an inpcb possibly with a source .
static int
inp_leave_group(struct inpcb *inp, struct sockopt *sopt)
{
	struct group_source_req		 gsr;
	struct ip_mreq_source		 mreqs;
	sockunion_t			*gsa, *ssa;
	struct ifnet			*ifp;
	struct in_mfilter		*imf;
	struct ip_moptions		*imo;
	struct in_msource		*ims;
	struct in_multi			*inm;
	size_t				 idx;
	int				 error, is_final;
	ifp = NULL;
	error = 0;
	is_final = 1;
	memset(&gsr, 0, sizeof(struct group_source_req));
	gsa = (sockunion_t *)&gsr.gsr_group;
	gsa->ss.ss_family = AF_UNSPEC;
	ssa = (sockunion_t *)&gsr.gsr_source;
	ssa->ss.ss_family = AF_UNSPEC;
	switch (sopt->sopt_name) {
	case IP_DROP_MEMBERSHIP:
	case IP_DROP_SOURCE_MEMBERSHIP:
		if (sopt->sopt_name == IP_DROP_MEMBERSHIP) {
			error = sooptcopyin(sopt, &mreqs,
			    sizeof(struct ip_mreq),
			    sizeof(struct ip_mreq));
			mreqs.imr_interface = mreqs.imr_sourceaddr;
			mreqs.imr_sourceaddr.s_addr = INADDR_ANY;
		} else if (sopt->sopt_name == IP_DROP_SOURCE_MEMBERSHIP) {
			error = sooptcopyin(sopt, &mreqs,
			    sizeof(struct ip_mreq_source),
			    sizeof(struct ip_mreq_source));
		}
		if (error)
			return (error);
		gsa->sin.sin_family = AF_INET;
		gsa->sin.sin_len = sizeof(struct sockaddr_in);
		gsa->sin.sin_addr = mreqs.imr_multiaddr;
		if (sopt->sopt_name == IP_DROP_SOURCE_MEMBERSHIP) {
			ssa->sin.sin_family = AF_INET;
			ssa->sin.sin_len = sizeof(struct sockaddr_in);
			ssa->sin.sin_addr = mreqs.imr_sourceaddr;
		}
		if (!in_nullhost(mreqs.imr_interface))
			INADDR_TO_IFP(mreqs.imr_interface, ifp);
		CTR3(KTR_IGMPV3, "%s: imr_interface = %s, ifp = %p",
		    __func__, inet_ntoa(mreqs.imr_interface), ifp);
		break;
	case MCAST_LEAVE_GROUP:
	case MCAST_LEAVE_SOURCE_GROUP:
		if (sopt->sopt_name == MCAST_LEAVE_GROUP) {
			error = sooptcopyin(sopt, &gsr,
			    sizeof(struct group_req),
			    sizeof(struct group_req));
		} else if (sopt->sopt_name == MCAST_LEAVE_SOURCE_GROUP) {
			error = sooptcopyin(sopt, &gsr,
			    sizeof(struct group_source_req),
			    sizeof(struct group_source_req));
		}
		if (error)
			return (error);
		if (gsa->sin.sin_family != AF_INET ||
		    gsa->sin.sin_len != sizeof(struct sockaddr_in))
			return (EINVAL);
		if (sopt->sopt_name == MCAST_LEAVE_SOURCE_GROUP) {
			if (ssa->sin.sin_family != AF_INET ||
			    ssa->sin.sin_len != sizeof(struct sockaddr_in))
				return (EINVAL);
		}
		if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
			return (EADDRNOTAVAIL);
		ifp = ifnet_byindex(gsr.gsr_interface);
		if (ifp == NULL)
			return (EADDRNOTAVAIL);
		break;
	default:
		CTR2(KTR_IGMPV3, "%s: unknown sopt_name %d",
		    __func__, sopt->sopt_name);
		return (EOPNOTSUPP);
		break;
	}
	if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
		return (EINVAL);
	imo = inp_findmoptions(inp);
	idx = imo_match_group(imo, ifp, &gsa->sa);
	if (idx == -1) {
		error = EADDRNOTAVAIL;
		goto out_inp_locked;
	}
	inm = imo->imo_membership[idx];
	imf = &imo->imo_mfilters[idx];
	if (ssa->ss.ss_family != AF_UNSPEC)
		is_final = 0;
	INP_WLOCK_ASSERT(inp);
	if (is_final) {
		imf_leave(imf);
	} else {
		if (imf->imf_st[0] == MCAST_EXCLUDE) {
			error = EADDRNOTAVAIL;
			goto out_inp_locked;
		}
		ims = imo_match_source(imo, idx, &ssa->sa);
		if (ims == NULL) {
			CTR3(KTR_IGMPV3, "%s: source %s %spresent", __func__,
			    inet_ntoa(ssa->sin.sin_addr), "not ");
			error = EADDRNOTAVAIL;
			goto out_inp_locked;
		}
		CTR2(KTR_IGMPV3, "%s: %s source", __func__, "block");
		error = imf_prune(imf, &ssa->sin);
		if (error) {
			CTR1(KTR_IGMPV3, "%s: merge imf state failed",
			    __func__);
			goto out_inp_locked;
		}
	}
	IN_MULTI_LOCK();
	if (is_final) {
		(void)in_leavegroup_locked(inm, imf);
	} else {
		CTR1(KTR_IGMPV3, "%s: merge inm state", __func__);
		error = inm_merge(inm, imf);
		if (error) {
			CTR1(KTR_IGMPV3, "%s: failed to merge inm state",
			    __func__);
			goto out_in_multi_locked;
		}
		CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__);
		error = igmp_change_state(inm);
		if (error) {
			CTR1(KTR_IGMPV3, "%s: failed igmp downcall",
			    __func__);
		}
	}
out_in_multi_locked:
	IN_MULTI_UNLOCK();
	if (error)
		imf_rollback(imf);
	else
		imf_commit(imf);
	imf_reap(imf);
	if (is_final) {
		for (++idx; idx < imo->imo_num_memberships; ++idx) {
			imo->imo_membership[idx-1] = imo->imo_membership[idx];
			imo->imo_mfilters[idx-1] = imo->imo_mfilters[idx];
		}
		imo->imo_num_memberships--;
	}
out_inp_locked:
	INP_WUNLOCK(inp);
	return (error);
}
static int inp_leave_group ( struct inpcb * inp , struct sockopt * sopt ) { struct group_source_req gsr ; struct ip_mreq_source mreqs ; sockunion_t * gsa , * ssa ; struct ifnet * ifp ; struct in_mfilter * imf ; struct ip_moptions * imo ; struct in_msource * ims ; struct in_multi * inm ; size_t idx ; int error , is_final ; ifp = NULL ; error = 0 ; is_final = 1 ; memset ( & gsr , 0 , sizeof ( struct group_source_req ) ) ; gsa = ( sockunion_t * ) & gsr . gsr_group ; gsa -> ss . ss_family = AF_UNSPEC ; ssa = ( sockunion_t * ) & gsr . gsr_source ; ssa -> ss . ss_family = AF_UNSPEC ; switch ( sopt -> sopt_name ) { case IP_DROP_MEMBERSHIP : case IP_DROP_SOURCE_MEMBERSHIP : if ( sopt -> sopt_name == IP_DROP_MEMBERSHIP ) { error = sooptcopyin ( sopt , & mreqs , sizeof ( struct ip_mreq ) , sizeof ( struct ip_mreq ) ) ; mreqs . imr_interface = mreqs . imr_sourceaddr ; mreqs . imr_sourceaddr . s_addr = INADDR_ANY ; } else if ( sopt -> sopt_name == IP_DROP_SOURCE_MEMBERSHIP ) { error = sooptcopyin ( sopt , & mreqs , sizeof ( struct ip_mreq_source ) , sizeof ( struct ip_mreq_source ) ) ; } if ( error ) return ( error ) ; gsa -> sin . sin_family = AF_INET ; gsa -> sin . sin_len = sizeof ( struct sockaddr_in ) ; gsa -> sin . sin_addr = mreqs . imr_multiaddr ; if ( sopt -> sopt_name == IP_DROP_SOURCE_MEMBERSHIP ) { ssa -> sin . sin_family = AF_INET ; ssa -> sin . sin_len = sizeof ( struct sockaddr_in ) ; ssa -> sin . sin_addr = mreqs . imr_sourceaddr ; } if ( ! in_nullhost ( mreqs . imr_interface ) ) INADDR_TO_IFP ( mreqs . imr_interface , ifp ) ; CTR3 ( KTR_IGMPV3 , " " , __func__ , inet_ntoa ( mreqs . imr_interface ) , ifp ) ; break ; case MCAST_LEAVE_GROUP : case MCAST_LEAVE_SOURCE_GROUP : if ( sopt -> sopt_name == MCAST_LEAVE_GROUP ) { error = sooptcopyin ( sopt , & gsr , sizeof ( struct group_req ) , sizeof ( struct group_req ) ) ; } else if ( sopt -> sopt_name == MCAST_LEAVE_SOURCE_GROUP ) { error = sooptcopyin ( sopt , & gsr , sizeof ( struct group_source_req ) , sizeof ( struct group_source_req ) ) ; } if ( error ) return ( error ) ; if ( gsa -> sin . sin_family != AF_INET || gsa -> sin . sin_len != sizeof ( struct sockaddr_in ) ) return ( EINVAL ) ; if ( sopt -> sopt_name == MCAST_LEAVE_SOURCE_GROUP ) { if ( ssa -> sin . sin_family != AF_INET || ssa -> sin . sin_len != sizeof ( struct sockaddr_in ) ) return ( EINVAL ) ; } if ( gsr . gsr_interface == 0 || V_if_index < gsr . gsr_interface ) return ( EADDRNOTAVAIL ) ; ifp = ifnet_byindex ( gsr . gsr_interface ) ; if ( ifp == NULL ) return ( EADDRNOTAVAIL ) ; break ; default : CTR2 ( KTR_IGMPV3 , " " , __func__ , sopt -> sopt_name ) ; return ( EOPNOTSUPP ) ; break ; } if ( ! IN_MULTICAST ( ntohl ( gsa -> sin . sin_addr . s_addr ) ) ) return ( EINVAL ) ; imo = inp_findmoptions ( inp ) ; idx = imo_match_group ( imo , ifp , & gsa -> sa ) ; if ( idx == -1 ) { error = EADDRNOTAVAIL ; goto out_inp_locked ; } inm = imo -> imo_membership [ idx ] ; imf = & imo -> imo_mfilters [ idx ] ; if ( ssa -> ss . ss_family != AF_UNSPEC ) is_final = 0 ; INP_WLOCK_ASSERT ( inp ) ; if ( is_final ) { imf_leave ( imf ) ; } else { if ( imf -> imf_st [ 0 ] == MCAST_EXCLUDE ) { error = EADDRNOTAVAIL ; goto out_inp_locked ; } ims = imo_match_source ( imo , idx , & ssa -> sa ) ; if ( ims == NULL ) { CTR3 ( KTR_IGMPV3 , " " , __func__ , inet_ntoa ( ssa -> sin . sin_addr ) , " " ) ; error = EADDRNOTAVAIL ; goto out_inp_locked ; } CTR2 ( KTR_IGMPV3 , " " , __func__ , " " ) ; error = imf_prune ( imf , & ssa -> sin ) ; if ( error ) { CTR1 ( KTR_IGMPV3 , " " , __func__ ) ; goto out_inp_locked ; } } IN_MULTI_LOCK ( ) ; if ( is_final ) { ( void ) in_leavegroup_locked ( inm , imf ) ; } else { CTR1 ( KTR_IGMPV3 , " " , __func__ ) ; error = inm_merge ( inm , imf ) ; if ( error ) { CTR1 ( KTR_IGMPV3 , " " , __func__ ) ; goto out_in_multi_locked ; } CTR1 ( KTR_IGMPV3 , " " , __func__ ) ; error = igmp_change_state ( inm ) ; if ( error ) { CTR1 ( KTR_IGMPV3 , " " , __func__ ) ; } } out_in_multi_locked : IN_MULTI_UNLOCK ( ) ; if ( error ) imf_rollback ( imf ) ; else imf_commit ( imf ) ; imf_reap ( imf ) ; if ( is_final ) { for ( ++ idx ; idx < imo -> imo_num_memberships ; ++ idx ) { imo -> imo_membership [ idx - 1 ] = imo -> imo_membership [ idx ] ; imo -> imo_mfilters [ idx - 1 ] = imo -> imo_mfilters [ idx ] ; } imo -> imo_num_memberships -- ; } out_inp_locked : INP_WUNLOCK ( inp ) ; return ( error ) ; }
Leave an IPv4 multicast group on an inpcb, possibly with a source.
Leave an IPv4 multicast group on an inpcb possibly with a source .
/*
			 * Swap interface and sourceaddr arguments,
			 * as ip_mreq and ip_mreq_source are laid
			 * out differently.
			 */ /*
		 * Attempt to look up hinted ifp from interface address.
		 * Fallthrough with null ifp iff lookup fails, to
		 * preserve 4.4BSD mcast API idempotence.
		 * XXX NOTE WELL: The RFC 3678 API is preferred because
		 * using an IPv4 address as a key is racy.
		 */ /*
	 * Find the membership in the membership array.
	 */ /*
	 * Begin state merge transaction at socket layer.
	 */ /*
	 * If we were instructed only to leave a given source, do so.
	 * MCAST_LEAVE_SOURCE_GROUP is only valid for inclusive memberships.
	 */ /*
	 * Begin state merge transaction at IGMP layer.
	 */ /*
		 * Give up the multicast address record to which
		 * the membership points.
		 */ /* Remove the gap in the membership and filter array. */