mcast.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 2,496 行 · 第 1/4 页

C
2,496
字号
static intmld_scount(struct ifmcaddr6 *pmc, int type, int gdeleted, int sdeleted){	struct ip6_sf_list *psf;	int scount = 0;	for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {		if (!is_in(pmc, psf, type, gdeleted, sdeleted))			continue;		scount++;	}	return scount;}static struct sk_buff *mld_newpack(struct net_device *dev, int size){	struct sock *sk = igmp6_socket->sk;	struct sk_buff *skb;	struct mld2_report *pmr;	struct in6_addr addr_buf;	int err;	u8 ra[8] = { IPPROTO_ICMPV6, 0,		     IPV6_TLV_ROUTERALERT, 2, 0, 0,		     IPV6_TLV_PADN, 0 };	/* we assume size > sizeof(ra) here */	skb = sock_alloc_send_skb(sk, size + LL_RESERVED_SPACE(dev), 1, &err);	if (skb == 0)		return NULL;	skb_reserve(skb, LL_RESERVED_SPACE(dev));	if (dev->hard_header) {		unsigned char ha[MAX_ADDR_LEN];		ndisc_mc_map(&mld2_all_mcr, ha, dev, 1);		if (dev->hard_header(skb, dev, ETH_P_IPV6,ha,NULL,size) < 0) {			kfree_skb(skb);			return NULL;		}	}	if (ipv6_get_lladdr(dev, &addr_buf)) {		/* <draft-ietf-magma-mld-source-05.txt>:		 * use unspecified address as the source address 		 * when a valid link-local address is not available.		 */		memset(&addr_buf, 0, sizeof(addr_buf));	}	ip6_nd_hdr(sk, skb, dev, &addr_buf, &mld2_all_mcr, NEXTHDR_HOP, 0);	memcpy(skb_put(skb, sizeof(ra)), ra, sizeof(ra));	pmr =(struct mld2_report *)skb_put(skb, sizeof(*pmr));	skb->h.raw = (unsigned char *)pmr;	pmr->type = ICMPV6_MLD2_REPORT;	pmr->resv1 = 0;	pmr->csum = 0;	pmr->resv2 = 0;	pmr->ngrec = 0;	return skb;}static void mld_sendpack(struct sk_buff *skb){	struct ipv6hdr *pip6 = skb->nh.ipv6h;	struct mld2_report *pmr = (struct mld2_report *)skb->h.raw;	int payload_len, mldlen;	struct inet6_dev *idev = in6_dev_get(skb->dev);	int err;	IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS);	payload_len = skb->tail - (unsigned char *)skb->nh.ipv6h -		sizeof(struct ipv6hdr);	mldlen = skb->tail - skb->h.raw;	pip6->payload_len = htons(payload_len);	pmr->csum = csum_ipv6_magic(&pip6->saddr, &pip6->daddr, mldlen,		IPPROTO_ICMPV6, csum_partial(skb->h.raw, mldlen, 0));	err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev,		dev_queue_xmit);	if (!err) {		ICMP6_INC_STATS(idev,ICMP6_MIB_OUTMSGS);		IP6_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS);	} else		IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS);	if (likely(idev != NULL))		in6_dev_put(idev);}static int grec_size(struct ifmcaddr6 *pmc, int type, int gdel, int sdel){	return sizeof(struct mld2_grec) + 4*mld_scount(pmc,type,gdel,sdel);}static struct sk_buff *add_grhead(struct sk_buff *skb, struct ifmcaddr6 *pmc,	int type, struct mld2_grec **ppgr){	struct net_device *dev = pmc->idev->dev;	struct mld2_report *pmr;	struct mld2_grec *pgr;	if (!skb)		skb = mld_newpack(dev, dev->mtu);	if (!skb)		return NULL;	pgr = (struct mld2_grec *)skb_put(skb, sizeof(struct mld2_grec));	pgr->grec_type = type;	pgr->grec_auxwords = 0;	pgr->grec_nsrcs = 0;	pgr->grec_mca = pmc->mca_addr;	/* structure copy */	pmr = (struct mld2_report *)skb->h.raw;	pmr->ngrec = htons(ntohs(pmr->ngrec)+1);	*ppgr = pgr;	return skb;}#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? (skb)->dev->mtu - (skb)->len : \	skb_tailroom(skb)) : 0)static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,	int type, int gdeleted, int sdeleted){	struct net_device *dev = pmc->idev->dev;	struct mld2_report *pmr;	struct mld2_grec *pgr = NULL;	struct ip6_sf_list *psf, *psf_next, *psf_prev, **psf_list;	int scount, first, isquery, truncate;	if (pmc->mca_flags & MAF_NOREPORT)		return skb;	isquery = type == MLD2_MODE_IS_INCLUDE ||		  type == MLD2_MODE_IS_EXCLUDE;	truncate = type == MLD2_MODE_IS_EXCLUDE ||		    type == MLD2_CHANGE_TO_EXCLUDE;	psf_list = sdeleted ? &pmc->mca_tomb : &pmc->mca_sources;	if (!*psf_list) {		if (type == MLD2_ALLOW_NEW_SOURCES ||		    type == MLD2_BLOCK_OLD_SOURCES)			return skb;		if (pmc->mca_crcount || isquery) {			/* make sure we have room for group header and at			 * least one source.			 */			if (skb && AVAILABLE(skb) < sizeof(struct mld2_grec)+			    sizeof(struct in6_addr)) {				mld_sendpack(skb);				skb = NULL; /* add_grhead will get a new one */			}			skb = add_grhead(skb, pmc, type, &pgr);		}		return skb;	}	pmr = skb ? (struct mld2_report *)skb->h.raw : NULL;	/* EX and TO_EX get a fresh packet, if needed */	if (truncate) {		if (pmr && pmr->ngrec &&		    AVAILABLE(skb) < grec_size(pmc, type, gdeleted, sdeleted)) {			if (skb)				mld_sendpack(skb);			skb = mld_newpack(dev, dev->mtu);		}	}	first = 1;	scount = 0;	psf_prev = NULL;	for (psf=*psf_list; psf; psf=psf_next) {		struct in6_addr *psrc;		psf_next = psf->sf_next;		if (!is_in(pmc, psf, type, gdeleted, sdeleted)) {			psf_prev = psf;			continue;		}		/* clear marks on query responses */		if (isquery)			psf->sf_gsresp = 0;		if (AVAILABLE(skb) < sizeof(*psrc) +		    first*sizeof(struct mld2_grec)) {			if (truncate && !first)				break;	 /* truncate these */			if (pgr)				pgr->grec_nsrcs = htons(scount);			if (skb)				mld_sendpack(skb);			skb = mld_newpack(dev, dev->mtu);			first = 1;			scount = 0;		}		if (first) {			skb = add_grhead(skb, pmc, type, &pgr);			first = 0;		}		psrc = (struct in6_addr *)skb_put(skb, sizeof(*psrc));		*psrc = psf->sf_addr;		scount++;		if ((type == MLD2_ALLOW_NEW_SOURCES ||		     type == MLD2_BLOCK_OLD_SOURCES) && psf->sf_crcount) {			psf->sf_crcount--;			if ((sdeleted || gdeleted) && psf->sf_crcount == 0) {				if (psf_prev)					psf_prev->sf_next = psf->sf_next;				else					*psf_list = psf->sf_next;				kfree(psf);				continue;			}		}		psf_prev = psf;	}	if (pgr)		pgr->grec_nsrcs = htons(scount);	if (isquery)		pmc->mca_flags &= ~MAF_GSQUERY;	/* clear query state */	return skb;}static void mld_send_report(struct inet6_dev *idev, struct ifmcaddr6 *pmc){	struct sk_buff *skb = NULL;	int type;	if (!pmc) {		read_lock_bh(&idev->lock);		for (pmc=idev->mc_list; pmc; pmc=pmc->next) {			if (pmc->mca_flags & MAF_NOREPORT)				continue;			spin_lock_bh(&pmc->mca_lock);			if (pmc->mca_sfcount[MCAST_EXCLUDE])				type = MLD2_MODE_IS_EXCLUDE;			else				type = MLD2_MODE_IS_INCLUDE;			skb = add_grec(skb, pmc, type, 0, 0);			spin_unlock_bh(&pmc->mca_lock);		}		read_unlock_bh(&idev->lock);	} else {		spin_lock_bh(&pmc->mca_lock);		if (pmc->mca_sfcount[MCAST_EXCLUDE])			type = MLD2_MODE_IS_EXCLUDE;		else			type = MLD2_MODE_IS_INCLUDE;		skb = add_grec(skb, pmc, type, 0, 0);		spin_unlock_bh(&pmc->mca_lock);	}	if (skb)		mld_sendpack(skb);}/* * remove zero-count source records from a source filter list */static void mld_clear_zeros(struct ip6_sf_list **ppsf){	struct ip6_sf_list *psf_prev, *psf_next, *psf;	psf_prev = NULL;	for (psf=*ppsf; psf; psf = psf_next) {		psf_next = psf->sf_next;		if (psf->sf_crcount == 0) {			if (psf_prev)				psf_prev->sf_next = psf->sf_next;			else				*ppsf = psf->sf_next;			kfree(psf);		} else			psf_prev = psf;	}}static void mld_send_cr(struct inet6_dev *idev){	struct ifmcaddr6 *pmc, *pmc_prev, *pmc_next;	struct sk_buff *skb = NULL;	int type, dtype;	read_lock_bh(&idev->lock);	write_lock_bh(&idev->mc_lock);	/* deleted MCA's */	pmc_prev = NULL;	for (pmc=idev->mc_tomb; pmc; pmc=pmc_next) {		pmc_next = pmc->next;		if (pmc->mca_sfmode == MCAST_INCLUDE) {			type = MLD2_BLOCK_OLD_SOURCES;			dtype = MLD2_BLOCK_OLD_SOURCES;			skb = add_grec(skb, pmc, type, 1, 0);			skb = add_grec(skb, pmc, dtype, 1, 1);		}		if (pmc->mca_crcount) {			pmc->mca_crcount--;			if (pmc->mca_sfmode == MCAST_EXCLUDE) {				type = MLD2_CHANGE_TO_INCLUDE;				skb = add_grec(skb, pmc, type, 1, 0);			}			if (pmc->mca_crcount == 0) {				mld_clear_zeros(&pmc->mca_tomb);				mld_clear_zeros(&pmc->mca_sources);			}		}		if (pmc->mca_crcount == 0 && !pmc->mca_tomb &&		    !pmc->mca_sources) {			if (pmc_prev)				pmc_prev->next = pmc_next;			else				idev->mc_tomb = pmc_next;			in6_dev_put(pmc->idev);			kfree(pmc);		} else			pmc_prev = pmc;	}	write_unlock_bh(&idev->mc_lock);	/* change recs */	for (pmc=idev->mc_list; pmc; pmc=pmc->next) {		spin_lock_bh(&pmc->mca_lock);		if (pmc->mca_sfcount[MCAST_EXCLUDE]) {			type = MLD2_BLOCK_OLD_SOURCES;			dtype = MLD2_ALLOW_NEW_SOURCES;		} else {			type = MLD2_ALLOW_NEW_SOURCES;			dtype = MLD2_BLOCK_OLD_SOURCES;		}		skb = add_grec(skb, pmc, type, 0, 0);		skb = add_grec(skb, pmc, dtype, 0, 1);	/* deleted sources */		/* filter mode changes */		if (pmc->mca_crcount) {			pmc->mca_crcount--;			if (pmc->mca_sfmode == MCAST_EXCLUDE)				type = MLD2_CHANGE_TO_EXCLUDE;			else				type = MLD2_CHANGE_TO_INCLUDE;			skb = add_grec(skb, pmc, type, 0, 0);		}		spin_unlock_bh(&pmc->mca_lock);	}	read_unlock_bh(&idev->lock);	if (!skb)		return;	(void) mld_sendpack(skb);}static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type){	struct sock *sk = igmp6_socket->sk;	struct inet6_dev *idev;        struct sk_buff *skb;        struct icmp6hdr *hdr;	struct in6_addr *snd_addr;	struct in6_addr *addrp;	struct in6_addr addr_buf;	struct in6_addr all_routers;	int err, len, payload_len, full_len;	u8 ra[8] = { IPPROTO_ICMPV6, 0,		     IPV6_TLV_ROUTERALERT, 2, 0, 0,		     IPV6_TLV_PADN, 0 };	IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS);	snd_addr = addr;	if (type == ICMPV6_MGM_REDUCTION) {		snd_addr = &all_routers;		ipv6_addr_all_routers(&all_routers);	}	len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr);	payload_len = len + sizeof(ra);	full_len = sizeof(struct ipv6hdr) + payload_len;	skb = sock_alloc_send_skb(sk, LL_RESERVED_SPACE(dev) + full_len, 1, &err);	if (skb == NULL) {		IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS);		return;	}	skb_reserve(skb, LL_RESERVED_SPACE(dev));	if (dev->hard_header) {		unsigned char ha[MAX_ADDR_LEN];		ndisc_mc_map(snd_addr, ha, dev, 1);		if (dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, full_len) < 0)			goto out;	}	if (ipv6_get_lladdr(dev, &addr_buf)) {		/* <draft-ietf-magma-mld-source-05.txt>:		 * use unspecified address as the source address 		 * when a valid link-local address is not available.		 */		memset(&addr_buf, 0, sizeof(addr_buf));	}	ip6_nd_hdr(sk, skb, dev, &addr_buf, snd_addr, NEXTHDR_HOP, payload_len);	memcpy(skb_put(skb, sizeof(ra)), ra, sizeof(ra));	hdr = (struct icmp6hdr *) skb_put(skb, sizeof(struct icmp6hdr));	memset(hdr, 0, sizeof(struct icmp6hdr));	hdr->icmp6_type = type;	addrp = (struct in6_addr *) skb_put(skb, sizeof(struct in6_addr));	ipv6_addr_copy(addrp, addr);	hdr->icmp6_cksum = csum_ipv6_magic(&addr_buf, snd_addr, len,					   IPPROTO_ICMPV6,					   csum_partial((__u8 *) hdr, len, 0));	idev = in6_dev_get(skb->dev);	err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev,		dev_queue_xmit);	if (!err) {		if (type == ICMPV6_MGM_REDUCTION)			ICMP6_INC_STATS(idev, ICMP6_MIB_OUTGROUPMEMBREDUCTIONS);		else			ICMP6_INC_STATS(idev, ICMP6_MIB_OUTGROUPMEMBRESPONSES);		ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS);		IP6_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS);	} else		IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS);	if (likely(idev != NULL))		in6_dev_put(idev);	return;out:	IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS);	kfree_skb(skb);}static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode,	struct in6_addr *psfsrc){	struct ip6_sf_list *psf, *psf_prev;	int rv = 0;	psf_prev = NULL;	for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {		if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0)			break;		psf_prev = psf;	}	if (!psf || psf->sf_count[sfmode] == 0) {		/* source filter not found, or count wrong =>  bug */		return -ESRCH;	}	psf->sf_count[sfmode]--;	if (!psf->sf_count[MCAST_INCLUDE] && !psf->sf_count[MCAST_EXCLUDE]) {		struct inet6_dev *idev = pmc->idev;		/* no more filters for this source */		if (psf_prev)			psf_prev->sf_next = psf->sf_next;		else			pmc->mca_sources = psf->sf_next;		if (psf->sf_oldin && !(pmc->mca_flags & MAF_NOREPORT) &&		    !MLD_V1_SEEN(idev)) {			psf->sf_crcount = idev->mc_qrv;			psf->sf_next = pmc->mca_tomb;			pmc->mca_tomb = psf;			rv = 1;		} else			kfree(psf);	}	return rv;}int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,	int sfcount, struct in6_addr *psfsrc, int delta){	struct ifmcaddr6 *pmc;	int	changerec = 0;	int	i, err;	if (!idev)		return -ENODEV;	read_lock_bh(&idev->lock);	for (pmc=idev->mc_list; pmc; pmc=pmc->next) {		if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0)			break;	}	if (!pmc) {		/* MCA not found?? bug */		read_unlock_bh(&idev->lock);		return -ESRCH;	}	spin_lock_bh(&pmc->mca_lock);	sf_markstate(pmc);	if (!delta) {		if (!pmc->mca_sfcount[sfmode]) {			spin_unlock_bh(&pmc->mca_lock);			read_unlock_bh(&idev->lock);			return -EINVAL;		}		pmc->mca_sfcount[sfmode]--;	}	err = 0;	for (i=0; i<sfcount; i++) {		int rv = ip6_mc_del1_src(pmc, sfmode, &psfsrc[i]);		changerec |= rv > 0;		if (!err && rv < 0)			err = rv;	}	if (pmc->mca_sfmode == MCAST_EXCLUDE &&	    pmc->mca_sfcount[MCAST_EXCLUDE] == 0 &&	    pmc->mca_sfcount[MCAST_INCLUDE]) {		struct ip6_sf_list *psf;		/* filter mode change */		pmc->mca_sfmode = MCAST_INCLUDE;		pmc->mca_crcount = idev->mc_qrv;		idev->mc_ifc_count = pmc->mca_crcount;		for (psf=pmc->mca_sources; psf; psf = psf->sf_next)			psf->sf_crcount = 0;		mld_ifc_event(pmc->idev);	} else if (sf_setstate(pmc) || changerec)		mld_ifc_event(pmc->idev);	spin_unlock_bh(&pmc->mca_lock);	read_unlock_bh(&idev->lock);	return err;}/* * Add multicast single-source filter to the interface list */static int ip6_mc_add1_src(struct ifmcaddr6 *pmc, int sfmode,	struct in6_addr *psfsrc, int delta){	struct ip6_sf_list *psf, *psf_prev;	psf_prev = NULL;	for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {		if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0)			break;		psf_prev = psf;	}	if (!psf) {		psf = (struct ip6_sf_list *)kmalloc(sizeof(*psf), GFP_ATOMIC);		if (!psf)			return -ENOBUFS;		memset(psf, 0, sizeof(*psf));		psf->sf_addr = *psfsrc;		if (psf_prev) {			psf_prev->sf_next = psf;		} else			pmc->mca_sources = psf;	}	psf->sf_count[sfmode]++;	return 0;}static void sf_markstate(struct ifmcaddr6 *pmc){	struct ip6_sf_list *psf;	int mca_xcount = pmc->mca_sfcount[MCAST_EXCLUDE];	for (psf=pmc->mca_sources; psf; psf=psf->sf_next)		if (pmc->mca_sfcount[MCAST_EXCLUDE]) {			psf->sf_oldin = mca_xcount ==				psf->sf_count[MCAST_EXCLUDE] &&				!psf->sf_count[MCAST_INCLUDE];		} else			psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0;}static int sf_setstate(struct ifmcaddr6 *pmc){	struct ip6_sf_list *psf;	int mca_xcount = pmc->mca_sfcount[MCAST_EXCLUDE];	int qrv = pmc->idev->mc_qrv;	int new_in, rv;	rv = 0;	for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {		if (pmc->mca_sfcount[MCAST_EXCLUDE]) {			new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] &&				!psf->sf_count[MCAST_INCLUDE];		} else			new_in = psf->sf_count[MCAST_INCLUDE] != 0;		if (new_in != psf->sf_oldin) {			psf->sf_crcount = qrv;			rv++;		}	}	return rv;}/* * Add multicast source filter list to the interface list */int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,	int sfcount, struct in6_addr *psfsrc, int delta){	struct ifmcaddr6 *pmc;	int	isexclude;	int	i, err;	if (!idev)		return -ENODEV;	read_lock_bh(&idev->lock);	for (pmc=idev->mc_list; pmc; pmc=pmc->next) {		if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0)			break;	}	if (!pmc) {		/* MCA not found?? bug */		read_unlock_bh(&idev->lock);		return -ESRCH;	}	spin_lock_bh(&pmc->mca_lock);	sf_markstate(pmc);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?