patch-2.1.68 linux/net/ipv4/ipmr.c

Next file: linux/net/ipv4/packet.c
Previous file: linux/net/ipv4/ipip.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.67/linux/net/ipv4/ipmr.c linux/net/ipv4/ipmr.c
@@ -9,6 +9,7 @@
  *	as published by the Free Software Foundation; either version
  *	2 of the License, or (at your option) any later version.
  *
+ *	Version: $Id: ipmr.c,v 1.28 1997/10/30 00:43:16 davem Exp $
  *
  *	Fixes:
  *	Michael Chastain	:	Incorrect size of copying.
@@ -20,14 +21,8 @@
  *	Alexey Kuznetsov	:	Status, optimisations and more.
  *	Brad Parker		:	Better behaviour on mrouted upcall
  *					overflow.
+ *      Carlos Picoto           :       PIMv1 Support
  *
- *	Status:
- *		Cache manager under test. Forwarding in vague test mode
- *	Todo:
- *		Flow control
- *		Finish Tunnels
- *		Debug cache ttl handling properly
- *		Resolve IFF_ALLMULTI for rest of cards
  */
 
 #include <linux/config.h>
@@ -45,6 +40,8 @@
 #include <linux/in.h>
 #include <linux/inet.h>
 #include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
 #include <linux/proc_fs.h>
 #include <linux/mroute.h>
 #include <linux/init.h>
@@ -54,9 +51,16 @@
 #include <net/sock.h>
 #include <net/icmp.h>
 #include <net/udp.h>
+#include <net/raw.h>
 #include <linux/notifier.h>
+#include <linux/if_arp.h>
+#include <net/ipip.h>
 #include <net/checksum.h>
 
+#if defined(CONFIG_IP_PIMSM_V1) || defined(CONFIG_IP_PIMSM_V2)
+#define CONFIG_IP_PIMSM	1
+#endif
+
 /*
  *	Multicast router control variables
  */
@@ -64,10 +68,133 @@
 static struct vif_device vif_table[MAXVIFS];		/* Devices 		*/
 static unsigned long vifc_map;				/* Active device map	*/
 static int maxvif;
-int mroute_do_pim = 0;					/* Set in PIM assert	*/
+int mroute_do_assert = 0;				/* Set in PIM assert	*/
+int mroute_do_pim = 0;
 static struct mfc_cache *mfc_cache_array[MFC_LINES];	/* Forwarding cache	*/
 int cache_resolve_queue_len = 0;			/* Size of unresolved	*/
 
+static int ip_mr_forward(struct sk_buff *skb, struct mfc_cache *cache, int local);
+static int ipmr_cache_report(struct sk_buff *pkt, vifi_t vifi, int assert);
+static int ipmr_fill_mroute(struct sk_buff *skb, struct mfc_cache *c, struct rtmsg *rtm);
+
+extern struct inet_protocol pim_protocol;
+
+static
+struct device *ipmr_new_tunnel(struct vifctl *v)
+{
+	struct device  *dev = NULL;
+
+	rtnl_lock();
+	dev = dev_get("tunl0");
+
+	if (dev) {
+		int err;
+		struct ifreq ifr;
+		mm_segment_t	oldfs;
+		struct ip_tunnel_parm p;
+		struct in_device  *in_dev;
+
+		memset(&p, 0, sizeof(p));
+		p.iph.daddr = v->vifc_rmt_addr.s_addr;
+		p.iph.saddr = v->vifc_lcl_addr.s_addr;
+		p.iph.version = 4;
+		p.iph.ihl = 5;
+		p.iph.protocol = IPPROTO_IPIP;
+		sprintf(p.name, "dvmrp%d", v->vifc_vifi);
+		ifr.ifr_ifru.ifru_data = (void*)&p;
+
+		oldfs = get_fs(); set_fs(KERNEL_DS);
+		err = dev->do_ioctl(dev, &ifr, SIOCADDTUNNEL);
+		set_fs(oldfs);
+
+		if (err == 0 && (dev = dev_get(p.name)) != NULL) {
+			dev->flags |= IFF_MULTICAST;
+
+			in_dev = dev->ip_ptr;
+			if (in_dev == NULL && (in_dev = inetdev_init(dev)) == NULL)
+				goto failure;
+
+			if (dev_open(dev))
+				goto failure;
+		}
+	}
+	rtnl_unlock();
+	return dev;
+
+failure:
+	unregister_netdevice(dev);
+	rtnl_unlock();
+	return NULL;
+}
+
+#ifdef CONFIG_IP_PIMSM
+
+static int reg_vif_num = -1;
+static struct device * reg_dev;
+
+static int reg_vif_xmit(struct sk_buff *skb, struct device *dev)
+{
+	ipmr_cache_report(skb, reg_vif_num, IGMPMSG_WHOLEPKT);
+	kfree_skb(skb, FREE_WRITE);
+	return 0;
+}
+
+static struct net_device_stats *reg_vif_get_stats(struct device *dev)
+{
+	return (struct net_device_stats*)dev->priv;
+}
+
+static
+struct device *ipmr_reg_vif(struct vifctl *v)
+{
+	struct device  *dev;
+	struct in_device *in_dev;
+	int size;
+
+	size = sizeof(*dev) + IFNAMSIZ + sizeof(struct net_device_stats);
+	dev = kmalloc(size, GFP_KERNEL);
+	if (!dev)
+		return NULL;
+
+	memset(dev, 0, size);
+
+	dev->priv = dev + 1;
+	dev->name = dev->priv + sizeof(struct net_device_stats);
+
+	strcpy(dev->name, "pimreg");
+
+	dev->type		= ARPHRD_PIMREG;
+	dev->mtu		= 1500 - sizeof(struct iphdr) - 8;
+	dev->flags		= IFF_NOARP;
+	dev->hard_start_xmit	= reg_vif_xmit;
+	dev->get_stats		= reg_vif_get_stats;
+
+	rtnl_lock();
+
+	if (register_netdevice(dev)) {
+		rtnl_unlock();
+		kfree(dev);
+		return NULL;
+	}
+
+	if ((in_dev = inetdev_init(dev)) == NULL)
+		goto failure;
+
+	if (dev_open(dev))
+		goto failure;
+
+	rtnl_unlock();
+	reg_dev = dev;
+	return dev;
+
+failure:
+	unregister_netdevice(dev);
+	rtnl_unlock();
+	kfree(dev);
+	return NULL;
+}
+#endif
+
 /*
  *	Delete a VIF entry
  */
@@ -75,28 +202,36 @@
 static int vif_delete(int vifi)
 {
 	struct vif_device *v;
+	struct device *dev;
+	struct in_device *in_dev;
 	
 	if (vifi < 0 || vifi >= maxvif || !(vifc_map&(1<<vifi)))
 		return -EADDRNOTAVAIL;
 
 	v = &vif_table[vifi];
 
-	start_bh_atomic();
-
-	if (!(v->flags&VIFF_TUNNEL)) {
-		v->u.dev->flags &= ~IFF_ALLMULTI;
-		dev_mc_upload(v->u.dev);
-		ip_rt_multicast_event(v->u.dev);
-		v->u.dev = NULL;
-	} else {
-		ip_rt_put(v->u.rt);
-		v->u.rt = NULL;
+	dev = v->dev;
+	v->dev = NULL;
+	vifc_map &= ~(1<<vifi);
+
+	if ((in_dev = dev->ip_ptr) != NULL)
+		in_dev->flags &= ~IFF_IP_MFORWARD;
+
+	dev_set_allmulti(dev, -1);
+	ip_rt_multicast_event(in_dev);
+
+	if (v->flags&(VIFF_TUNNEL|VIFF_REGISTER)) {
+#ifdef CONFIG_IP_PIMSM
+		if (vifi == reg_vif_num) {
+			reg_vif_num = -1;
+			reg_dev = NULL;
+		}
+#endif
+		unregister_netdevice(dev);
+		if (v->flags&VIFF_REGISTER)
+			kfree(dev);
 	}
 
-	vifc_map&=~(1<<vifi);
-
-	end_bh_atomic();
-
 	if (vifi+1 == maxvif) {
 		int tmp;
 		for (tmp=vifi-1; tmp>=0; tmp--) {
@@ -108,21 +243,27 @@
 	return 0;
 }
 
-static void ipmr_set_bounds(struct mfc_cache *cache)
+static void ipmr_update_threshoulds(struct mfc_cache *cache, unsigned char *ttls)
 {
 	int vifi;
+
+	start_bh_atomic();
+
+	cache->mfc_minvif = MAXVIFS;
+	cache->mfc_maxvif = 0;
+	memset(cache->mfc_ttls, 255, MAXVIFS);
+
 	for (vifi=0; vifi<maxvif; vifi++) {
-		if (vifc_map&(1<<vifi) && cache->mfc_ttls[vifi]) {
-			cache->mfc_minvif = vifi;
-			cache->mfc_maxvif = vifi+1;
+		if (vifc_map&(1<<vifi) && ttls[vifi] && ttls[vifi] < 255) {
+			cache->mfc_ttls[vifi] = ttls[vifi];
+			if (cache->mfc_minvif > vifi)
+				cache->mfc_minvif = vifi;
+			if (cache->mfc_maxvif <= vifi)
+				cache->mfc_maxvif = vifi + 1;
 			vifi++;
-			break;
 		}
 	}
-	for ( ; vifi<maxvif; vifi++) {
-		if (vifc_map&(1<<vifi) && cache->mfc_ttls[vifi])
-			cache->mfc_maxvif = vifi+1;
-	}
+	end_bh_atomic();
 }
 
 /*
@@ -148,7 +289,7 @@
 	/*
 	 *	Unlink the buffer
 	 */
-	
+
 	while(*cp!=NULL)
 	{
 		if(*cp==cache)
@@ -158,7 +299,7 @@
 		}
 		cp=&((*cp)->next);
 	}
-	
+
 	/*
 	 *	Free the buffer. If it is a pending resolution
 	 *	clean up the other resources.
@@ -167,8 +308,19 @@
 	if(cache->mfc_flags&MFC_QUEUED)
 	{
 		cache_resolve_queue_len--;
-		while((skb=skb_dequeue(&cache->mfc_unresolved)))
+		while((skb=skb_dequeue(&cache->mfc_unresolved))) {
+#ifdef CONFIG_RTNETLINK
+			if (skb->nh.iph->version == 0) {
+				struct nlmsghdr *nlh = (struct nlmsghdr *)skb_pull(skb, sizeof(struct iphdr));
+				nlh->nlmsg_type = NLMSG_ERROR;
+				nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+				skb_trim(skb, nlh->nlmsg_len);
+				((struct nlmsgerr*)NLMSG_DATA(nlh))->error = -ETIMEDOUT;
+				netlink_unicast(rtnl, skb, NETLINK_CB(skb).pid, MSG_DONTWAIT);
+			} else
+#endif
 			kfree_skb(skb, FREE_WRITE);
+		}
 	}
 	kfree_s(cache,sizeof(cache));
 }
@@ -222,14 +374,12 @@
 	struct mfc_cache *c=(struct mfc_cache *)kmalloc(sizeof(struct mfc_cache), priority);
 	if(c==NULL)
 		return NULL;
-	c->mfc_queuelen=0;
+	memset(c, 0, sizeof(*c));
 	skb_queue_head_init(&c->mfc_unresolved);
 	init_timer(&c->mfc_timer);
 	c->mfc_timer.data=(long)c;
 	c->mfc_timer.function=ipmr_cache_timer;
-	c->mfc_last_assert=0;
 	c->mfc_minvif = MAXVIFS;
-	c->mfc_maxvif = 0;
 	return c;
 }
  
@@ -259,8 +409,26 @@
 	/*
 	 *	Play the pending entries through our router
 	 */
-	while((skb=skb_dequeue(&cache->mfc_unresolved)))
-		ip_mr_input(skb);
+	while((skb=skb_dequeue(&cache->mfc_unresolved))) {
+#ifdef CONFIG_RTNETLINK
+		if (skb->nh.iph->version == 0) {
+			int err;
+			struct nlmsghdr *nlh = (struct nlmsghdr *)skb_pull(skb, sizeof(struct iphdr));
+
+			if (ipmr_fill_mroute(skb, cache, NLMSG_DATA(nlh)) > 0) {
+				nlh->nlmsg_len = skb->tail - (u8*)nlh;
+			} else {
+				nlh->nlmsg_type = NLMSG_ERROR;
+				nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+				skb_trim(skb, nlh->nlmsg_len);
+				((struct nlmsgerr*)NLMSG_DATA(nlh))->error = -EMSGSIZE;
+			}
+			err = netlink_unicast(rtnl, skb, NETLINK_CB(skb).pid, MSG_DONTWAIT);
+			if (err < 0) printk(KERN_DEBUG "Err=%d", err);
+		} else
+#endif
+			ip_mr_forward(skb, cache, 0);
+	}
 }
 
 /*
@@ -270,15 +438,40 @@
  
 static int ipmr_cache_report(struct sk_buff *pkt, vifi_t vifi, int assert)
 {
-	struct sk_buff *skb = alloc_skb(128, GFP_ATOMIC);
+	struct sk_buff *skb;
 	int ihl = pkt->nh.iph->ihl<<2;
 	struct igmphdr *igmp;
 	struct igmpmsg *msg;
 	int ret;
 
+#ifdef CONFIG_IP_PIMSM
+	if (assert == IGMPMSG_WHOLEPKT)
+		skb = skb_realloc_headroom(pkt, sizeof(struct iphdr));
+	else
+#endif
+		skb = alloc_skb(128, GFP_ATOMIC);
+
 	if(!skb)
-		return -ENOMEM;
-		
+		return -ENOBUFS;
+
+#ifdef CONFIG_IP_PIMSM
+	if (assert == IGMPMSG_WHOLEPKT) {
+		/* Ugly, but we have no choice with this interface.
+		   Duplicate old header, fix ihl, length etc.
+		   And all this only to mangle msg->im_msgtype and
+		   to set msg->im_mbz to "mbz" :-)
+		 */
+		msg = (struct igmpmsg*)skb_push(skb, sizeof(struct iphdr));
+		skb->nh.raw = skb->h.raw = (u8*)msg;
+		memcpy(msg, pkt->nh.raw, sizeof(struct iphdr));
+		msg->im_msgtype = IGMPMSG_WHOLEPKT;
+		msg->im_mbz = 0;
+ 		msg->im_vif = reg_vif_num;
+		skb->nh.iph->ihl = sizeof(struct iphdr) >> 2;
+		skb->nh.iph->tot_len = htons(ntohs(pkt->nh.iph->tot_len) + sizeof(struct iphdr));
+	} else {
+#endif
+
 	/*
 	 *	Copy the IP header
 	 */
@@ -287,33 +480,30 @@
 	memcpy(skb->data,pkt->data,ihl);
 	skb->nh.iph->protocol = 0;			/* Flag to the kernel this is a route add */
 	msg = (struct igmpmsg*)skb->nh.iph;
-	if (assert)
-		msg->im_vif = vifi;
-	
+	msg->im_vif = vifi;
+	skb->dst = dst_clone(pkt->dst);
+
 	/*
 	 *	Add our header
 	 */
-	
+
 	igmp=(struct igmphdr *)skb_put(skb,sizeof(struct igmphdr));
 	igmp->type	=
-	msg->im_msgtype = assert ? IGMPMSG_WRONGVIF : IGMPMSG_NOCACHE;
+	msg->im_msgtype = assert;
 	igmp->code 	=	0;
 	skb->nh.iph->tot_len=htons(skb->len);			/* Fix the length */
 	skb->h.raw = skb->nh.raw;
+#ifdef CONFIG_IP_PIMSM
+        }
+#endif
 	
 	/*
 	 *	Deliver to mrouted
 	 */
-	if((ret=sock_queue_rcv_skb(mroute_socket,skb))<0)
-	{
-		static unsigned long last_warn;
-		if(jiffies-last_warn>10*HZ)
-		{
-			last_warn=jiffies;
-			printk("mroute: pending queue full, dropping entries.\n");
-		}
+	if ((ret=sock_queue_rcv_skb(mroute_socket,skb))<0) {
+		if (net_ratelimit())
+			printk(KERN_WARNING "mroute: pending queue full, dropping entries.\n");
 		kfree_skb(skb, FREE_READ);
-		return ret;
 	}
 
 	return ret;
@@ -323,7 +513,7 @@
  *	Queue a packet for resolution
  */
  
-static void ipmr_cache_unresolved(struct mfc_cache *cache, vifi_t vifi, struct sk_buff *skb)
+static int ipmr_cache_unresolved(struct mfc_cache *cache, vifi_t vifi, struct sk_buff *skb)
 {
 	if(cache==NULL)
 	{	
@@ -333,12 +523,12 @@
 		if(cache_resolve_queue_len>=10 || (cache=ipmr_cache_alloc(GFP_ATOMIC))==NULL)
 		{
 			kfree_skb(skb, FREE_WRITE);
-			return;
+			return -ENOBUFS;
 		}
 		/*
 		 *	Fill in the new cache entry
 		 */
-		cache->mfc_parent=vifi;
+		cache->mfc_parent=ALL_VIFS;
 		cache->mfc_origin=skb->nh.iph->saddr;
 		cache->mfc_mcastgrp=skb->nh.iph->daddr;
 		cache->mfc_flags=MFC_QUEUED;
@@ -358,9 +548,16 @@
 		if(mroute_socket)
 		{
 			/* If the report failed throw the cache entry 
-			   out - Brad Parker */
-			if(ipmr_cache_report(skb, vifi, 0)<0)
+			   out - Brad Parker
+
+			   OK, OK, Brad. Only do not forget to free skb
+			   and return :-) --ANK
+			 */
+			if (ipmr_cache_report(skb, vifi, IGMPMSG_NOCACHE)<0) {
 				ipmr_cache_delete(cache);
+				kfree_skb(skb, FREE_WRITE);
+				return -ENOBUFS;
+			}
 		}
 	}
 	/*
@@ -369,10 +566,11 @@
 	if(cache->mfc_queuelen>3)
 	{
 		kfree_skb(skb, FREE_WRITE);
-		return;
+		return -ENOBUFS;
 	}
 	cache->mfc_queuelen++;
 	skb_queue_tail(&cache->mfc_unresolved,skb);
+	return 0;
 }
 
 /*
@@ -416,8 +614,7 @@
 
 		cache->mfc_flags|=MFC_RESOLVED;
 		cache->mfc_parent=mfc->mfcc_parent;
-		memcpy(cache->mfc_ttls, mfc->mfcc_ttls,sizeof(cache->mfc_ttls));
-		ipmr_set_bounds(cache);
+		ipmr_update_threshoulds(cache, mfc->mfcc_ttls);
 		 
 		/*
 		 *	Check to see if we resolved a queued list. If so we
@@ -445,13 +642,21 @@
 	cache->mfc_origin=mfc->mfcc_origin.s_addr;
 	cache->mfc_mcastgrp=mfc->mfcc_mcastgrp.s_addr;
 	cache->mfc_parent=mfc->mfcc_parent;
-	memcpy(cache->mfc_ttls, mfc->mfcc_ttls,sizeof(cache->mfc_ttls));
-	ipmr_set_bounds(cache);
+	ipmr_update_threshoulds(cache, mfc->mfcc_ttls);
 	ipmr_cache_insert(cache);
 	end_bh_atomic();
 	return 0;
 }
- 
+
+static void mrtsock_destruct(struct sock *sk)
+{
+	if (sk == mroute_socket) {
+		ipv4_config.multicast_route = 0;
+		mroute_socket=NULL;
+		mroute_close(sk);
+	}
+}
+
 /*
  *	Socket options and virtual interface manipulation. The whole
  *	virtual interface system is a complete heap, but unfortunately
@@ -461,7 +666,6 @@
  
 int ip_mroute_setsockopt(struct sock *sk,int optname,char *optval,int optlen)
 {
-	int err;
 	struct vifctl vif;
 	struct mfcctl mfc;
 	
@@ -480,9 +684,8 @@
 				return -ENOPROTOOPT;
 			{
 				int opt;
-				err = get_user(opt,(int *)optval);
-				if (err) 
-					return err; 
+				if (get_user(opt,(int *)optval))
+					return -EFAULT;
 				if (opt != 1)
 					return -ENOPROTOOPT;
 			}
@@ -490,78 +693,101 @@
 				return -EADDRINUSE;
 			mroute_socket=sk;
 			ipv4_config.multicast_route = 1;
-			/* Initialise state */
-			return 0;
+			if (ip_ra_control(sk, 1, mrtsock_destruct) == 0)
+				return 0;
+			mrtsock_destruct(sk);
+			return -EADDRINUSE;
 		case MRT_DONE:
-			ipv4_config.multicast_route = 0;
-			mroute_close(sk);
-			mroute_socket=NULL;
+			mrtsock_destruct(sk);
 			return 0;
 		case MRT_ADD_VIF:
 		case MRT_DEL_VIF:
 			if(optlen!=sizeof(vif))
 				return -EINVAL;
-			err = copy_from_user(&vif,optval,sizeof(vif));
-			if (err)
+			if (copy_from_user(&vif,optval,sizeof(vif)))
 				return -EFAULT; 
-			if(vif.vifc_vifi > MAXVIFS)
+			if(vif.vifc_vifi >= MAXVIFS)
 				return -ENFILE;
 			if(optname==MRT_ADD_VIF)
 			{
 				struct vif_device *v=&vif_table[vif.vifc_vifi];
 				struct device *dev;
-				/* Empty vif ? */
-				if(vifc_map&(1<<vif.vifc_vifi))
+				struct in_device *in_dev;
+
+				/* Is vif busy ? */
+				if (vifc_map&(1<<vif.vifc_vifi))
 					return -EADDRINUSE;
-				/* Find the interface */
-				dev=ip_dev_find(vif.vifc_lcl_addr.s_addr, NULL);
-				if(!dev)
-					return -EADDRNOTAVAIL;
-				/* Must be tunnelled or multicastable */
-				if(vif.vifc_flags&VIFF_TUNNEL)
-				{
-					if(vif.vifc_flags&VIFF_SRCRT)
-						return -EOPNOTSUPP;
-				}
-				else
-				{
-					if(dev->flags&IFF_MULTICAST)
-					{
-						/* Most ethernet cards don't know
-						   how to do this yet.. */
-						dev->flags|=IFF_ALLMULTI;
-						dev_mc_upload(dev);
-						ip_rt_multicast_event(dev);
-					}
-					else
-					{
-						/* We are stuck.. */
-						return -EOPNOTSUPP;
+
+				switch (vif.vifc_flags) {
+#ifdef CONFIG_IP_PIMSM
+				case VIFF_REGISTER:
+
+				/*
+				 * Special Purpose VIF in PIM
+				 * All the packets will be sent to the daemon
+				 */
+					if (reg_vif_num >= 0)
+						return -EADDRINUSE;
+					reg_vif_num = vif.vifc_vifi;
+					dev = ipmr_reg_vif(&vif);
+					if (!dev) {
+						reg_vif_num = -1;
+						return -ENOBUFS;
 					}
+					break;
+#endif
+				case VIFF_TUNNEL:	
+					dev = ipmr_new_tunnel(&vif);
+					if (!dev)
+						return -ENOBUFS;
+					break;
+				case 0:	
+					dev=ip_dev_find(vif.vifc_lcl_addr.s_addr);
+					if (!dev)
+						return -EADDRNOTAVAIL;
+					break;
+				default:
+					printk(KERN_DEBUG "ipmr_add_vif: flags %02x\n", vif.vifc_flags);
+					return -EINVAL;
 				}
+
+				if ((in_dev = dev->ip_ptr) == NULL)
+					return -EADDRNOTAVAIL;
+				if (in_dev->flags & IFF_IP_MFORWARD)
+					return -EADDRINUSE;
+				in_dev->flags |= IFF_IP_MFORWARD;
+				dev_set_allmulti(dev, +1);
+				ip_rt_multicast_event(in_dev);
+
 				/*
 				 *	Fill in the VIF structures
 				 */
-				cli();
+				start_bh_atomic();
 				v->rate_limit=vif.vifc_rate_limit;
 				v->local=vif.vifc_lcl_addr.s_addr;
 				v->remote=vif.vifc_rmt_addr.s_addr;
 				v->flags=vif.vifc_flags;
 				v->threshold=vif.vifc_threshold;
-				v->u.dev=NULL;
-				if (!(vif.vifc_flags&VIFF_TUNNEL))
-					v->u.dev=dev;
+				v->dev=dev;
 				v->bytes_in = 0;
 				v->bytes_out = 0;
 				v->pkt_in = 0;
 				v->pkt_out = 0;
+				v->link = dev->ifindex;
+				if (vif.vifc_flags&(VIFF_TUNNEL|VIFF_REGISTER))
+					v->link = dev->iflink;
 				vifc_map|=(1<<vif.vifc_vifi);
 				if (vif.vifc_vifi+1 > maxvif)
 					maxvif = vif.vifc_vifi+1;
-				sti();
+				end_bh_atomic();
 				return 0;
-			} else
-				return vif_delete(vif.vifc_vifi);
+			} else {
+				int ret;
+				rtnl_lock();
+				ret = vif_delete(vif.vifc_vifi);
+				rtnl_unlock();
+				return ret;
+			}
 
 		/*
 		 *	Manipulate the forwarding caches. These live
@@ -571,8 +797,9 @@
 		case MRT_DEL_MFC:
 			if(optlen!=sizeof(mfc))
 				return -EINVAL;
-			err = copy_from_user(&mfc,optval, sizeof(mfc));
-			return err ? -EFAULT : ipmr_mfc_modify(optname, &mfc);
+			if (copy_from_user(&mfc,optval, sizeof(mfc)))
+				return -EFAULT;
+			return ipmr_mfc_modify(optname, &mfc);
 		/*
 		 *	Control PIM assert.
 		 */
@@ -581,9 +808,29 @@
 			int v;
 			if(get_user(v,(int *)optval))
 				return -EFAULT;
-			mroute_do_pim=(v)?1:0;
+			mroute_do_assert=(v)?1:0;
 			return 0;
 		}
+#ifdef CONFIG_IP_PIMSM
+		case MRT_PIM:
+		{
+			int v;
+			if(get_user(v,(int *)optval))
+				return -EFAULT;
+			v = (v)?1:0;
+			if (v != mroute_do_pim) {
+				mroute_do_pim = v;
+				mroute_do_assert = v;
+#ifdef CONFIG_IP_PIMSM_V2
+				if (mroute_do_pim)
+					inet_add_protocol(&pim_protocol);
+				else
+					inet_del_protocol(&pim_protocol);
+#endif
+			}
+			return 0;
+		}
+#endif
 		/*
 		 *	Spurious command, or MRT_VERSION which you cannot
 		 *	set.
@@ -604,7 +851,11 @@
 
 	if(sk!=mroute_socket)
 		return -EACCES;
-	if(optname!=MRT_VERSION && optname!=MRT_ASSERT)
+	if(optname!=MRT_VERSION && 
+#ifdef CONFIG_IP_PIMSM
+	   optname!=MRT_PIM &&
+#endif
+	   optname!=MRT_ASSERT)
 		return -ENOPROTOOPT;
 	
 	if(get_user(olr, optlen))
@@ -615,8 +866,12 @@
 		return -EFAULT;
 	if(optname==MRT_VERSION)
 		val=0x0305;
-	else
+#ifdef CONFIG_IP_PIMSM
+	else if(optname==MRT_PIM)
 		val=mroute_do_pim;
+#endif
+	else
+		val=mroute_do_assert;
 	if(copy_to_user(optval,&val,olr))
 		return -EFAULT;
 	return 0;
@@ -628,7 +883,6 @@
  
 int ipmr_ioctl(struct sock *sk, int cmd, unsigned long arg)
 {
-	int err;
 	struct sioc_sg_req sr;
 	struct sioc_vif_req vr;
 	struct vif_device *vif;
@@ -637,8 +891,7 @@
 	switch(cmd)
 	{
 		case SIOCGETVIFCNT:
-			err = copy_from_user(&vr,(void *)arg,sizeof(vr));
-			if (err)
+			if (copy_from_user(&vr,(void *)arg,sizeof(vr)))
 				return -EFAULT; 
 			if(vr.vifi>=maxvif)
 				return -EINVAL;
@@ -649,16 +902,13 @@
 				vr.ocount=vif->pkt_out;
 				vr.ibytes=vif->bytes_in;
 				vr.obytes=vif->bytes_out;
-				err = copy_to_user((void *)arg,&vr,sizeof(vr));
-				if (err)
-					err = -EFAULT;
-				return err;
+				if (copy_to_user((void *)arg,&vr,sizeof(vr)))
+					return -EFAULT;
 				return 0;
 			}
 			return -EADDRNOTAVAIL;
 		case SIOCGETSGCNT:
-			err = copy_from_user(&sr,(void *)arg,sizeof(sr));
-			if (err)
+			if (copy_from_user(&sr,(void *)arg,sizeof(sr)))
 				return -EFAULT; 
 			for (c = mfc_cache_array[MFC_HASH(sr.grp.s_addr, sr.src.s_addr)];
 			     c; c = c->next) {
@@ -667,10 +917,8 @@
 					sr.pktcnt = c->mfc_pkt;
 					sr.bytecnt = c->mfc_bytes;
 					sr.wrong_if = c->mfc_wrong_if;
-					err = copy_to_user((void *)arg,&sr,sizeof(sr));
-					if (err)
-						err = -EFAULT;
-					return err;
+					if (copy_to_user((void *)arg,&sr,sizeof(sr)))
+						return -EFAULT;
 					return 0;
 				}
 			}
@@ -691,9 +939,10 @@
 	/*
 	 *	Shut down all active vif entries
 	 */
-	 
+	rtnl_lock();
 	for(i=0; i<maxvif; i++)
 		vif_delete(i);
+	rtnl_unlock();
 
 	/*
 	 *	Wipe the cache
@@ -711,12 +960,11 @@
 {
 	struct vif_device *v;
 	int ct;
-	if(event!=NETDEV_DOWN)
+	if (event != NETDEV_UNREGISTER)
 		return NOTIFY_DONE;
 	v=&vif_table[0];
-	for(ct=0;ct<maxvif;ct++)
-	{
-		if(vifc_map&(1<<ct) && !(v->flags&VIFF_TUNNEL) && v->u.dev==ptr)
+	for(ct=0;ct<maxvif;ct++) {
+		if (vifc_map&(1<<ct) && v->dev==ptr)
 			vif_delete(ct);
 		v++;
 	}
@@ -769,26 +1017,24 @@
 	struct rtable *rt;
 	int    encap = 0;
 	struct sk_buff *skb2;
-	int    err;
-		
+
+#ifdef CONFIG_IP_PIMSM
+	if (vif->flags & VIFF_REGISTER) {
+		vif->pkt_out++;
+		vif->bytes_out+=skb->len;
+		((struct net_device_stats*)vif->dev->priv)->tx_bytes += skb->len;
+		((struct net_device_stats*)vif->dev->priv)->tx_packets++;
+		ipmr_cache_report(skb, vifi, IGMPMSG_WHOLEPKT);
+		return;
+	}
+#endif
+
 	if (vif->flags&VIFF_TUNNEL) {
-		rt = vif->u.rt;
-		if (!rt || rt->u.dst.obsolete) {
-			ip_rt_put(rt);
-			vif->u.rt = NULL;
-			err = ip_route_output(&rt, vif->remote, vif->local, RT_TOS(iph->tos), NULL);
-			if (err)
-				return;
-			vif->u.rt = rt;
-		}
-		dst_clone(&rt->u.dst);
+		if (ip_route_output(&rt, vif->remote, vif->local, RT_TOS(iph->tos), vif->link))
+			return;
 		encap = sizeof(struct iphdr);
 	} else {
-		dev = vif->u.dev;
-		if (dev == NULL)
-			return;
-		err = ip_route_output(&rt, iph->daddr, 0, RT_TOS(iph->tos), dev);
-		if (err)
+		if (ip_route_output(&rt, iph->daddr, 0, RT_TOS(iph->tos), vif->link))
 			return;
 	}
 
@@ -807,10 +1053,14 @@
 		return;
 	}
 
-	if (skb_headroom(skb) < encap || (encap && !last))
+	if (skb_headroom(skb) < encap || skb_cloned(skb) || !last)
 		skb2 = skb_realloc_headroom(skb, (encap + 15)&~15);
-	else
+	else if (atomic_read(&skb->users) != 1)
 		skb2 = skb_clone(skb, GFP_ATOMIC);
+	else {
+		atomic_inc(&skb->users);
+		skb2 = skb;
+	}
 
 	if (skb2 == NULL) {
 		ip_rt_put(rt);
@@ -826,34 +1076,45 @@
 	iph = skb2->nh.iph;
 	ip_decrease_ttl(iph);
 
-	if (vif->flags & VIFF_TUNNEL)
+	if (vif->flags & VIFF_TUNNEL) {
 		ip_encap(skb2, vif->local, vif->remote);
+		((struct ip_tunnel *)vif->dev->priv)->stat.tx_packets++;
+		((struct ip_tunnel *)vif->dev->priv)->stat.tx_bytes+=skb2->len;
+	}
+
+	IPCB(skb2)->flags |= IPSKB_FORWARDED;
 
-	ip_send(skb2);
+	/*
+	 * 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.
+	 */
+	ip_ll_header(skb2);
+	skb2->dst->output(skb2);
 }
 
-/*
- *	Multicast packets for forwarding arrive here
- */
+int ipmr_find_vif(struct device *dev)
+{
+	int ct;
+	for (ct=0; ct<maxvif; ct++) {
+		if (vifc_map&(1<<ct) && vif_table[ct].dev == dev)
+			return ct;
+	}
+	return ALL_VIFS;
+}
 
-int ip_mr_input(struct sk_buff *skb)
+/* "local" means that we should preserve one skb (for local delivery) */
+
+int ip_mr_forward(struct sk_buff *skb, struct mfc_cache *cache, int local)
 {
-	struct mfc_cache *cache;
 	int psend = -1;
 	int vif, ct;
-	int local = 0;
-	int tunneled = IPCB(skb)->flags&IPSKB_TUNNELED;
-
-	cache = ipmr_cache_find(skb->nh.iph->saddr, skb->nh.iph->daddr);
-	
-	/*
-	 *	No usable cache entry
-	 */
-	 
-	if (cache==NULL || (cache->mfc_flags&MFC_QUEUED)) {
-		ipmr_cache_unresolved(cache, ALL_VIFS, skb);
-		return -EAGAIN;
-	}
 
 	vif = cache->mfc_parent;
 	cache->mfc_pkt++;
@@ -862,75 +1123,290 @@
 	/*
 	 * Wrong interface: drop packet and (maybe) send PIM assert.
 	 */
-	if (vif >= maxvif || !(vifc_map&(1<<vif)) ||
-	    (tunneled && IPCB(skb)->vif != vif) ||
-	    (!tunneled && (vif_table[vif].flags&VIFF_TUNNEL ||
-	     vif_table[vif].u.dev != skb->dev))) {
+	if (vif_table[vif].dev != skb->dev) {
+		int true_vifi;
+
+		if (((struct rtable*)skb->dst)->key.iif == 0) {
+			/* 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_wrong_if++;
-		if (vif < MAXVIFS && mroute_do_pim &&
-		    !(vif_table[vif].flags&VIFF_TUNNEL) &&
-		    skb->dev->flags&IFF_BROADCAST &&
+		true_vifi = ipmr_find_vif(skb->dev);
+
+		if (true_vifi < MAXVIFS && mroute_do_assert &&
+		    /* 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
+		     */
+		    (mroute_do_pim || cache->mfc_ttls[true_vifi] < 255) &&
 		    jiffies - cache->mfc_last_assert > MFC_ASSERT_THRESH) {
 			cache->mfc_last_assert = jiffies;
-			/*
-			 *	It is wrong! Routing daemon can
-			 *	determine vif itself, but it cannot
-			 *	determine REAL device.
-			 *	BSD bug. Fix it later, PIM does not
-			 *	work in any case 8) _ANK_
-			 */
-			ipmr_cache_report(skb, vif, 1);
+			ipmr_cache_report(skb, true_vifi, IGMPMSG_WRONGVIF);
 		}
-		kfree_skb(skb, FREE_WRITE);
-		return -EINVAL;
+		goto dont_forward;
 	}
 
 	vif_table[vif].pkt_in++;
 	vif_table[vif].bytes_in+=skb->len;
 
-	if (IPCB(skb)->opt.router_alert ||
-	    ((struct rtable*)skb->dst)->rt_flags&RTF_LOCAL ||
-	    skb->nh.iph->protocol == IPPROTO_IGMP)
-		local = 1;
-
 	/*
 	 *	Forward the frame
 	 */
-	ct = cache->mfc_maxvif-1;
-	while (ct>=cache->mfc_minvif) {
-		/*
-		 *	0 means don't do it. Silly idea, 255 as don't do it would be cleaner!
-		 */
-		if (skb->nh.iph->ttl > cache->mfc_ttls[ct] && cache->mfc_ttls[ct]>0) {
+	for (ct = cache->mfc_maxvif-1; ct >= cache->mfc_minvif; ct--) {
+		if (skb->nh.iph->ttl > cache->mfc_ttls[ct]) {
 			if (psend != -1)
 				ipmr_queue_xmit(skb, cache, psend, 0);
 			psend=ct;
 		}
-		ct--;
 	}
 	if (psend != -1)
-		ipmr_queue_xmit(skb, cache, psend, 1);
+		ipmr_queue_xmit(skb, cache, psend, !local);
+
+dont_forward:
+	if (!local)
+		kfree_skb(skb, FREE_WRITE);
+	return 0;
+}
+
+
+/*
+ *	Multicast packets for forwarding arrive here
+ */
+
+int ip_mr_input(struct sk_buff *skb)
+{
+	struct mfc_cache *cache;
+	int local = ((struct rtable*)skb->dst)->rt_flags&RTCF_LOCAL;
+
+	/* 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 (skb->nh.iph->protocol == IPPROTO_IGMP && mroute_socket) {
+			    /* 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.
+			     */
+			    raw_rcv(mroute_socket, skb);
+			    return 0;
+		    }
+	}
+
+	cache = ipmr_cache_find(skb->nh.iph->saddr, skb->nh.iph->daddr);
+
+	/*
+	 *	No usable cache entry
+	 */
+
+	if (cache==NULL || (cache->mfc_flags&MFC_QUEUED)) {
+		int vif;
+
+		if (local) {
+			struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+			ip_local_deliver(skb);
+			if (skb2 == NULL)
+				return -ENOBUFS;
+			skb = skb2;
+		}
+
+		vif = ipmr_find_vif(skb->dev);
+		if (vif != ALL_VIFS) {
+			ipmr_cache_unresolved(cache, vif, skb);
+			return -EAGAIN;
+		}
 		kfree_skb(skb, FREE_READ);
 		return 0;
 	}
-	return ip_local_deliver(skb);
+
+	ip_mr_forward(skb, cache, local);
+
+	if (local)
+		return ip_local_deliver(skb);
+	return 0;
+
+dont_forward:
+	if (local)
+		return ip_local_deliver(skb);
+	kfree_skb(skb, FREE_READ);
+	return 0;
+}
+
+#ifdef CONFIG_IP_PIMSM_V1
+/*
+ * Handle IGMP messages of PIMv1
+ */
+
+int pim_rcv_v1(struct sk_buff * skb, unsigned short len)
+{
+	struct igmphdr *pim = (struct igmphdr*)skb->h.raw;
+	struct iphdr   *encap;
+
+        if (!mroute_do_pim ||
+	    len < sizeof(*pim) + sizeof(*encap) ||
+	    pim->group != PIM_V1_VERSION || pim->code != PIM_V1_REGISTER ||
+	    reg_dev == NULL) {
+		kfree_skb(skb, FREE_READ);
+                return -EINVAL;
+        }
+
+	encap = (struct iphdr*)(skb->h.raw + sizeof(struct igmphdr));
+	/*
+	   Check that:
+	   a. packet is really destinted to a multicast group
+	   b. packet is not a NULL-REGISTER
+	   c. packet is not truncated
+	 */
+	if (!MULTICAST(encap->daddr) ||
+	    ntohs(encap->tot_len) == 0 ||
+	    ntohs(encap->tot_len) + sizeof(*pim) > len) {
+		kfree_skb(skb, FREE_READ);
+		return -EINVAL;
+	}
+	skb_pull(skb, (u8*)encap - skb->data);
+	skb->nh.iph = (struct iphdr *)skb->data;
+	skb->dev = reg_dev;
+	memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+	skb->protocol = __constant_htons(ETH_P_IP);
+	skb->ip_summed = 0;
+	skb->pkt_type = PACKET_HOST;
+	dst_release(skb->dst);
+	skb->dst = NULL;
+	((struct net_device_stats*)reg_dev->priv)->rx_bytes += skb->len;
+	((struct net_device_stats*)reg_dev->priv)->rx_packets++;
+	netif_rx(skb);
+	return 0;
 }
+#endif
 
-int ip_mr_find_tunnel(u32 local, u32 remote)
+#ifdef CONFIG_IP_PIMSM_V2
+int pim_rcv(struct sk_buff * skb, unsigned short len)
+{
+	struct pimreghdr *pim = (struct pimreghdr*)skb->h.raw;
+	struct iphdr   *encap;
+
+        if (len < sizeof(*pim) + sizeof(*encap) ||
+	    pim->type != ((PIM_VERSION<<4)|(PIM_REGISTER)) ||
+	    (pim->flags&PIM_NULL_REGISTER) ||
+	    reg_dev == NULL ||
+	    ip_compute_csum((void *)pim, len)) {
+		kfree_skb(skb, FREE_READ);
+                return -EINVAL;
+        }
+
+	/* check if the inner packet is destined to mcast group */
+	encap = (struct iphdr*)(skb->h.raw + sizeof(struct pimreghdr));
+	if (!MULTICAST(encap->daddr) ||
+	    ntohs(encap->tot_len) == 0 ||
+	    ntohs(encap->tot_len) + sizeof(*pim) > len) {
+		kfree_skb(skb, FREE_READ);
+		return -EINVAL;
+	}
+	skb_pull(skb, (u8*)encap - skb->data);
+	skb->nh.iph = (struct iphdr *)skb->data;
+	skb->dev = reg_dev;
+	memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+	skb->protocol = __constant_htons(ETH_P_IP);
+	skb->ip_summed = 0;
+	skb->pkt_type = PACKET_HOST;
+	dst_release(skb->dst);
+	((struct net_device_stats*)reg_dev->priv)->rx_bytes += skb->len;
+	((struct net_device_stats*)reg_dev->priv)->rx_packets++;
+	skb->dst = NULL;
+	netif_rx(skb);
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_RTNETLINK
+
+static int
+ipmr_fill_mroute(struct sk_buff *skb, struct mfc_cache *c, struct rtmsg *rtm)
 {
 	int ct;
-	struct vif_device *vif;
+	struct rtnexthop *nhp;
+	struct device *dev = vif_table[c->mfc_parent].dev;
 
-	for (ct=0; ct<maxvif; ct++) {
-		vif = &vif_table[ct];
-		if (vifc_map&(1<<ct) && vif->flags&VIFF_TUNNEL &&
-		    vif->local == local && vif->remote == remote)
-			return ct;
+	if (dev) {
+		u8 *o = skb->tail;
+		RTA_PUT(skb, RTA_IIF, 4, &dev->ifindex);
+		rtm->rtm_optlen += skb->tail - o;
+	}
+
+	for (ct = c->mfc_minvif; ct < c->mfc_maxvif; ct++) {
+		if (c->mfc_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)));
+			nhp->rtnh_flags = 0;
+			nhp->rtnh_hops = c->mfc_ttls[ct];
+			nhp->rtnh_ifindex = vif_table[ct].dev->ifindex;
+			nhp->rtnh_len = sizeof(*nhp);
+			rtm->rtm_nhs++;
+		}
 	}
-	return -1;
+	rtm->rtm_type = RTN_MULTICAST;
+	return 1;
+
+rtattr_failure:
+	return -EMSGSIZE;
 }
 
+int ipmr_get_route(struct sk_buff *skb, struct rtmsg *rtm)
+{
+	struct mfc_cache *cache;
+	struct rtable *rt = (struct rtable*)skb->dst;
+
+	start_bh_atomic();
+	cache = ipmr_cache_find(rt->rt_src, rt->rt_dst);
+	if (cache==NULL || (cache->mfc_flags&MFC_QUEUED)) {
+		struct device *dev = skb->dev;
+		int vif;
+		int err;
+
+		if (dev == NULL || (vif = ipmr_find_vif(dev)) == ALL_VIFS) {
+			end_bh_atomic();
+			return -ENODEV;
+		}
+		skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
+		skb->nh.iph->ihl = sizeof(struct iphdr)>>2;
+		skb->nh.iph->saddr = rt->rt_src;
+		skb->nh.iph->daddr = rt->rt_dst;
+		skb->nh.iph->version = 0;
+		err = ipmr_cache_unresolved(cache, vif, skb);
+		end_bh_atomic();
+		return err;
+	}
+	/* Resolved cache entry is not changed by net bh,
+	   so that we are allowed to enable it.
+	 */
+	end_bh_atomic();
+
+	if (rtm->rtm_flags & RTM_F_NOTIFY)
+		cache->mfc_flags |= MFC_NOTIFY;
+	return ipmr_fill_mroute(skb, cache, rtm);
+}
+#endif
+
 /*
  *	The /proc interfaces to multicast routing /proc/ip_mr_cache /proc/ip_mr_vif
  */
@@ -945,16 +1421,19 @@
 	int ct;
 
 	len += sprintf(buffer,
-		 "Interface  Bytes In  Pkts In  Bytes Out  Pkts Out  Flags Local    Remote\n");
+		 "Interface      BytesIn  PktsIn  BytesOut PktsOut Flags Local    Remote\n");
 	pos=len;
   
 	for (ct=0;ct<maxvif;ct++) 
 	{
+		char *name = "none";
 		vif=&vif_table[ct];
 		if(!(vifc_map&(1<<ct)))
 			continue;
-        	size = sprintf(buffer+len, "%2d %-10s %8ld  %7ld  %8ld   %7ld   %05X %08lX %08lX\n",
-        		ct, vif->flags&VIFF_TUNNEL ? "Tunnel" : vif->u.dev->name, vif->bytes_in, vif->pkt_in, vif->bytes_out, vif->pkt_out,
+		if (vif->dev)
+			name = vif->dev->name;
+        	size = sprintf(buffer+len, "%2d %-10s %8ld %7ld  %8ld %7ld %05X %08X %08X\n",
+        		ct, name, vif->bytes_in, vif->pkt_in, vif->bytes_out, vif->pkt_out,
         		vif->flags, vif->local, vif->remote);
 		len+=size;
 		pos+=size;
@@ -984,7 +1463,7 @@
 	int ct;
 
 	len += sprintf(buffer,
-		 "Group    Origin   SrcIface Pkts     Bytes    Wrong    VifTtls\n");
+		 "Group    Origin   Iif     Pkts    Bytes    Wrong Oifs\n");
 	pos=len;
   
 	for (ct=0;ct<MFC_LINES;ct++) 
@@ -993,33 +1472,22 @@
 		mfc=mfc_cache_array[ct];
 		while(mfc!=NULL)
 		{
-			char *name="none";
 			int n;
-			/*
-			 *	Device name
-			 */
-			if(mfc->mfc_parent < maxvif && vifc_map&(1<<mfc->mfc_parent)) {
-				if (vif_table[mfc->mfc_parent].flags&VIFF_TUNNEL)
-					name="Tunnel";
-				else
-					name=vif_table[mfc->mfc_parent].u.dev->name;
-			}
+
 			/*
 			 *	Interface forwarding map
 			 */
-			size = sprintf(buffer+len, "%08lX %08lX %-8s %8ld %8ld %8ld",
+			size = sprintf(buffer+len, "%08lX %08lX %-3d %8ld %8ld %8ld",
 				(unsigned long)mfc->mfc_mcastgrp,
 				(unsigned long)mfc->mfc_origin,
-				name,
+				mfc->mfc_parent == ALL_VIFS ? -1 : mfc->mfc_parent,
+				(mfc->mfc_flags & MFC_QUEUED) ? mfc->mfc_unresolved.qlen : mfc->mfc_pkt,
 				mfc->mfc_bytes,
-				mfc->mfc_pkt,
 				mfc->mfc_wrong_if);
-			for(n=0;n<maxvif;n++)
+			for(n=mfc->mfc_minvif;n<mfc->mfc_maxvif;n++)
 			{
-				if(vifc_map&(1<<n))
-					size += sprintf(buffer+len+size, " %-3d", mfc->mfc_ttls[n]);
-				else
-					size += sprintf(buffer+len+size, " --- ");
+				if(vifc_map&(1<<n) && mfc->mfc_ttls[n] < 255)
+					size += sprintf(buffer+len+size, " %2d:%-3d", n, mfc->mfc_ttls[n]);
 			}
 			size += sprintf(buffer+len+size, "\n");
 			len+=size;
@@ -1043,6 +1511,10 @@
   	len-=(offset-begin);
   	if(len>length)
   		len=length;
+	if (len < 0) {
+		len = 0;
+		printk(KERN_CRIT "Yep, guys... our template for proc_*_read is crappy :-)\n");
+	}
   	return len;
 }
 
@@ -1061,6 +1533,19 @@
 };
 #endif	
 
+#ifdef CONFIG_IP_PIMSM_V2
+struct inet_protocol pim_protocol = 
+{
+	pim_rcv,		/* PIM handler		*/
+	NULL,			/* PIM error control	*/
+	NULL,			/* next			*/
+	IPPROTO_PIM,		/* protocol ID		*/
+	0,			/* copy			*/
+	NULL,			/* data			*/
+	"PIM"			/* name			*/
+};
+#endif
+
 
 /*
  *	Setup for IP multicast routing
@@ -1068,7 +1553,7 @@
  
 __initfunc(void ip_mr_init(void))
 {
-	printk(KERN_INFO "Linux IP multicast router 0.06.\n");
+	printk(KERN_INFO "Linux IP multicast router 0.06 plus PIM-SM\n");
 	register_netdevice_notifier(&ip_mr_notifier);
 #ifdef CONFIG_PROC_FS	
 	proc_net_register(&proc_net_ipmr_vif);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov