/* * Multicast support for IPv6 * Linux INET6 implementation * * Authors: * Pedro Roque * * $Id: mcast.c,v 1.10 1997/05/07 09:40:22 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. */ #define __NO_VERSION__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Set to 3 to get tracing... */ #define MCAST_DEBUG 2 #if MCAST_DEBUG >= 3 #define MDBG(x) printk x #else #define MDBG(x) #endif static struct inode igmp6_inode; static struct socket *igmp6_socket=&igmp6_inode.u.socket_i; static void igmp6_join_group(struct ifmcaddr6 *ma); static void igmp6_leave_group(struct ifmcaddr6 *ma); void igmp6_timer_handler(unsigned long data); #define IGMP6_UNSOLICITED_IVAL (10*HZ) /* * socket join on multicast group */ int ipv6_sock_mc_join(struct sock *sk, struct device *dev, struct in6_addr *addr) { struct ipv6_mc_socklist *mc_lst; struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; int err; MDBG(("ipv6_sock_mc_join(%s) addr[", dev ? dev->name : "[NULL]")); MDBG(("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]\n", addr->s6_addr16[0], addr->s6_addr16[1], addr->s6_addr16[2], addr->s6_addr16[3], addr->s6_addr16[4], addr->s6_addr16[5], addr->s6_addr16[6], addr->s6_addr16[7])); if (!(ipv6_addr_type(addr) & IPV6_ADDR_MULTICAST)) return -EINVAL; if(!(dev->flags & IFF_MULTICAST)) return -EADDRNOTAVAIL; mc_lst = kmalloc(sizeof(struct ipv6_mc_socklist), GFP_KERNEL); if (mc_lst == NULL) return -ENOMEM; mc_lst->next = NULL; memcpy(&mc_lst->addr, addr, sizeof(struct in6_addr)); mc_lst->dev = dev; /* * now add/increase the group membership on the device */ err = ipv6_dev_mc_inc(dev, addr); if (err) { kfree(mc_lst); return err; } mc_lst->next = np->ipv6_mc_list; np->ipv6_mc_list = mc_lst; return 0; } /* * socket leave on multicast group */ int ipv6_sock_mc_drop(struct sock *sk, struct device *dev, struct in6_addr *addr) { struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; struct ipv6_mc_socklist *mc_lst, **lnk; lnk = &np->ipv6_mc_list; MDBG(("ipv6_sock_mc_drop(%s) addr[", dev ? dev->name : "[NULL]")); MDBG(("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]\n", addr->s6_addr16[0], addr->s6_addr16[1], addr->s6_addr16[2], addr->s6_addr16[3], addr->s6_addr16[4], addr->s6_addr16[5], addr->s6_addr16[6], addr->s6_addr16[7])); for (mc_lst = *lnk ; mc_lst; mc_lst = mc_lst->next) { if (mc_lst->dev == dev && ipv6_addr_cmp(&mc_lst->addr, addr) == 0) { *lnk = mc_lst->next; ipv6_dev_mc_dec(mc_lst->dev, &mc_lst->addr); kfree(mc_lst); return 0; } lnk = &mc_lst->next; } return -ENOENT; } void ipv6_sock_mc_close(struct sock *sk) { struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6; struct ipv6_mc_socklist *mc_lst; for (mc_lst = np->ipv6_mc_list; mc_lst; ) { struct ipv6_mc_socklist *back; /* * leave group */ ipv6_dev_mc_dec(mc_lst->dev, &mc_lst->addr); back = mc_lst; mc_lst = mc_lst->next; kfree(back); } } /* * device multicast group inc (add if not found) */ int ipv6_dev_mc_inc(struct device *dev, struct in6_addr *addr) { struct ifmcaddr6 *mc; struct inet6_dev *idev; char buf[6]; int hash; MDBG(("ipv6_dev_mc_inc(%s) addr[", dev ? dev->name : "[NULL]")); MDBG(("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]\n", addr->s6_addr16[0], addr->s6_addr16[1], addr->s6_addr16[2], addr->s6_addr16[3], addr->s6_addr16[4], addr->s6_addr16[5], addr->s6_addr16[6], addr->s6_addr16[7])); hash = ipv6_devindex_hash(dev->ifindex); for (idev = inet6_dev_lst[hash]; idev; idev=idev->next) if (idev->dev == dev) break; if (idev == NULL) { printk(KERN_DEBUG "ipv6_dev_mc_inc: device not found\n"); return -EINVAL; } hash = ipv6_addr_hash(addr); for (mc = inet6_mcast_lst[hash]; mc; mc = mc->next) { if ((ipv6_addr_cmp(&mc->mca_addr, addr) == 0) && (mc->dev->ifindex == dev->ifindex)) { atomic_inc(&mc->mca_users); return 0; } } /* * not found: create a new one. */ mc = kmalloc(sizeof(struct ifmcaddr6), GFP_ATOMIC); if (mc == NULL) return -ENOMEM; MDBG(("create new ipv6 MC entry, ")); memset(mc, 0, sizeof(struct ifmcaddr6)); mc->mca_timer.function = igmp6_timer_handler; mc->mca_timer.data = (unsigned long) mc; memcpy(&mc->mca_addr, addr, sizeof(struct in6_addr)); mc->dev = dev; atomic_set(&mc->mca_users, 1); mc->next = inet6_mcast_lst[hash]; inet6_mcast_lst[hash] = mc; mc->if_next = idev->mc_list; idev->mc_list = mc; /* * multicast mapping is defined in IPv6-over-foo documents */ switch (dev->type) { case ARPHRD_ETHER: ipv6_mc_map(addr, buf); MDBG(("ARPHRD_ETHER[%02x:%02x:%02x:%02x:%02x:%02x] dev_mc_add()\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5])); dev_mc_add(dev, buf, ETH_ALEN, 0); break; default: printk(KERN_DEBUG "dev_mc_inc: unkown device type\n"); }; igmp6_join_group(mc); return 0; } static void ipv6_mca_remove(struct device *dev, struct ifmcaddr6 *ma) { struct inet6_dev *idev; idev = ipv6_get_idev(dev); if (idev) { struct ifmcaddr6 *iter, **lnk; lnk = &idev->mc_list; for (iter = *lnk; iter; iter = iter->if_next) { if (iter == ma) { *lnk = iter->if_next; break; } lnk = &iter->if_next; } } } /* * device multicast group del */ int ipv6_dev_mc_dec(struct device *dev, struct in6_addr *addr) { struct ifmcaddr6 *ma, **lnk; int hash; hash = ipv6_addr_hash(addr); lnk = &inet6_mcast_lst[hash]; for (ma = inet6_mcast_lst[hash]; ma; ma = ma->next) { if (ipv6_addr_cmp(&ma->mca_addr, addr) == 0) { if (atomic_dec_and_test(&ma->mca_users)) { igmp6_leave_group(ma); *lnk = ma->next; ipv6_mca_remove(ma->dev, ma); kfree(ma); } return 0; } lnk = &ma->next; } return -ENOENT; } /* * check if the interface/address pair is valid */ int ipv6_chk_mcast_addr(struct device *dev, struct in6_addr *addr) { struct ifmcaddr6 *mc; int hash; hash = ipv6_addr_hash(addr); for (mc = inet6_mcast_lst[hash]; mc; mc=mc->next) { if ((mc->dev == dev) && ipv6_addr_cmp(&mc->mca_addr, addr) == 0) return 1; } return 0; } /* * IGMP handling (alias multicast ICMPv6 messages) */ static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime) { unsigned long delay; ma->mca_flags |= MAF_TIMER_RUNNING; delay = ipv6_random() % resptime; ma->mca_timer.expires = jiffies + delay; add_timer(&ma->mca_timer); } int igmp6_event_query(struct sk_buff *skb, struct icmp6hdr *hdr, int len) { struct ifmcaddr6 *ma; struct in6_addr *addrp; unsigned long resptime; if (len < sizeof(struct icmp6hdr) + sizeof(struct ipv6hdr)) return -EINVAL; resptime = hdr->icmp6_maxdelay; addrp = (struct in6_addr *) (hdr + 1); if (ipv6_addr_any(addrp)) { struct inet6_dev *idev; idev = ipv6_get_idev(skb->dev); if (idev == NULL) return 0; for (ma = idev->mc_list; ma; ma=ma->if_next) igmp6_group_queried(ma, resptime); } else { int hash = ipv6_addr_hash(addrp); for (ma = inet6_mcast_lst[hash]; ma; ma=ma->next) { if (ma->dev == skb->dev && ipv6_addr_cmp(addrp, &ma->mca_addr) == 0) { igmp6_group_queried(ma, resptime); break; } } } return 0; } int igmp6_event_report(struct sk_buff *skb, struct icmp6hdr *hdr, int len) { struct ifmcaddr6 *ma; struct in6_addr *addrp; struct device *dev; int hash; if (len < sizeof(struct icmp6hdr) + sizeof(struct ipv6hdr)) return -EINVAL; addrp = (struct in6_addr *) (hdr + 1); dev = skb->dev; /* * Cancel the timer for this group */ hash = ipv6_addr_hash(addrp); for (ma = inet6_mcast_lst[hash]; ma; ma=ma->next) { if ((ma->dev == dev) && ipv6_addr_cmp(&ma->mca_addr, addrp) == 0) { if (ma->mca_flags & MAF_TIMER_RUNNING) { del_timer(&ma->mca_timer); ma->mca_flags &= ~MAF_TIMER_RUNNING; } ma->mca_flags &= ~MAF_LAST_REPORTER; break; } } return 0; } void igmp6_send(struct in6_addr *addr, struct device *dev, int type) { struct sock *sk = igmp6_socket->sk; struct sk_buff *skb; struct icmp6hdr *hdr; struct inet6_ifaddr *ifp; struct in6_addr *addrp; int err, len, plen; len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr); plen = sizeof(struct ipv6hdr) + len; skb = sock_alloc_send_skb(sk, dev->hard_header_len + plen, 0, 0, &err); if (skb == NULL) return; if (dev->hard_header_len) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15); if (dev->hard_header) { unsigned char ha[MAX_ADDR_LEN]; ipv6_mc_map(addr, ha); dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, plen); skb->arp = 1; } } ifp = ipv6_get_lladdr(dev); if (ifp == NULL) { #if MCAST_DEBUG >= 1 printk(KERN_DEBUG "igmp6: %s no linklocal address\n", dev->name); #endif return; } ip6_nd_hdr(sk, skb, dev, &ifp->addr, addr, IPPROTO_ICMPV6, len); /* * need hop-by-hop router alert option. */ 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(&ifp->addr, addr, len, IPPROTO_ICMPV6, csum_partial((__u8 *) hdr, len, 0)); dev_queue_xmit(skb); } static void igmp6_join_group(struct ifmcaddr6 *ma) { unsigned long delay; int addr_type; addr_type = ipv6_addr_type(&ma->mca_addr); if ((addr_type & IPV6_ADDR_LINKLOCAL)) return; igmp6_send(&ma->mca_addr, ma->dev, ICMPV6_MGM_REPORT); delay = ipv6_random() % IGMP6_UNSOLICITED_IVAL; ma->mca_timer.expires = jiffies + delay; add_timer(&ma->mca_timer); ma->mca_flags |= MAF_TIMER_RUNNING | MAF_LAST_REPORTER; } static void igmp6_leave_group(struct ifmcaddr6 *ma) { int addr_type; addr_type = ipv6_addr_type(&ma->mca_addr); if ((addr_type & IPV6_ADDR_LINKLOCAL)) return; if (ma->mca_flags & MAF_LAST_REPORTER) igmp6_send(&ma->mca_addr, ma->dev, ICMPV6_MGM_REDUCTION); if (ma->mca_flags & MAF_TIMER_RUNNING) del_timer(&ma->mca_timer); } void igmp6_timer_handler(unsigned long data) { struct ifmcaddr6 *ma = (struct ifmcaddr6 *) data; ma->mca_flags |= MAF_LAST_REPORTER; igmp6_send(&ma->mca_addr, ma->dev, ICMPV6_MGM_REPORT); ma->mca_flags &= ~MAF_TIMER_RUNNING; } __initfunc(void igmp6_init(struct net_proto_family *ops)) { struct sock *sk; int err; igmp6_inode.i_mode = S_IFSOCK; igmp6_inode.i_sock = 1; igmp6_inode.i_uid = 0; igmp6_inode.i_gid = 0; igmp6_socket->inode = &igmp6_inode; igmp6_socket->state = SS_UNCONNECTED; igmp6_socket->type = SOCK_RAW; if((err=ops->create(igmp6_socket, IPPROTO_ICMPV6))<0) printk(KERN_DEBUG "Failed to create the IGMP6 control socket.\n"); MOD_DEC_USE_COUNT; sk = igmp6_socket->sk; sk->allocation = GFP_ATOMIC; sk->num = 256; /* Don't receive any data */ sk->net_pinfo.af_inet6.hop_limit = 1; }