patch-2.4.0-test10 linux/net/core/dv.c

Next file: linux/net/core/sysctl_net_core.c
Previous file: linux/net/core/dev_mcast.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test9/linux/net/core/dv.c linux/net/core/dv.c
@@ -0,0 +1,551 @@
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Generic frame diversion
+ *
+ * Version:	@(#)eth.c	0.41	09/09/2000
+ *
+ * Authors:	
+ * 		Benoit LOCHER:	initial integration within the kernel with support for ethernet
+ * 		Dave Miller:	improvement on the code (correctness, performance and source files)
+ *
+ */
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <net/dst.h>
+#include <net/arp.h>
+#include <net/sock.h>
+#include <net/ipv6.h>
+#include <net/ip.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/checksum.h>
+#include <linux/divert.h>
+#include <linux/sockios.h>
+
+const char sysctl_divert_version[32]="0.46";	/* Current version */
+
+int __init dv_init(void)
+{
+	printk(KERN_INFO "NET4: Frame Diverter %s\n", sysctl_divert_version);
+	return 0;
+}
+
+/*
+ * Allocate a divert_blk for a device. This must be an ethernet nic.
+ */
+int alloc_divert_blk(struct net_device *dev)
+{
+	int alloc_size = (sizeof(struct divert_blk) + 3) & ~3;
+
+	if (!strncmp(dev->name, "eth", 3)) {
+		printk(KERN_DEBUG "divert: allocating divert_blk for %s\n",
+		       dev->name);
+
+		dev->divert = (struct divert_blk *)
+			kmalloc(alloc_size, GFP_KERNEL);
+		if (dev->divert == NULL) {
+			printk(KERN_DEBUG "divert: unable to allocate divert_blk for %s\n",
+			       dev->name);
+			return -EFAULT;
+		} else {
+			memset(dev->divert, 0, sizeof(struct divert_blk));
+		}
+	} else {
+		printk(KERN_DEBUG "divert: not allocating divert_blk for non-ethernet device %s\n",
+		       dev->name);
+
+		dev->divert = NULL;
+	}
+	return 0;
+} 
+
+/*
+ * Free a divert_blk allocated by the above function, if it was 
+ * allocated on that device.
+ */
+void free_divert_blk(struct net_device *dev)
+{
+	if (dev->divert) {
+		kfree(dev->divert);
+		dev->divert=NULL;
+		printk(KERN_DEBUG "divert: freeing divert_blk for %s\n",
+		       dev->name);
+	} else {
+		printk(KERN_DEBUG "divert: no divert_blk to free, %s not ethernet\n",
+		       dev->name);
+	}
+}
+
+/*
+ * Adds a tcp/udp (source or dest) port to an array
+ */
+int add_port(u16 ports[], u16 port)
+{
+	int i;
+
+	if (port == 0)
+		return -EINVAL;
+
+	/* Storing directly in network format for performance,
+	 * thanks Dave :)
+	 */
+	port = htons(port);
+
+	for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+		if (ports[i] == port)
+			return -EALREADY;
+	}
+	
+	for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+		if (ports[i] == 0) {
+			ports[i] = port;
+			return 0;
+		}
+	}
+
+	return -ENOBUFS;
+}
+
+/*
+ * Removes a port from an array tcp/udp (source or dest)
+ */
+int remove_port(u16 ports[], u16 port)
+{
+	int i;
+
+	if (port == 0)
+		return -EINVAL;
+	
+	/* Storing directly in network format for performance,
+	 * thanks Dave !
+	 */
+	port = htons(port);
+
+	for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+		if (ports[i] == port) {
+			ports[i] = 0;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/* Some basic sanity checks on the arguments passed to divert_ioctl() */
+int check_args(struct divert_cf *div_cf, struct net_device **dev)
+{
+	char devname[32];
+		
+	if (dev == NULL)
+		return -EFAULT;
+	
+	/* GETVERSION: all other args are unused */
+	if (div_cf->cmd == DIVCMD_GETVERSION)
+		return 0;
+	
+	/* Network device index should reasonably be between 0 and 1000 :) */
+	if (div_cf->dev_index < 0 || div_cf->dev_index > 1000) 
+		return -EINVAL;
+			
+	/* Let's try to find the ifname */
+	sprintf(devname, "eth%d", div_cf->dev_index);
+	*dev = dev_get_by_name(devname);
+	
+	/* dev should NOT be null */
+	if (*dev == NULL)
+		return -EINVAL;
+	
+	/* user issuing the ioctl must be a super one :) */
+	if (!suser())
+		return -EPERM;
+
+	/* Device must have a divert_blk member NOT null */
+	if ((*dev)->divert == NULL)
+		return -EFAULT;
+
+	return 0;
+}
+
+/*
+ * control function of the diverter
+ */
+#define	DVDBG(a)	\
+	printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a))
+
+int divert_ioctl(unsigned int cmd, struct divert_cf *arg)
+{
+	struct divert_cf	div_cf;
+	struct divert_blk	*div_blk;
+	struct net_device	*dev;
+	int			ret;
+
+	switch (cmd) {
+	case SIOCGIFDIVERT:
+		DVDBG("SIOCGIFDIVERT, copy_from_user");
+		if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
+			return -EFAULT;
+		DVDBG("before check_args");
+		ret = check_args(&div_cf, &dev);
+		if (ret)
+			return ret;
+		DVDBG("after checkargs");
+		div_blk = dev->divert;
+			
+		DVDBG("befre switch()");
+		switch (div_cf.cmd) {
+		case DIVCMD_GETSTATUS:
+			/* Now, just give the user the raw divert block
+			 * for him to play with :)
+			 */
+			if (copy_to_user(div_cf.arg1.ptr, dev->divert,
+					 sizeof(struct divert_blk)))
+				return -EFAULT;
+			break;
+
+		case DIVCMD_GETVERSION:
+			DVDBG("GETVERSION: checking ptr");
+			if (div_cf.arg1.ptr == NULL)
+				return -EINVAL;
+			DVDBG("GETVERSION: copying data to userland");
+			if (copy_to_user(div_cf.arg1.ptr,
+					 sysctl_divert_version, 32))
+				return -EFAULT;
+			DVDBG("GETVERSION: data copied");
+			break;
+
+		default:
+			return -EINVAL;
+		};
+
+		break;
+
+	case SIOCSIFDIVERT:
+		if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
+			return -EFAULT;
+
+		ret = check_args(&div_cf, &dev);
+		if (ret)
+			return ret;
+
+		div_blk = dev->divert;
+
+		switch(div_cf.cmd) {
+		case DIVCMD_RESET:
+			div_blk->divert = 0;
+			div_blk->protos = DIVERT_PROTO_NONE;
+			memset(div_blk->tcp_dst, 0,
+			       MAX_DIVERT_PORTS * sizeof(u16));
+			memset(div_blk->tcp_src, 0,
+			       MAX_DIVERT_PORTS * sizeof(u16));
+			memset(div_blk->udp_dst, 0,
+			       MAX_DIVERT_PORTS * sizeof(u16));
+			memset(div_blk->udp_src, 0,
+			       MAX_DIVERT_PORTS * sizeof(u16));
+			return 0;
+				
+		case DIVCMD_DIVERT:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ENABLE:
+				if (div_blk->divert)
+					return -EALREADY;
+				div_blk->divert = 1;
+				break;
+
+			case DIVARG1_DISABLE:
+				if (!div_blk->divert)
+					return -EALREADY;
+				div_blk->divert = 0;
+				break;
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_IP:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ENABLE:
+				if (div_blk->protos & DIVERT_PROTO_IP)
+					return -EALREADY;
+				div_blk->protos |= DIVERT_PROTO_IP;
+				break;
+
+			case DIVARG1_DISABLE:
+				if (!(div_blk->protos & DIVERT_PROTO_IP))
+					return -EALREADY;
+				div_blk->protos &= ~DIVERT_PROTO_IP;
+				break;
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_TCP:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ENABLE:
+				if (div_blk->protos & DIVERT_PROTO_TCP)
+					return -EALREADY;
+				div_blk->protos |= DIVERT_PROTO_TCP;
+				break;
+
+			case DIVARG1_DISABLE:
+				if (!(div_blk->protos & DIVERT_PROTO_TCP))
+					return -EALREADY;
+				div_blk->protos &= ~DIVERT_PROTO_TCP;
+				break;
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_TCPDST:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ADD:
+				return add_port(div_blk->tcp_dst,
+						div_cf.arg2.uint16);
+				
+			case DIVARG1_REMOVE:
+				return remove_port(div_blk->tcp_dst,
+						   div_cf.arg2.uint16);
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_TCPSRC:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ADD:
+				return add_port(div_blk->tcp_src,
+						div_cf.arg2.uint16);
+
+			case DIVARG1_REMOVE:
+				return remove_port(div_blk->tcp_src,
+						   div_cf.arg2.uint16);
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_UDP:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ENABLE:
+				if (div_blk->protos & DIVERT_PROTO_UDP)
+					return -EALREADY;
+				div_blk->protos |= DIVERT_PROTO_UDP;
+				break;
+
+			case DIVARG1_DISABLE:
+				if (!(div_blk->protos & DIVERT_PROTO_UDP))
+					return -EALREADY;
+				div_blk->protos &= ~DIVERT_PROTO_UDP;
+				break;
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_UDPDST:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ADD:
+				return add_port(div_blk->udp_dst,
+						div_cf.arg2.uint16);
+
+			case DIVARG1_REMOVE:
+				return remove_port(div_blk->udp_dst,
+						   div_cf.arg2.uint16);
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_UDPSRC:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ADD:
+				return add_port(div_blk->udp_src,
+						div_cf.arg2.uint16);
+
+			case DIVARG1_REMOVE:
+				return remove_port(div_blk->udp_src,
+						   div_cf.arg2.uint16);
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		case DIVCMD_ICMP:
+			switch(div_cf.arg1.int32) {
+			case DIVARG1_ENABLE:
+				if (div_blk->protos & DIVERT_PROTO_ICMP)
+					return -EALREADY;
+				div_blk->protos |= DIVERT_PROTO_ICMP;
+				break;
+
+			case DIVARG1_DISABLE:
+				if (!(div_blk->protos & DIVERT_PROTO_ICMP))
+					return -EALREADY;
+				div_blk->protos &= ~DIVERT_PROTO_ICMP;
+				break;
+
+			default:
+				return -EINVAL;
+			};
+
+			break;
+
+		default:
+			return -EINVAL;
+		};
+
+		break;
+
+	default:
+		return -EINVAL;
+	};
+
+	return 0;
+}
+
+
+/*
+ * Check if packet should have its dest mac address set to the box itself
+ * for diversion
+ */
+
+#define	ETH_DIVERT_FRAME(skb) \
+	memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); \
+	skb->pkt_type=PACKET_HOST
+		
+void divert_frame(struct sk_buff *skb)
+{
+	struct ethhdr			*eth = skb->mac.ethernet;
+	struct iphdr			*iph;
+	struct tcphdr			*tcph;
+	struct udphdr			*udph;
+	struct divert_blk		*divert = skb->dev->divert;
+	int				i, src, dst;
+	unsigned char			*skb_data_end = skb->data + skb->len;
+
+	/* Packet is already aimed at us, return */
+	if (!memcmp(eth, skb->dev->dev_addr, ETH_ALEN))
+		return;
+	
+	/* proto is not IP, do nothing */
+	if (eth->h_proto != htons(ETH_P_IP))
+		return;
+	
+	/* Divert all IP frames ? */
+	if (divert->protos & DIVERT_PROTO_IP) {
+		ETH_DIVERT_FRAME(skb);
+		return;
+	}
+	
+	/* Check for possible (maliciously) malformed IP frame (thanks Dave) */
+	iph = (struct iphdr *) skb->data;
+	if (((iph->ihl<<2)+(unsigned char*)(iph)) >= skb_data_end) {
+		printk(KERN_INFO "divert: malformed IP packet !\n");
+		return;
+	}
+
+	switch (iph->protocol) {
+	/* Divert all ICMP frames ? */
+	case IPPROTO_ICMP:
+		if (divert->protos & DIVERT_PROTO_ICMP) {
+			ETH_DIVERT_FRAME(skb);
+			return;
+		}
+		break;
+
+	/* Divert all TCP frames ? */
+	case IPPROTO_TCP:
+		if (divert->protos & DIVERT_PROTO_TCP) {
+			ETH_DIVERT_FRAME(skb);
+			return;
+		}
+
+		/* Check for possible (maliciously) malformed IP
+		 * frame (thanx Dave)
+		 */
+		tcph = (struct tcphdr *)
+			(((unsigned char *)iph) + (iph->ihl<<2));
+		if (((unsigned char *)(tcph+1)) >= skb_data_end) {
+			printk(KERN_INFO "divert: malformed TCP packet !\n");
+			return;
+		}
+
+		/* Divert some tcp dst/src ports only ?*/
+		for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+			dst = divert->tcp_dst[i];
+			src = divert->tcp_src[i];
+			if ((dst && dst == tcph->dest) ||
+			    (src && src == tcph->source)) {
+				ETH_DIVERT_FRAME(skb);
+				return;
+			}
+		}
+		break;
+
+	/* Divert all UDP frames ? */
+	case IPPROTO_UDP:
+		if (divert->protos & DIVERT_PROTO_UDP) {
+			ETH_DIVERT_FRAME(skb);
+			return;
+		}
+
+		/* Check for possible (maliciously) malformed IP
+		 * packet (thanks Dave)
+		 */
+		udph = (struct udphdr *)
+			(((unsigned char *)iph) + (iph->ihl<<2));
+		if (((unsigned char *)(udph+1)) >= skb_data_end) {
+			printk(KERN_INFO
+			       "divert: malformed UDP packet !\n");
+			return;
+		}
+
+		/* Divert some udp dst/src ports only ? */
+		for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+			dst = divert->udp_dst[i];
+			src = divert->udp_src[i];
+			if ((dst && dst == udph->dest) ||
+			    (src && src == udph->source)) {
+				ETH_DIVERT_FRAME(skb);
+				return;
+			}
+		}
+		break;
+	};
+
+	return;
+}
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)