📄 mcast.c
字号:
/* * Multicast support for IPv6 * Linux INET6 implementation * * Authors: * Pedro Roque <roque@di.fc.ul.pt> * * $Id: mcast.c,v 1.40 2002/02/08 03:57:19 davem Exp $ * * Based on linux/ipv4/igmp.c and linux/ipv4/ip_sockglue.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *//* Changes: * * yoshfuji : fix format of router-alert option * YOSHIFUJI Hideaki @USAGI: * Fixed source address for MLD message based on * <draft-ietf-magma-mld-source-05.txt>. * YOSHIFUJI Hideaki @USAGI: * - Ignore Queries for invalid addresses. * - MLD for link-local addresses. * David L Stevens <dlstevens@us.ibm.com>: * - MLDv2 support */#include <linux/module.h>#include <linux/errno.h>#include <linux/types.h>#include <linux/string.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/jiffies.h>#include <linux/times.h>#include <linux/net.h>#include <linux/in.h>#include <linux/in6.h>#include <linux/netdevice.h>#include <linux/if_arp.h>#include <linux/route.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv6.h>#include <net/net_namespace.h>#include <net/sock.h>#include <net/snmp.h>#include <net/ipv6.h>#include <net/protocol.h>#include <net/if_inet6.h>#include <net/ndisc.h>#include <net/addrconf.h>#include <net/ip6_route.h>#include <net/ip6_checksum.h>/* Set to 3 to get tracing... */#define MCAST_DEBUG 2#if MCAST_DEBUG >= 3#define MDBG(x) printk x#else#define MDBG(x)#endif/* * These header formats should be in a separate include file, but icmpv6.h * doesn't have in6_addr defined in all cases, there is no __u128, and no * other files reference these. * * +-DLS 4/14/03 *//* Multicast Listener Discovery version 2 headers */struct mld2_grec { __u8 grec_type; __u8 grec_auxwords; __be16 grec_nsrcs; struct in6_addr grec_mca; struct in6_addr grec_src[0];};struct mld2_report { __u8 type; __u8 resv1; __sum16 csum; __be16 resv2; __be16 ngrec; struct mld2_grec grec[0];};struct mld2_query { __u8 type; __u8 code; __sum16 csum; __be16 mrc; __be16 resv1; struct in6_addr mca;#if defined(__LITTLE_ENDIAN_BITFIELD) __u8 qrv:3, suppress:1, resv2:4;#elif defined(__BIG_ENDIAN_BITFIELD) __u8 resv2:4, suppress:1, qrv:3;#else#error "Please fix <asm/byteorder.h>"#endif __u8 qqic; __be16 nsrcs; struct in6_addr srcs[0];};static struct in6_addr mld2_all_mcr = MLD2_ALL_MCR_INIT;/* Big mc list lock for all the sockets */static DEFINE_RWLOCK(ipv6_sk_mc_lock);static struct socket *igmp6_socket;int __ipv6_dev_mc_dec(struct inet6_dev *idev, struct in6_addr *addr);static void igmp6_join_group(struct ifmcaddr6 *ma);static void igmp6_leave_group(struct ifmcaddr6 *ma);static void igmp6_timer_handler(unsigned long data);static void mld_gq_timer_expire(unsigned long data);static void mld_ifc_timer_expire(unsigned long data);static void mld_ifc_event(struct inet6_dev *idev);static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *pmc);static void mld_del_delrec(struct inet6_dev *idev, struct in6_addr *addr);static void mld_clear_delrec(struct inet6_dev *idev);static int sf_setstate(struct ifmcaddr6 *pmc);static void sf_markstate(struct ifmcaddr6 *pmc);static void ip6_mc_clear_src(struct ifmcaddr6 *pmc);static int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, int sfcount, struct in6_addr *psfsrc, int delta);static int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, int sfcount, struct in6_addr *psfsrc, int delta);static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, struct inet6_dev *idev);#define IGMP6_UNSOLICITED_IVAL (10*HZ)#define MLD_QRV_DEFAULT 2#define MLD_V1_SEEN(idev) (ipv6_devconf.force_mld_version == 1 || \ (idev)->cnf.force_mld_version == 1 || \ ((idev)->mc_v1_seen && \ time_before(jiffies, (idev)->mc_v1_seen)))#define MLDV2_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value))#define MLDV2_EXP(thresh, nbmant, nbexp, value) \ ((value) < (thresh) ? (value) : \ ((MLDV2_MASK(value, nbmant) | (1<<(nbmant))) << \ (MLDV2_MASK((value) >> (nbmant), nbexp) + (nbexp))))#define MLDV2_QQIC(value) MLDV2_EXP(0x80, 4, 3, value)#define MLDV2_MRC(value) MLDV2_EXP(0x8000, 12, 3, value)#define IPV6_MLD_MAX_MSF 64int sysctl_mld_max_msf __read_mostly = IPV6_MLD_MAX_MSF;/* * socket join on multicast group */int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr){ struct net_device *dev = NULL; struct ipv6_mc_socklist *mc_lst; struct ipv6_pinfo *np = inet6_sk(sk); int err; if (!ipv6_addr_is_multicast(addr)) return -EINVAL; read_lock_bh(&ipv6_sk_mc_lock); for (mc_lst=np->ipv6_mc_list; mc_lst; mc_lst=mc_lst->next) { if ((ifindex == 0 || mc_lst->ifindex == ifindex) && ipv6_addr_equal(&mc_lst->addr, addr)) { read_unlock_bh(&ipv6_sk_mc_lock); return -EADDRINUSE; } } read_unlock_bh(&ipv6_sk_mc_lock); mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL); if (mc_lst == NULL) return -ENOMEM; mc_lst->next = NULL; ipv6_addr_copy(&mc_lst->addr, addr); if (ifindex == 0) { struct rt6_info *rt; rt = rt6_lookup(addr, NULL, 0, 0); if (rt) { dev = rt->rt6i_dev; dev_hold(dev); dst_release(&rt->u.dst); } } else dev = dev_get_by_index(&init_net, ifindex); if (dev == NULL) { sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); return -ENODEV; } mc_lst->ifindex = dev->ifindex; mc_lst->sfmode = MCAST_EXCLUDE; rwlock_init(&mc_lst->sflock); mc_lst->sflist = NULL; /* * now add/increase the group membership on the device */ err = ipv6_dev_mc_inc(dev, addr); if (err) { sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); dev_put(dev); return err; } write_lock_bh(&ipv6_sk_mc_lock); mc_lst->next = np->ipv6_mc_list; np->ipv6_mc_list = mc_lst; write_unlock_bh(&ipv6_sk_mc_lock); dev_put(dev); return 0;}/* * socket leave on multicast group */int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr){ struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_mc_socklist *mc_lst, **lnk; write_lock_bh(&ipv6_sk_mc_lock); for (lnk = &np->ipv6_mc_list; (mc_lst = *lnk) !=NULL ; lnk = &mc_lst->next) { if ((ifindex == 0 || mc_lst->ifindex == ifindex) && ipv6_addr_equal(&mc_lst->addr, addr)) { struct net_device *dev; *lnk = mc_lst->next; write_unlock_bh(&ipv6_sk_mc_lock); if ((dev = dev_get_by_index(&init_net, mc_lst->ifindex)) != NULL) { struct inet6_dev *idev = in6_dev_get(dev); (void) ip6_mc_leave_src(sk, mc_lst, idev); if (idev) { __ipv6_dev_mc_dec(idev, &mc_lst->addr); in6_dev_put(idev); } dev_put(dev); } else (void) ip6_mc_leave_src(sk, mc_lst, NULL); sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); return 0; } } write_unlock_bh(&ipv6_sk_mc_lock); return -EADDRNOTAVAIL;}static struct inet6_dev *ip6_mc_find_dev(struct in6_addr *group, int ifindex){ struct net_device *dev = NULL; struct inet6_dev *idev = NULL; if (ifindex == 0) { struct rt6_info *rt; rt = rt6_lookup(group, NULL, 0, 0); if (rt) { dev = rt->rt6i_dev; dev_hold(dev); dst_release(&rt->u.dst); } } else dev = dev_get_by_index(&init_net, ifindex); if (!dev) return NULL; idev = in6_dev_get(dev); if (!idev) { dev_put(dev); return NULL; } read_lock_bh(&idev->lock); if (idev->dead) { read_unlock_bh(&idev->lock); in6_dev_put(idev); dev_put(dev); return NULL; } return idev;}void ipv6_sock_mc_close(struct sock *sk){ struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_mc_socklist *mc_lst; write_lock_bh(&ipv6_sk_mc_lock); while ((mc_lst = np->ipv6_mc_list) != NULL) { struct net_device *dev; np->ipv6_mc_list = mc_lst->next; write_unlock_bh(&ipv6_sk_mc_lock); dev = dev_get_by_index(&init_net, mc_lst->ifindex); if (dev) { struct inet6_dev *idev = in6_dev_get(dev); (void) ip6_mc_leave_src(sk, mc_lst, idev); if (idev) { __ipv6_dev_mc_dec(idev, &mc_lst->addr); in6_dev_put(idev); } dev_put(dev); } else (void) ip6_mc_leave_src(sk, mc_lst, NULL); sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); write_lock_bh(&ipv6_sk_mc_lock); } write_unlock_bh(&ipv6_sk_mc_lock);}int ip6_mc_source(int add, int omode, struct sock *sk, struct group_source_req *pgsr){ struct in6_addr *source, *group; struct ipv6_mc_socklist *pmc; struct net_device *dev; struct inet6_dev *idev; struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ip6_sf_socklist *psl; int i, j, rv; int leavegroup = 0; int pmclocked = 0; int err; if (pgsr->gsr_group.ss_family != AF_INET6 || pgsr->gsr_source.ss_family != AF_INET6) return -EINVAL; source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr; group = &((struct sockaddr_in6 *)&pgsr->gsr_group)->sin6_addr; if (!ipv6_addr_is_multicast(group)) return -EINVAL; idev = ip6_mc_find_dev(group, pgsr->gsr_interface); if (!idev) return -ENODEV; dev = idev->dev; err = -EADDRNOTAVAIL; read_lock_bh(&ipv6_sk_mc_lock); for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface) continue; if (ipv6_addr_equal(&pmc->addr, group)) break; } if (!pmc) { /* must have a prior join */ err = -EINVAL; goto done; } /* if a source filter was set, must be the same mode as before */ if (pmc->sflist) { if (pmc->sfmode != omode) { err = -EINVAL; goto done; } } else if (pmc->sfmode != omode) { /* allow mode switches for empty-set filters */ ip6_mc_add_src(idev, group, omode, 0, NULL, 0); ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0); pmc->sfmode = omode; } write_lock_bh(&pmc->sflock); pmclocked = 1; psl = pmc->sflist; if (!add) { if (!psl) goto done; /* err = -EADDRNOTAVAIL */ rv = !0; for (i=0; i<psl->sl_count; i++) { rv = memcmp(&psl->sl_addr[i], source, sizeof(struct in6_addr)); if (rv == 0) break; } if (rv) /* source not found */ goto done; /* err = -EADDRNOTAVAIL */ /* special case - (INCLUDE, empty) == LEAVE_GROUP */ if (psl->sl_count == 1 && omode == MCAST_INCLUDE) { leavegroup = 1; goto done; } /* update the interface filter */ ip6_mc_del_src(idev, group, omode, 1, source, 1); for (j=i+1; j<psl->sl_count; j++) psl->sl_addr[j-1] = psl->sl_addr[j]; psl->sl_count--; err = 0; goto done; } /* else, add a new source to the filter */ if (psl && psl->sl_count >= sysctl_mld_max_msf) { err = -ENOBUFS; goto done; } if (!psl || psl->sl_count == psl->sl_max) { struct ip6_sf_socklist *newpsl; int count = IP6_SFBLOCK; if (psl) count += psl->sl_max; newpsl = sock_kmalloc(sk, IP6_SFLSIZE(count), GFP_ATOMIC); if (!newpsl) { err = -ENOBUFS; goto done; } newpsl->sl_max = count; newpsl->sl_count = count - IP6_SFBLOCK; if (psl) { for (i=0; i<psl->sl_count; i++) newpsl->sl_addr[i] = psl->sl_addr[i]; sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max)); } pmc->sflist = psl = newpsl; } rv = 1; /* > 0 for insert logic below if sl_count is 0 */ for (i=0; i<psl->sl_count; i++) { rv = memcmp(&psl->sl_addr[i], source, sizeof(struct in6_addr)); if (rv == 0) break; } if (rv == 0) /* address already there is an error */ goto done; for (j=psl->sl_count-1; j>=i; j--) psl->sl_addr[j+1] = psl->sl_addr[j]; psl->sl_addr[i] = *source; psl->sl_count++; err = 0; /* update the interface list */ ip6_mc_add_src(idev, group, omode, 1, source, 1);done: if (pmclocked) write_unlock_bh(&pmc->sflock); read_unlock_bh(&ipv6_sk_mc_lock); read_unlock_bh(&idev->lock); in6_dev_put(idev); dev_put(dev); if (leavegroup) return ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group); return err;}int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf){ struct in6_addr *group; struct ipv6_mc_socklist *pmc; struct net_device *dev; struct inet6_dev *idev; struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ip6_sf_socklist *newpsl, *psl; int leavegroup = 0; int i, err; group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr; if (!ipv6_addr_is_multicast(group)) return -EINVAL; if (gsf->gf_fmode != MCAST_INCLUDE && gsf->gf_fmode != MCAST_EXCLUDE) return -EINVAL; idev = ip6_mc_find_dev(group, gsf->gf_interface); if (!idev) return -ENODEV; dev = idev->dev; err = 0; read_lock_bh(&ipv6_sk_mc_lock); if (gsf->gf_fmode == MCAST_INCLUDE && gsf->gf_numsrc == 0) { leavegroup = 1; goto done; } for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -