patch-2.1.89 linux/drivers/net/ethertap.c

Next file: linux/drivers/net/ppp.c
Previous file: linux/drivers/net/dgrs.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.88/linux/drivers/net/ethertap.c linux/drivers/net/ethertap.c
@@ -11,6 +11,7 @@
  *	even for building bridging tunnels.
  */
  
+#include <linux/config.h>
 #include <linux/module.h>
 
 #include <linux/kernel.h>
@@ -25,6 +26,7 @@
 #include <linux/skbuff.h>
 #include <linux/init.h>
 
+#include <net/sock.h>
 #include <linux/netlink.h>
 
 /*
@@ -36,7 +38,10 @@
 static int  ethertap_start_xmit(struct sk_buff *skb, struct device *dev);
 static int  ethertap_close(struct device *dev);
 static struct net_device_stats *ethertap_get_stats(struct device *dev);
-static int ethertap_rx(int id, struct sk_buff *skb);
+static void ethertap_rx(struct sock *sk, int len);
+#ifdef CONFIG_ETHERTAP_MC
+static void set_multicast_list(struct device *dev);
+#endif
 
 static int ethertap_debug = 0;
 
@@ -48,6 +53,10 @@
 
 struct net_local
 {
+	struct sock	*nl;
+#ifdef CONFIG_ETHERTAP_MC
+	__u32		groups;
+#endif
 	struct net_device_stats stats;
 };
 
@@ -58,7 +67,7 @@
  
 __initfunc(int ethertap_probe(struct device *dev))
 {
-	memcpy(dev->dev_addr, "\xFD\xFD\x00\x00\x00\x00", 6);
+	memcpy(dev->dev_addr, "\xFE\xFD\x00\x00\x00\x00", 6);
 	if (dev->mem_start & 0xf)
 		ethertap_debug = dev->mem_start & 0x7;
 
@@ -79,6 +88,9 @@
 	dev->hard_start_xmit = ethertap_start_xmit;
 	dev->stop = ethertap_close;
 	dev->get_stats = ethertap_get_stats;
+#ifdef CONFIG_ETHERTAP_MC
+	dev->set_multicast_list = set_multicast_list;
+#endif
 
 	/*
 	 *	Setup the generic properties
@@ -86,10 +98,10 @@
 
 	ether_setup(dev);
 
-	dev->flags|=IFF_NOARP;	/* Need to set ARP - looks like there is a bug
-				   in the 2.1.x hard header code currently */
+	dev->tx_queue_len = 0;
+	dev->flags|=IFF_NOARP;
 	tap_map[dev->base_addr]=dev;
-	
+
 	return 0;
 }
 
@@ -99,28 +111,58 @@
 
 static int ethertap_open(struct device *dev)
 {
-	struct in_device *in_dev;
+	struct net_local *lp = (struct net_local*)dev->priv;
+
 	if (ethertap_debug > 2)
 		printk("%s: Doing ethertap_open()...", dev->name);
-	netlink_attach(dev->base_addr, ethertap_rx);
+
+	MOD_INC_USE_COUNT;
+
+	lp->nl = netlink_kernel_create(dev->base_addr, ethertap_rx);
+	if (lp->nl == NULL) {
+		MOD_DEC_USE_COUNT;
+		return -ENOBUFS;
+	}
+
 	dev->start = 1;
 	dev->tbusy = 0;
+	return 0;
+}
 
-	/* Fill in the MAC based on the IP address. We do the same thing
-	   here as PLIP does */
-	
-	if((in_dev=dev->ip_ptr)!=NULL)
-	{
-		/*
-		 *	Any address wil do - we take the first
-		 */
-		struct in_ifaddr *ifa=in_dev->ifa_list;
-		if(ifa!=NULL)
-			memcpy(dev->dev_addr+2,&ifa->ifa_local,4);
+#ifdef CONFIG_ETHERTAP_MC
+static unsigned ethertap_mc_hash(__u8 *dest)
+{
+	unsigned idx = 0;
+	idx ^= dest[0];
+	idx ^= dest[1];
+	idx ^= dest[2];
+	idx ^= dest[3];
+	idx ^= dest[4];
+	idx ^= dest[5];
+	return 1U << (idx&0x1F);
+}
+
+static void set_multicast_list(struct device *dev)
+{
+	unsigned groups = ~0;
+	struct net_local *lp = (struct net_local *)dev->priv;
+
+	if (!(dev->flags&(IFF_NOARP|IFF_PROMISC|IFF_ALLMULTI))) {
+		struct dev_mc_list *dmi;
+
+		groups = ethertap_mc_hash(dev->broadcast);
+
+		for (dmi=dev->mc_list; dmi; dmi=dmi->next) {
+			if (dmi->dmi_addrlen != 6)
+				continue;
+			groups |= ethertap_mc_hash(dmi->dmi_addr);
+		}
 	}
-	MOD_INC_USE_COUNT;
-	return 0;
+	lp->groups = groups;
+	if (lp->nl)
+		lp->nl->protinfo.af_netlink.groups = groups;
 }
+#endif
 
 /*
  *	We transmit by throwing the packet at netlink. We have to clone
@@ -130,20 +172,110 @@
 static int ethertap_start_xmit(struct sk_buff *skb, struct device *dev)
 {
 	struct net_local *lp = (struct net_local *)dev->priv;
-	struct sk_buff *tmp;
-	/* copy buffer to tap */
-	tmp=skb_clone(skb, GFP_ATOMIC);
-	if(tmp)
-	{
-		if(netlink_post(dev->base_addr, tmp)<0)
-			kfree_skb(tmp);
-		lp->stats.tx_bytes+=skb->len;
-		lp->stats.tx_packets++;
+#ifdef CONFIG_ETHERTAP_MC
+	struct ethhdr *eth = (struct ethhdr*)skb->data;
+#endif
+
+	if (skb_headroom(skb) < 2) {
+		printk(KERN_DEBUG "%s : bug --- xmit with head<2\n", dev->name);
+		dev_kfree_skb(skb);
+		return 0;
+	}
+	skb_push(skb, 2);
+
+	/* Make the same thing, which loopback does. */
+	if (skb_shared(skb)) {
+	  	struct sk_buff *skb2 = skb;
+	  	skb = skb_clone(skb, GFP_ATOMIC);	/* Clone the buffer */
+	  	if (skb==NULL) {
+			dev_kfree_skb(skb2);
+			return 0;
+		}
+	  	dev_kfree_skb(skb2);
 	}
-	dev_kfree_skb (skb);
+	/* ... but do not orphan it here, netlink does it in any case. */
+
+	lp->stats.tx_bytes+=skb->len;
+	lp->stats.tx_packets++;
+
+#ifndef CONFIG_ETHERTAP_MC
+	netlink_broadcast(lp->nl, skb, 0, ~0, GFP_ATOMIC);
+#else
+	if (dev->flags&IFF_NOARP) {
+		netlink_broadcast(lp->nl, skb, 0, ~0, GFP_ATOMIC);
+		return 0;
+	}
+
+	if (!(eth->h_dest[0]&1)) {
+		/* Unicast packet */
+		__u32 pid;
+		memcpy(&pid, eth->h_dest+2, 4);
+		netlink_unicast(lp->nl, skb, ntohl(pid), MSG_DONTWAIT);
+	} else
+		netlink_broadcast(lp->nl, skb, 0, ethertap_mc_hash(eth->h_dest), GFP_ATOMIC);
+#endif
 	return 0;
 }
 
+static __inline__ int ethertap_rx_skb(struct sk_buff *skb, struct device *dev)
+{
+	struct net_local *lp = (struct net_local *)dev->priv;
+#ifdef CONFIG_ETHERTAP_MC
+	struct ethhdr *eth = (struct ethhdr*)(skb->data + 2);
+#endif
+	int len = skb->len;
+
+	if (len < 16) {
+		printk(KERN_DEBUG "%s : rx len = %d\n", dev->name, len);
+		kfree_skb(skb);
+		return -EINVAL;
+	}
+	if (NETLINK_CREDS(skb)->uid) {
+		printk(KERN_INFO "%s : user %d\n", dev->name, NETLINK_CREDS(skb)->uid);
+		kfree_skb(skb);
+		return -EPERM;
+	}
+
+#ifdef CONFIG_ETHERTAP_MC
+	if (!(dev->flags&(IFF_NOARP|IFF_PROMISC))) {
+		int drop = 0;
+
+		if (eth->h_dest[0]&1) {
+			if (!(ethertap_mc_hash(eth->h_dest)&lp->groups))
+				drop = 1;
+		} else if (memcmp(eth->h_dest, dev->dev_addr, 6) != 0)
+			drop = 1;
+
+		if (drop) {
+			if (ethertap_debug > 3)
+				printk(KERN_DEBUG "%s : not for us\n", dev->name);
+			kfree_skb(skb);
+			return -EINVAL;
+		}
+	}
+#endif
+
+	if (skb_shared(skb)) {
+	  	struct sk_buff *skb2 = skb;
+	  	skb = skb_clone(skb, GFP_KERNEL);	/* Clone the buffer */
+	  	if (skb==NULL) {
+			kfree_skb(skb2);
+			return -ENOBUFS;
+		}
+	  	kfree_skb(skb2);
+	} else
+		skb_orphan(skb);
+
+	skb_pull(skb, 2);
+	skb->dev = dev;
+	skb->protocol=eth_type_trans(skb,dev);
+	memset(skb->cb, 0, sizeof(skb->cb));
+	lp->stats.rx_packets++;
+	lp->stats.rx_bytes+=len;
+	netif_rx(skb);
+	return len;
+}
+
 /*
  *	The typical workload of the driver:
  *	Handle the ether interface interrupts.
@@ -151,37 +283,40 @@
  *	(In this case handle the packets posted from user space..)
  */
 
-static int ethertap_rx(int id, struct sk_buff *skb)
+static void ethertap_rx(struct sock *sk, int len)
 {
-	struct device *dev = (struct device *)(tap_map[id]);
-	struct net_local *lp;
-	int len=skb->len;
-	
-	if(dev==NULL)
-	{
-		printk("ethertap: bad unit!\n");
-		kfree_skb(skb);
-		return -ENXIO;
+	struct device *dev = tap_map[sk->protocol];
+	struct sk_buff *skb;
+
+	if (dev==NULL) {
+		printk(KERN_CRIT "ethertap: bad unit!\n");
+		skb_queue_purge(&sk->receive_queue);
+		return;
 	}
-	lp = (struct net_local *)dev->priv;
 
 	if (ethertap_debug > 3)
 		printk("%s: ethertap_rx()\n", dev->name);
-	skb->dev = dev;
-	skb->protocol=eth_type_trans(skb,dev);
-	lp->stats.rx_packets++;
-	lp->stats.rx_bytes+=len;
-	netif_rx(skb);
-	return len;
+
+	while ((skb = skb_dequeue(&sk->receive_queue)) != NULL)
+		ethertap_rx_skb(skb, dev);
 }
 
 static int ethertap_close(struct device *dev)
 {
+	struct net_local *lp = (struct net_local *)dev->priv;
+	struct sock *sk = lp->nl;
+
 	if (ethertap_debug > 2)
-		printk("%s: Shutting down tap %ld.\n", dev->name, dev->base_addr);
+		printk("%s: Shutting down.\n", dev->name);
 
 	dev->tbusy = 1;
 	dev->start = 0;
+
+	if (sk) {
+		lp->nl = NULL;
+		sock_release(sk->socket);
+	}
+
 	MOD_DEC_USE_COUNT;
 	return 0;
 }
@@ -213,7 +348,7 @@
 	sprintf(devicename,"tap%d",unit);
 	if (dev_get(devicename))
 	{
-		printk(KERN_INFO "ethertap: tap %d already loaded.\n", unit);
+		printk(KERN_INFO "%s already loaded.\n", devicename);
 		return -EBUSY;
 	}
 	if (register_netdev(&dev_ethertap) != 0)

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