Skip to content
Snippets Groups Projects
ipmr.c 45 KiB
Newer Older
  • Learn to ignore specific revisions
  • Stephen Hemminger's avatar
    Stephen Hemminger committed
    		if (get_user(v,(int __user *)optval))
    			return -EFAULT;
    
    		init_net.ipv4.mroute_do_assert = (v) ? 1 : 0;
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    		return 0;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_IP_PIMSM
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    	case MRT_PIM:
    	{
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    		if (get_user(v,(int __user *)optval))
    			return -EFAULT;
    
    		v = (v) ? 1 : 0;
    
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    		rtnl_lock();
    		ret = 0;
    
    		if (v != init_net.ipv4.mroute_do_pim) {
    			init_net.ipv4.mroute_do_pim = v;
    			init_net.ipv4.mroute_do_assert = v;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_IP_PIMSM_V2
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    				ret = inet_add_protocol(&pim_protocol,
    							IPPROTO_PIM);
    			else
    				ret = inet_del_protocol(&pim_protocol,
    							IPPROTO_PIM);
    			if (ret < 0)
    				ret = -EAGAIN;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    		}
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    		rtnl_unlock();
    		return ret;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    	/*
    	 *	Spurious command, or MRT_VERSION which you cannot
    	 *	set.
    	 */
    	default:
    		return -ENOPROTOOPT;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    }
    
    /*
     *	Getsock opt support for the multicast routing system.
     */
    
    int ip_mroute_getsockopt(struct sock *sk, int optname, char __user *optval, int __user *optlen)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int olr;
    	int val;
    
    
    	if (optname != MRT_VERSION &&
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_IP_PIMSM
    	   optname!=MRT_PIM &&
    #endif
    	   optname!=MRT_ASSERT)
    		return -ENOPROTOOPT;
    
    	if (get_user(olr, optlen))
    		return -EFAULT;
    
    	olr = min_t(unsigned int, olr, sizeof(int));
    	if (olr < 0)
    		return -EINVAL;
    
    	if (put_user(olr, optlen))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    
    	if (optname == MRT_VERSION)
    		val = 0x0305;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_IP_PIMSM
    
    	else if (optname == MRT_PIM)
    
    		val = init_net.ipv4.mroute_do_pim;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    	else
    
    		val = init_net.ipv4.mroute_do_assert;
    
    	if (copy_to_user(optval, &val, olr))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    	return 0;
    }
    
    /*
     *	The IP multicast ioctl support routines.
     */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    int ipmr_ioctl(struct sock *sk, int cmd, void __user *arg)
    {
    	struct sioc_sg_req sr;
    	struct sioc_vif_req vr;
    	struct vif_device *vif;
    	struct mfc_cache *c;
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    	switch (cmd) {
    	case SIOCGETVIFCNT:
    
    		if (copy_from_user(&vr, arg, sizeof(vr)))
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    			return -EFAULT;
    
    		if (vr.vifi >= init_net.ipv4.maxvif)
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    			return -EINVAL;
    		read_lock(&mrt_lock);
    
    		vif = &init_net.ipv4.vif_table[vr.vifi];
    		if (VIF_EXISTS(&init_net, vr.vifi)) {
    
    			vr.icount = vif->pkt_in;
    			vr.ocount = vif->pkt_out;
    			vr.ibytes = vif->bytes_in;
    			vr.obytes = vif->bytes_out;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			read_unlock(&mrt_lock);
    
    
    			if (copy_to_user(arg, &vr, sizeof(vr)))
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    				return -EFAULT;
    			return 0;
    		}
    		read_unlock(&mrt_lock);
    		return -EADDRNOTAVAIL;
    	case SIOCGETSGCNT:
    
    		if (copy_from_user(&sr, arg, sizeof(sr)))
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    			return -EFAULT;
    
    		read_lock(&mrt_lock);
    		c = ipmr_cache_find(sr.src.s_addr, sr.grp.s_addr);
    		if (c) {
    			sr.pktcnt = c->mfc_un.res.pkt;
    			sr.bytecnt = c->mfc_un.res.bytes;
    			sr.wrong_if = c->mfc_un.res.wrong_if;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			read_unlock(&mrt_lock);
    
    			if (copy_to_user(arg, &sr, sizeof(sr)))
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    				return -EFAULT;
    			return 0;
    		}
    		read_unlock(&mrt_lock);
    		return -EADDRNOTAVAIL;
    	default:
    		return -ENOIOCTLCMD;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    }
    
    
    static int ipmr_device_event(struct notifier_block *this, unsigned long event, void *ptr)
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct vif_device *v;
    	int ct;
    
    	if (!net_eq(dev_net(dev), &init_net))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (event != NETDEV_UNREGISTER)
    		return NOTIFY_DONE;
    
    	v = &init_net.ipv4.vif_table[0];
    	for (ct = 0; ct < init_net.ipv4.maxvif; ct++, v++) {
    
    		if (v->dev == dev)
    
    			vif_delete(ct, 1);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	return NOTIFY_DONE;
    }
    
    
    
    static struct notifier_block ip_mr_notifier = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.notifier_call = ipmr_device_event,
    };
    
    /*
     * 	Encapsulate a packet by attaching a valid IPIP header to it.
     *	This avoids tunnel drivers and other mess and gives us the speed so
     *	important for multicast video.
     */
    
    Al Viro's avatar
    Al Viro committed
    static void ip_encap(struct sk_buff *skb, __be32 saddr, __be32 daddr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct iphdr *old_iph = ip_hdr(skb);
    
    
    	skb_push(skb, sizeof(struct iphdr));
    
    	skb->transport_header = skb->network_header;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	iph->version	= 	4;
    
    	iph->tos	=	old_iph->tos;
    	iph->ttl	=	old_iph->ttl;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	iph->frag_off	=	0;
    	iph->daddr	=	daddr;
    	iph->saddr	=	saddr;
    	iph->protocol	=	IPPROTO_IPIP;
    	iph->ihl	=	5;
    	iph->tot_len	=	htons(skb->len);
    	ip_select_ident(iph, skb->dst, NULL);
    	ip_send_check(iph);
    
    	memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
    	nf_reset(skb);
    }
    
    static inline int ipmr_forward_finish(struct sk_buff *skb)
    {
    	struct ip_options * opt	= &(IPCB(skb)->opt);
    
    
    	IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (unlikely(opt->optlen))
    		ip_forward_options(skb);
    
    	return dst_output(skb);
    }
    
    /*
     *	Processing handlers for ipmr_forward
     */
    
    static void ipmr_queue_xmit(struct sk_buff *skb, struct mfc_cache *c, int vifi)
    {
    
    	const struct iphdr *iph = ip_hdr(skb);
    
    	struct vif_device *vif = &init_net.ipv4.vif_table[vifi];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct net_device *dev;
    	struct rtable *rt;
    	int    encap = 0;
    
    	if (vif->dev == NULL)
    		goto out_free;
    
    #ifdef CONFIG_IP_PIMSM
    	if (vif->flags & VIFF_REGISTER) {
    		vif->pkt_out++;
    
    		vif->bytes_out += skb->len;
    
    		vif->dev->stats.tx_bytes += skb->len;
    		vif->dev->stats.tx_packets++;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ipmr_cache_report(skb, vifi, IGMPMSG_WHOLEPKT);
    		kfree_skb(skb);
    		return;
    	}
    #endif
    
    	if (vif->flags&VIFF_TUNNEL) {
    		struct flowi fl = { .oif = vif->link,
    				    .nl_u = { .ip4_u =
    					      { .daddr = vif->remote,
    						.saddr = vif->local,
    						.tos = RT_TOS(iph->tos) } },
    				    .proto = IPPROTO_IPIP };
    
    		if (ip_route_output_key(&init_net, &rt, &fl))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out_free;
    		encap = sizeof(struct iphdr);
    	} else {
    		struct flowi fl = { .oif = vif->link,
    				    .nl_u = { .ip4_u =
    					      { .daddr = iph->daddr,
    						.tos = RT_TOS(iph->tos) } },
    				    .proto = IPPROTO_IPIP };
    
    		if (ip_route_output_key(&init_net, &rt, &fl))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out_free;
    	}
    
    	dev = rt->u.dst.dev;
    
    	if (skb->len+encap > dst_mtu(&rt->u.dst) && (ntohs(iph->frag_off) & IP_DF)) {
    		/* Do not fragment multicasts. Alas, IPv4 does not
    		   allow to send ICMP, so that packets will disappear
    		   to blackhole.
    		 */
    
    
    		IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ip_rt_put(rt);
    		goto out_free;
    	}
    
    	encap += LL_RESERVED_SPACE(dev) + rt->u.dst.header_len;
    
    	if (skb_cow(skb, encap)) {
    
    		ip_rt_put(rt);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto out_free;
    	}
    
    	vif->pkt_out++;
    
    	vif->bytes_out += skb->len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	dst_release(skb->dst);
    	skb->dst = &rt->u.dst;
    
    	ip_decrease_ttl(ip_hdr(skb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* FIXME: forward and output firewalls used to be called here.
    	 * What do we do with netfilter? -- RR */
    	if (vif->flags & VIFF_TUNNEL) {
    		ip_encap(skb, vif->local, vif->remote);
    		/* FIXME: extra output firewall step used to be here. --RR */
    
    		vif->dev->stats.tx_packets++;
    		vif->dev->stats.tx_bytes += skb->len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	IPCB(skb)->flags |= IPSKB_FORWARDED;
    
    	/*
    	 * RFC1584 teaches, that DVMRP/PIM router must deliver packets locally
    	 * not only before forwarding, but after forwarding on all output
    	 * interfaces. It is clear, if mrouter runs a multicasting
    	 * program, it should receive packets not depending to what interface
    	 * program is joined.
    	 * If we will not make it, the program will have to join on all
    	 * interfaces. On the other hand, multihoming host (or router, but
    	 * not mrouter) cannot join to more than one interface - it will
    	 * result in receiving multiple packets.
    	 */
    
    	NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, dev,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ipmr_forward_finish);
    	return;
    
    out_free:
    	kfree_skb(skb);
    	return;
    }
    
    static int ipmr_find_vif(struct net_device *dev)
    {
    	int ct;
    
    	for (ct = init_net.ipv4.maxvif-1; ct >= 0; ct--) {
    		if (init_net.ipv4.vif_table[ct].dev == dev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			break;
    	}
    	return ct;
    }
    
    /* "local" means that we should preserve one skb (for local delivery) */
    
    static int ip_mr_forward(struct sk_buff *skb, struct mfc_cache *cache, int local)
    {
    	int psend = -1;
    	int vif, ct;
    
    	vif = cache->mfc_parent;
    	cache->mfc_un.res.pkt++;
    	cache->mfc_un.res.bytes += skb->len;
    
    	/*
    	 * Wrong interface: drop packet and (maybe) send PIM assert.
    	 */
    
    	if (init_net.ipv4.vif_table[vif].dev != skb->dev) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		int true_vifi;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			/* It is our own packet, looped back.
    			   Very complicated situation...
    
    			   The best workaround until routing daemons will be
    			   fixed is not to redistribute packet, if it was
    			   send through wrong interface. It means, that
    			   multicast applications WILL NOT work for
    			   (S,G), which have default multicast route pointing
    			   to wrong oif. In any case, it is not a good
    			   idea to use multicasting applications on router.
    			 */
    			goto dont_forward;
    		}
    
    		cache->mfc_un.res.wrong_if++;
    		true_vifi = ipmr_find_vif(skb->dev);
    
    
    		if (true_vifi >= 0 && init_net.ipv4.mroute_do_assert &&
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    /* pimsm uses asserts, when switching from RPT to SPT,
    		       so that we cannot check that packet arrived on an oif.
    		       It is bad, but otherwise we would need to move pretty
    		       large chunk of pimd to kernel. Ough... --ANK
    		     */
    
    		    (init_net.ipv4.mroute_do_pim ||
    		     cache->mfc_un.res.ttls[true_vifi] < 255) &&
    
    		    time_after(jiffies,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			       cache->mfc_un.res.last_assert + MFC_ASSERT_THRESH)) {
    			cache->mfc_un.res.last_assert = jiffies;
    			ipmr_cache_report(skb, true_vifi, IGMPMSG_WRONGVIF);
    		}
    		goto dont_forward;
    	}
    
    
    	init_net.ipv4.vif_table[vif].pkt_in++;
    	init_net.ipv4.vif_table[vif].bytes_in += skb->len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	Forward the frame
    	 */
    	for (ct = cache->mfc_un.res.maxvif-1; ct >= cache->mfc_un.res.minvif; ct--) {
    
    		if (ip_hdr(skb)->ttl > cache->mfc_un.res.ttls[ct]) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (psend != -1) {
    				struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
    				if (skb2)
    					ipmr_queue_xmit(skb2, cache, psend);
    			}
    
    			psend = ct;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    	}
    	if (psend != -1) {
    		if (local) {
    			struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
    			if (skb2)
    				ipmr_queue_xmit(skb2, cache, psend);
    		} else {
    			ipmr_queue_xmit(skb, cache, psend);
    			return 0;
    		}
    	}
    
    dont_forward:
    	if (!local)
    		kfree_skb(skb);
    	return 0;
    }
    
    
    /*
     *	Multicast packets for forwarding arrive here
     */
    
    int ip_mr_input(struct sk_buff *skb)
    {
    	struct mfc_cache *cache;
    
    	int local = skb->rtable->rt_flags&RTCF_LOCAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Packet is looped back after forward, it should not be
    	   forwarded second time, but still can be delivered locally.
    	 */
    	if (IPCB(skb)->flags&IPSKB_FORWARDED)
    		goto dont_forward;
    
    	if (!local) {
    		    if (IPCB(skb)->opt.router_alert) {
    			    if (ip_call_ra_chain(skb))
    				    return 0;
    
    		    } else if (ip_hdr(skb)->protocol == IPPROTO_IGMP){
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			    /* IGMPv1 (and broken IGMPv2 implementations sort of
    			       Cisco IOS <= 11.2(8)) do not put router alert
    			       option to IGMP packets destined to routable
    			       groups. It is very bad, because it means
    			       that we can forward NO IGMP messages.
    			     */
    			    read_lock(&mrt_lock);
    
    			    if (init_net.ipv4.mroute_sk) {
    
    				    raw_rcv(init_net.ipv4.mroute_sk, skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				    read_unlock(&mrt_lock);
    				    return 0;
    			    }
    			    read_unlock(&mrt_lock);
    		    }
    	}
    
    	read_lock(&mrt_lock);
    
    	cache = ipmr_cache_find(ip_hdr(skb)->saddr, ip_hdr(skb)->daddr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	No usable cache entry
    	 */
    
    	if (cache == NULL) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		int vif;
    
    		if (local) {
    			struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
    			ip_local_deliver(skb);
    			if (skb2 == NULL) {
    				read_unlock(&mrt_lock);
    				return -ENOBUFS;
    			}
    			skb = skb2;
    		}
    
    		vif = ipmr_find_vif(skb->dev);
    		if (vif >= 0) {
    			int err = ipmr_cache_unresolved(vif, skb);
    			read_unlock(&mrt_lock);
    
    			return err;
    		}
    		read_unlock(&mrt_lock);
    		kfree_skb(skb);
    		return -ENODEV;
    	}
    
    	ip_mr_forward(skb, cache, local);
    
    	read_unlock(&mrt_lock);
    
    	if (local)
    		return ip_local_deliver(skb);
    
    	return 0;
    
    dont_forward:
    	if (local)
    		return ip_local_deliver(skb);
    	kfree_skb(skb);
    	return 0;
    }
    
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    #ifdef CONFIG_IP_PIMSM
    static int __pim_rcv(struct sk_buff *skb, unsigned int pimlen)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    	struct net_device *reg_dev = NULL;
    	struct iphdr *encap;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    	encap = (struct iphdr *)(skb_transport_header(skb) + pimlen);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/*
    	   Check that:
    	   a. packet is really destinted to a multicast group
    	   b. packet is not a NULL-REGISTER
    	   c. packet is not truncated
    	 */
    
    	if (!ipv4_is_multicast(encap->daddr) ||
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	    encap->tot_len == 0 ||
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    	    ntohs(encap->tot_len) + pimlen > skb->len)
    		return 1;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	read_lock(&mrt_lock);
    	if (reg_vif_num >= 0)
    
    		reg_dev = init_net.ipv4.vif_table[reg_vif_num].dev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (reg_dev)
    		dev_hold(reg_dev);
    	read_unlock(&mrt_lock);
    
    
    	if (reg_dev == NULL)
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    		return 1;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	skb_pull(skb, (u8*)encap - skb->data);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	skb->dev = reg_dev;
    	skb->protocol = htons(ETH_P_IP);
    	skb->ip_summed = 0;
    	skb->pkt_type = PACKET_HOST;
    	dst_release(skb->dst);
    	skb->dst = NULL;
    
    	reg_dev->stats.rx_bytes += skb->len;
    	reg_dev->stats.rx_packets++;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	nf_reset(skb);
    	netif_rx(skb);
    	dev_put(reg_dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    }
    #endif
    
    #ifdef CONFIG_IP_PIMSM_V1
    /*
     * Handle IGMP messages of PIMv1
     */
    
    int pim_rcv_v1(struct sk_buff * skb)
    {
    	struct igmphdr *pim;
    
    	if (!pskb_may_pull(skb, sizeof(*pim) + sizeof(struct iphdr)))
    		goto drop;
    
    	pim = igmp_hdr(skb);
    
    
    	if (!init_net.ipv4.mroute_do_pim ||
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    	    pim->group != PIM_V1_VERSION || pim->code != PIM_V1_REGISTER)
    		goto drop;
    
    	if (__pim_rcv(skb, sizeof(*pim))) {
    drop:
    		kfree_skb(skb);
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    #endif
    
    #ifdef CONFIG_IP_PIMSM_V2
    static int pim_rcv(struct sk_buff * skb)
    {
    	struct pimreghdr *pim;
    
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    	if (!pskb_may_pull(skb, sizeof(*pim) + sizeof(struct iphdr)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto drop;
    
    
    	pim = (struct pimreghdr *)skb_transport_header(skb);
    
    	if (pim->type != ((PIM_VERSION<<4)|(PIM_REGISTER)) ||
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	    (pim->flags&PIM_NULL_REGISTER) ||
    
    	    (ip_compute_csum((void *)pim, sizeof(*pim)) != 0 &&
    
    	     csum_fold(skb_checksum(skb, 0, skb->len, 0))))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto drop;
    
    
    Ilpo Järvinen's avatar
    Ilpo Järvinen committed
    	if (__pim_rcv(skb, sizeof(*pim))) {
    drop:
    		kfree_skb(skb);
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    #endif
    
    static int
    ipmr_fill_mroute(struct sk_buff *skb, struct mfc_cache *c, struct rtmsg *rtm)
    {
    	int ct;
    	struct rtnexthop *nhp;
    
    	struct net_device *dev = init_net.ipv4.vif_table[c->mfc_parent].dev;
    
    	u8 *b = skb_tail_pointer(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct rtattr *mp_head;
    
    	if (dev)
    		RTA_PUT(skb, RTA_IIF, 4, &dev->ifindex);
    
    
    	mp_head = (struct rtattr *)skb_put(skb, RTA_LENGTH(0));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	for (ct = c->mfc_un.res.minvif; ct < c->mfc_un.res.maxvif; ct++) {
    		if (c->mfc_un.res.ttls[ct] < 255) {
    			if (skb_tailroom(skb) < RTA_ALIGN(RTA_ALIGN(sizeof(*nhp)) + 4))
    				goto rtattr_failure;
    
    			nhp = (struct rtnexthop *)skb_put(skb, RTA_ALIGN(sizeof(*nhp)));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			nhp->rtnh_flags = 0;
    			nhp->rtnh_hops = c->mfc_un.res.ttls[ct];
    
    			nhp->rtnh_ifindex = init_net.ipv4.vif_table[ct].dev->ifindex;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			nhp->rtnh_len = sizeof(*nhp);
    		}
    	}
    	mp_head->rta_type = RTA_MULTIPATH;
    
    	mp_head->rta_len = skb_tail_pointer(skb) - (u8 *)mp_head;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	rtm->rtm_type = RTN_MULTICAST;
    	return 1;
    
    rtattr_failure:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return -EMSGSIZE;
    }
    
    int ipmr_get_route(struct sk_buff *skb, struct rtmsg *rtm, int nowait)
    {
    	int err;
    	struct mfc_cache *cache;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	read_lock(&mrt_lock);
    	cache = ipmr_cache_find(rt->rt_src, rt->rt_dst);
    
    
    	if (cache == NULL) {
    
    		struct sk_buff *skb2;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		struct net_device *dev;
    		int vif;
    
    		if (nowait) {
    			read_unlock(&mrt_lock);
    			return -EAGAIN;
    		}
    
    		dev = skb->dev;
    		if (dev == NULL || (vif = ipmr_find_vif(dev)) < 0) {
    			read_unlock(&mrt_lock);
    			return -ENODEV;
    		}
    
    		skb2 = skb_clone(skb, GFP_ATOMIC);
    		if (!skb2) {
    			read_unlock(&mrt_lock);
    			return -ENOMEM;
    		}
    
    
    		skb_push(skb2, sizeof(struct iphdr));
    		skb_reset_network_header(skb2);
    
    		iph = ip_hdr(skb2);
    		iph->ihl = sizeof(struct iphdr) >> 2;
    		iph->saddr = rt->rt_src;
    		iph->daddr = rt->rt_dst;
    		iph->version = 0;
    
    		err = ipmr_cache_unresolved(vif, skb2);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		read_unlock(&mrt_lock);
    		return err;
    	}
    
    	if (!nowait && (rtm->rtm_flags&RTM_F_NOTIFY))
    		cache->mfc_flags |= MFC_NOTIFY;
    	err = ipmr_fill_mroute(skb, cache, rtm);
    	read_unlock(&mrt_lock);
    	return err;
    }
    
    
    #ifdef CONFIG_PROC_FS
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	The /proc interfaces to multicast routing /proc/ip_mr_cache /proc/ip_mr_vif
     */
    struct ipmr_vif_iter {
    	int ct;
    };
    
    static struct vif_device *ipmr_vif_seq_idx(struct ipmr_vif_iter *iter,
    					   loff_t pos)
    {
    
    	for (iter->ct = 0; iter->ct < init_net.ipv4.maxvif; ++iter->ct) {
    		if (!VIF_EXISTS(&init_net, iter->ct))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			continue;
    
    		if (pos-- == 0)
    
    			return &init_net.ipv4.vif_table[iter->ct];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	return NULL;
    }
    
    static void *ipmr_vif_seq_start(struct seq_file *seq, loff_t *pos)
    
    	__acquires(mrt_lock)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	read_lock(&mrt_lock);
    
    	return *pos ? ipmr_vif_seq_idx(seq->private, *pos - 1)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		: SEQ_START_TOKEN;
    }
    
    static void *ipmr_vif_seq_next(struct seq_file *seq, void *v, loff_t *pos)
    {
    	struct ipmr_vif_iter *iter = seq->private;
    
    	++*pos;
    	if (v == SEQ_START_TOKEN)
    		return ipmr_vif_seq_idx(iter, 0);
    
    	while (++iter->ct < init_net.ipv4.maxvif) {
    		if (!VIF_EXISTS(&init_net, iter->ct))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			continue;
    
    		return &init_net.ipv4.vif_table[iter->ct];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	return NULL;
    }
    
    static void ipmr_vif_seq_stop(struct seq_file *seq, void *v)
    
    	__releases(mrt_lock)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	read_unlock(&mrt_lock);
    }
    
    static int ipmr_vif_seq_show(struct seq_file *seq, void *v)
    {
    	if (v == SEQ_START_TOKEN) {
    
    		seq_puts(seq,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			 "Interface      BytesIn  PktsIn  BytesOut PktsOut Flags Local    Remote\n");
    	} else {
    		const struct vif_device *vif = v;
    		const char *name =  vif->dev ? vif->dev->name : "none";
    
    		seq_printf(seq,
    			   "%2Zd %-10s %8ld %7ld  %8ld %7ld %05X %08X %08X\n",
    
    			   vif - init_net.ipv4.vif_table,
    
    			   name, vif->bytes_in, vif->pkt_in,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			   vif->bytes_out, vif->pkt_out,
    			   vif->flags, vif->local, vif->remote);
    	}
    	return 0;
    }
    
    
    static const struct seq_operations ipmr_vif_seq_ops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.start = ipmr_vif_seq_start,
    	.next  = ipmr_vif_seq_next,
    	.stop  = ipmr_vif_seq_stop,
    	.show  = ipmr_vif_seq_show,
    };
    
    static int ipmr_vif_open(struct inode *inode, struct file *file)
    {
    
    	return seq_open_private(file, &ipmr_vif_seq_ops,
    			sizeof(struct ipmr_vif_iter));
    
    static const struct file_operations ipmr_vif_fops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.owner	 = THIS_MODULE,
    	.open    = ipmr_vif_open,
    	.read    = seq_read,
    	.llseek  = seq_lseek,
    	.release = seq_release_private,
    };
    
    struct ipmr_mfc_iter {
    	struct mfc_cache **cache;
    	int ct;
    };
    
    
    static struct mfc_cache *ipmr_mfc_seq_idx(struct ipmr_mfc_iter *it, loff_t pos)
    {
    	struct mfc_cache *mfc;
    
    
    	it->cache = init_net.ipv4.mfc_cache_array;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	read_lock(&mrt_lock);
    
    	for (it->ct = 0; it->ct < MFC_LINES; it->ct++)
    
    		for (mfc = init_net.ipv4.mfc_cache_array[it->ct];
    		     mfc; mfc = mfc->next)
    
    			if (pos-- == 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return mfc;
    	read_unlock(&mrt_lock);
    
    	it->cache = &mfc_unres_queue;
    	spin_lock_bh(&mfc_unres_lock);
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    	for (mfc = mfc_unres_queue; mfc; mfc = mfc->next)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (pos-- == 0)
    			return mfc;
    	spin_unlock_bh(&mfc_unres_lock);
    
    	it->cache = NULL;
    	return NULL;
    }
    
    
    static void *ipmr_mfc_seq_start(struct seq_file *seq, loff_t *pos)
    {
    	struct ipmr_mfc_iter *it = seq->private;
    	it->cache = NULL;
    	it->ct = 0;
    
    	return *pos ? ipmr_mfc_seq_idx(seq->private, *pos - 1)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		: SEQ_START_TOKEN;
    }
    
    static void *ipmr_mfc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
    {
    	struct mfc_cache *mfc = v;
    	struct ipmr_mfc_iter *it = seq->private;
    
    	++*pos;
    
    	if (v == SEQ_START_TOKEN)
    		return ipmr_mfc_seq_idx(seq->private, 0);
    
    	if (mfc->next)
    		return mfc->next;
    
    
    	if (it->cache == &mfc_unres_queue)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto end_of_list;
    
    
    	BUG_ON(it->cache != init_net.ipv4.mfc_cache_array);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	while (++it->ct < MFC_LINES) {
    
    		mfc = init_net.ipv4.mfc_cache_array[it->ct];
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (mfc)
    			return mfc;
    	}
    
    	/* exhausted cache_array, show unresolved */
    	read_unlock(&mrt_lock);
    	it->cache = &mfc_unres_queue;
    	it->ct = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	spin_lock_bh(&mfc_unres_lock);
    	mfc = mfc_unres_queue;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return mfc;
    
     end_of_list:
    	spin_unlock_bh(&mfc_unres_lock);
    	it->cache = NULL;
    
    	return NULL;
    }
    
    static void ipmr_mfc_seq_stop(struct seq_file *seq, void *v)
    {
    	struct ipmr_mfc_iter *it = seq->private;
    
    	if (it->cache == &mfc_unres_queue)
    		spin_unlock_bh(&mfc_unres_lock);
    
    	else if (it->cache == init_net.ipv4.mfc_cache_array)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		read_unlock(&mrt_lock);
    }
    
    static int ipmr_mfc_seq_show(struct seq_file *seq, void *v)
    {
    	int n;
    
    	if (v == SEQ_START_TOKEN) {
    
    		seq_puts(seq,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 "Group    Origin   Iif     Pkts    Bytes    Wrong Oifs\n");
    	} else {
    		const struct mfc_cache *mfc = v;
    		const struct ipmr_mfc_iter *it = seq->private;
    
    		seq_printf(seq, "%08lX %08lX %-3hd",
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			   (unsigned long) mfc->mfc_mcastgrp,
    			   (unsigned long) mfc->mfc_origin,
    
    			   mfc->mfc_parent);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		if (it->cache != &mfc_unres_queue) {
    
    			seq_printf(seq, " %8lu %8lu %8lu",
    				   mfc->mfc_un.res.pkt,
    				   mfc->mfc_un.res.bytes,
    				   mfc->mfc_un.res.wrong_if);
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    			for (n = mfc->mfc_un.res.minvif;
    			     n < mfc->mfc_un.res.maxvif; n++ ) {
    
    				if (VIF_EXISTS(&init_net, n) &&
    				    mfc->mfc_un.res.ttls[n] < 255)
    					seq_printf(seq,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					   n, mfc->mfc_un.res.ttls[n]);
    			}
    
    		} else {
    			/* unresolved mfc_caches don't contain
    			 * pkt, bytes and wrong_if values
    			 */
    			seq_printf(seq, " %8lu %8lu %8lu", 0ul, 0ul, 0ul);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    		seq_putc(seq, '\n');
    	}
    	return 0;
    }
    
    
    static const struct seq_operations ipmr_mfc_seq_ops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.start = ipmr_mfc_seq_start,
    	.next  = ipmr_mfc_seq_next,
    	.stop  = ipmr_mfc_seq_stop,
    	.show  = ipmr_mfc_seq_show,
    };
    
    static int ipmr_mfc_open(struct inode *inode, struct file *file)
    {
    
    	return seq_open_private(file, &ipmr_mfc_seq_ops,
    			sizeof(struct ipmr_mfc_iter));
    
    static const struct file_operations ipmr_mfc_fops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.owner	 = THIS_MODULE,
    	.open    = ipmr_mfc_open,
    	.read    = seq_read,
    	.llseek  = seq_lseek,
    	.release = seq_release_private,
    };
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #ifdef CONFIG_IP_PIMSM_V2
    static struct net_protocol pim_protocol = {
    	.handler	=	pim_rcv,
    };
    #endif
    
    
    /*
     *	Setup for IP multicast routing
     */
    
    static int __net_init ipmr_net_init(struct net *net)
    {
    	int err = 0;
    
    	net->ipv4.vif_table = kcalloc(MAXVIFS, sizeof(struct vif_device),
    				      GFP_KERNEL);
    	if (!net->ipv4.vif_table) {
    		err = -ENOMEM;
    		goto fail;
    	}
    
    
    	/* Forwarding cache */
    	net->ipv4.mfc_cache_array = kcalloc(MFC_LINES,
    					    sizeof(struct mfc_cache *),
    					    GFP_KERNEL);
    	if (!net->ipv4.mfc_cache_array) {
    		err = -ENOMEM;
    		goto fail_mfc_cache;
    	}
    	return 0;
    
    fail_mfc_cache:
    	kfree(net->ipv4.vif_table);
    
    fail:
    	return err;
    }
    
    static void __net_exit ipmr_net_exit(struct net *net)
    {
    
    	kfree(net->ipv4.mfc_cache_array);
    
    	kfree(net->ipv4.vif_table);
    }
    
    static struct pernet_operations ipmr_net_ops = {
    	.init = ipmr_net_init,
    	.exit = ipmr_net_exit,
    };
    
    int __init ip_mr_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	mrt_cachep = kmem_cache_create("ip_mrt_cache",
    				       sizeof(struct mfc_cache),
    
    Alexey Dobriyan's avatar
    Alexey Dobriyan committed
    				       0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,
    
    	if (!mrt_cachep)
    		return -ENOMEM;
    
    
    	err = register_pernet_subsys(&ipmr_net_ops);
    	if (err)
    		goto reg_pernet_fail;
    
    
    	setup_timer(&ipmr_expire_timer, ipmr_expire_process, 0);
    
    	err = register_netdevice_notifier(&ip_mr_notifier);
    	if (err)
    		goto reg_notif_fail;
    
    #ifdef CONFIG_PROC_FS
    
    	err = -ENOMEM;
    	if (!proc_net_fops_create(&init_net, "ip_mr_vif", 0, &ipmr_vif_fops))
    		goto proc_vif_fail;
    	if (!proc_net_fops_create(&init_net, "ip_mr_cache", 0, &ipmr_mfc_fops))
    		goto proc_cache_fail;
    
    	return 0;
    #ifdef CONFIG_PROC_FS
    proc_cache_fail:
    	proc_net_remove(&init_net, "ip_mr_vif");
    
    proc_vif_fail:
    	unregister_netdevice_notifier(&ip_mr_notifier);
    
    #endif
    
    reg_notif_fail:
    	del_timer(&ipmr_expire_timer);
    
    	unregister_pernet_subsys(&ipmr_net_ops);
    reg_pernet_fail:
    
    	kmem_cache_destroy(mrt_cachep);
    
    	return err;