File: /usr/src/linux/net/core/dv.c

1     /*
2      * INET		An implementation of the TCP/IP protocol suite for the LINUX
3      *		operating system.  INET is implemented using the  BSD Socket
4      *		interface as the means of communication with the user level.
5      *
6      *		Generic frame diversion
7      *
8      * Version:	@(#)eth.c	0.41	09/09/2000
9      *
10      * Authors:	
11      * 		Benoit LOCHER:	initial integration within the kernel with support for ethernet
12      * 		Dave Miller:	improvement on the code (correctness, performance and source files)
13      *
14      */
15     #include <linux/types.h>
16     #include <linux/kernel.h>
17     #include <linux/sched.h>
18     #include <linux/string.h>
19     #include <linux/mm.h>
20     #include <linux/socket.h>
21     #include <linux/in.h>
22     #include <linux/inet.h>
23     #include <linux/ip.h>
24     #include <linux/udp.h>
25     #include <linux/netdevice.h>
26     #include <linux/etherdevice.h>
27     #include <linux/skbuff.h>
28     #include <linux/errno.h>
29     #include <linux/init.h>
30     #include <net/dst.h>
31     #include <net/arp.h>
32     #include <net/sock.h>
33     #include <net/ipv6.h>
34     #include <net/ip.h>
35     #include <asm/uaccess.h>
36     #include <asm/system.h>
37     #include <asm/checksum.h>
38     #include <linux/divert.h>
39     #include <linux/sockios.h>
40     
41     const char sysctl_divert_version[32]="0.46";	/* Current version */
42     
43     int __init dv_init(void)
44     {
45     	printk(KERN_INFO "NET4: Frame Diverter %s\n", sysctl_divert_version);
46     	return 0;
47     }
48     
49     /*
50      * Allocate a divert_blk for a device. This must be an ethernet nic.
51      */
52     int alloc_divert_blk(struct net_device *dev)
53     {
54     	int alloc_size = (sizeof(struct divert_blk) + 3) & ~3;
55     
56     	if (!strncmp(dev->name, "eth", 3)) {
57     		printk(KERN_DEBUG "divert: allocating divert_blk for %s\n",
58     		       dev->name);
59     
60     		dev->divert = (struct divert_blk *)
61     			kmalloc(alloc_size, GFP_KERNEL);
62     		if (dev->divert == NULL) {
63     			printk(KERN_DEBUG "divert: unable to allocate divert_blk for %s\n",
64     			       dev->name);
65     			return -ENOMEM;
66     		} else {
67     			memset(dev->divert, 0, sizeof(struct divert_blk));
68     		}
69     		dev_hold(dev);
70     	} else {
71     		printk(KERN_DEBUG "divert: not allocating divert_blk for non-ethernet device %s\n",
72     		       dev->name);
73     
74     		dev->divert = NULL;
75     	}
76     	return 0;
77     } 
78     
79     /*
80      * Free a divert_blk allocated by the above function, if it was 
81      * allocated on that device.
82      */
83     void free_divert_blk(struct net_device *dev)
84     {
85     	if (dev->divert) {
86     		kfree(dev->divert);
87     		dev->divert=NULL;
88     		dev_put(dev);
89     		printk(KERN_DEBUG "divert: freeing divert_blk for %s\n",
90     		       dev->name);
91     	} else {
92     		printk(KERN_DEBUG "divert: no divert_blk to free, %s not ethernet\n",
93     		       dev->name);
94     	}
95     }
96     
97     /*
98      * Adds a tcp/udp (source or dest) port to an array
99      */
100     int add_port(u16 ports[], u16 port)
101     {
102     	int i;
103     
104     	if (port == 0)
105     		return -EINVAL;
106     
107     	/* Storing directly in network format for performance,
108     	 * thanks Dave :)
109     	 */
110     	port = htons(port);
111     
112     	for (i = 0; i < MAX_DIVERT_PORTS; i++) {
113     		if (ports[i] == port)
114     			return -EALREADY;
115     	}
116     	
117     	for (i = 0; i < MAX_DIVERT_PORTS; i++) {
118     		if (ports[i] == 0) {
119     			ports[i] = port;
120     			return 0;
121     		}
122     	}
123     
124     	return -ENOBUFS;
125     }
126     
127     /*
128      * Removes a port from an array tcp/udp (source or dest)
129      */
130     int remove_port(u16 ports[], u16 port)
131     {
132     	int i;
133     
134     	if (port == 0)
135     		return -EINVAL;
136     	
137     	/* Storing directly in network format for performance,
138     	 * thanks Dave !
139     	 */
140     	port = htons(port);
141     
142     	for (i = 0; i < MAX_DIVERT_PORTS; i++) {
143     		if (ports[i] == port) {
144     			ports[i] = 0;
145     			return 0;
146     		}
147     	}
148     
149     	return -EINVAL;
150     }
151     
152     /* Some basic sanity checks on the arguments passed to divert_ioctl() */
153     int check_args(struct divert_cf *div_cf, struct net_device **dev)
154     {
155     	char devname[32];
156     	int ret;
157     
158     	if (dev == NULL)
159     		return -EFAULT;
160     	
161     	/* GETVERSION: all other args are unused */
162     	if (div_cf->cmd == DIVCMD_GETVERSION)
163     		return 0;
164     	
165     	/* Network device index should reasonably be between 0 and 1000 :) */
166     	if (div_cf->dev_index < 0 || div_cf->dev_index > 1000) 
167     		return -EINVAL;
168     			
169     	/* Let's try to find the ifname */
170     	sprintf(devname, "eth%d", div_cf->dev_index);
171     	*dev = dev_get_by_name(devname);
172     	
173     	/* dev should NOT be null */
174     	if (*dev == NULL)
175     		return -EINVAL;
176     
177     	ret = 0;
178     
179     	/* user issuing the ioctl must be a super one :) */
180     	if (!capable(CAP_SYS_ADMIN)) {
181     		ret = -EPERM;
182     		goto out;
183     	}
184     
185     	/* Device must have a divert_blk member NOT null */
186     	if ((*dev)->divert == NULL)
187     		ret = -EINVAL;
188     out:
189     	dev_put(*dev);
190     	return ret;
191     }
192     
193     /*
194      * control function of the diverter
195      */
196     #define	DVDBG(a)	\
197     	printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a))
198     
199     int divert_ioctl(unsigned int cmd, struct divert_cf *arg)
200     {
201     	struct divert_cf	div_cf;
202     	struct divert_blk	*div_blk;
203     	struct net_device	*dev;
204     	int			ret;
205     
206     	switch (cmd) {
207     	case SIOCGIFDIVERT:
208     		DVDBG("SIOCGIFDIVERT, copy_from_user");
209     		if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
210     			return -EFAULT;
211     		DVDBG("before check_args");
212     		ret = check_args(&div_cf, &dev);
213     		if (ret)
214     			return ret;
215     		DVDBG("after checkargs");
216     		div_blk = dev->divert;
217     			
218     		DVDBG("befre switch()");
219     		switch (div_cf.cmd) {
220     		case DIVCMD_GETSTATUS:
221     			/* Now, just give the user the raw divert block
222     			 * for him to play with :)
223     			 */
224     			if (copy_to_user(div_cf.arg1.ptr, dev->divert,
225     					 sizeof(struct divert_blk)))
226     				return -EFAULT;
227     			break;
228     
229     		case DIVCMD_GETVERSION:
230     			DVDBG("GETVERSION: checking ptr");
231     			if (div_cf.arg1.ptr == NULL)
232     				return -EINVAL;
233     			DVDBG("GETVERSION: copying data to userland");
234     			if (copy_to_user(div_cf.arg1.ptr,
235     					 sysctl_divert_version, 32))
236     				return -EFAULT;
237     			DVDBG("GETVERSION: data copied");
238     			break;
239     
240     		default:
241     			return -EINVAL;
242     		};
243     
244     		break;
245     
246     	case SIOCSIFDIVERT:
247     		if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
248     			return -EFAULT;
249     
250     		ret = check_args(&div_cf, &dev);
251     		if (ret)
252     			return ret;
253     
254     		div_blk = dev->divert;
255     
256     		switch(div_cf.cmd) {
257     		case DIVCMD_RESET:
258     			div_blk->divert = 0;
259     			div_blk->protos = DIVERT_PROTO_NONE;
260     			memset(div_blk->tcp_dst, 0,
261     			       MAX_DIVERT_PORTS * sizeof(u16));
262     			memset(div_blk->tcp_src, 0,
263     			       MAX_DIVERT_PORTS * sizeof(u16));
264     			memset(div_blk->udp_dst, 0,
265     			       MAX_DIVERT_PORTS * sizeof(u16));
266     			memset(div_blk->udp_src, 0,
267     			       MAX_DIVERT_PORTS * sizeof(u16));
268     			return 0;
269     				
270     		case DIVCMD_DIVERT:
271     			switch(div_cf.arg1.int32) {
272     			case DIVARG1_ENABLE:
273     				if (div_blk->divert)
274     					return -EALREADY;
275     				div_blk->divert = 1;
276     				break;
277     
278     			case DIVARG1_DISABLE:
279     				if (!div_blk->divert)
280     					return -EALREADY;
281     				div_blk->divert = 0;
282     				break;
283     
284     			default:
285     				return -EINVAL;
286     			};
287     
288     			break;
289     
290     		case DIVCMD_IP:
291     			switch(div_cf.arg1.int32) {
292     			case DIVARG1_ENABLE:
293     				if (div_blk->protos & DIVERT_PROTO_IP)
294     					return -EALREADY;
295     				div_blk->protos |= DIVERT_PROTO_IP;
296     				break;
297     
298     			case DIVARG1_DISABLE:
299     				if (!(div_blk->protos & DIVERT_PROTO_IP))
300     					return -EALREADY;
301     				div_blk->protos &= ~DIVERT_PROTO_IP;
302     				break;
303     
304     			default:
305     				return -EINVAL;
306     			};
307     
308     			break;
309     
310     		case DIVCMD_TCP:
311     			switch(div_cf.arg1.int32) {
312     			case DIVARG1_ENABLE:
313     				if (div_blk->protos & DIVERT_PROTO_TCP)
314     					return -EALREADY;
315     				div_blk->protos |= DIVERT_PROTO_TCP;
316     				break;
317     
318     			case DIVARG1_DISABLE:
319     				if (!(div_blk->protos & DIVERT_PROTO_TCP))
320     					return -EALREADY;
321     				div_blk->protos &= ~DIVERT_PROTO_TCP;
322     				break;
323     
324     			default:
325     				return -EINVAL;
326     			};
327     
328     			break;
329     
330     		case DIVCMD_TCPDST:
331     			switch(div_cf.arg1.int32) {
332     			case DIVARG1_ADD:
333     				return add_port(div_blk->tcp_dst,
334     						div_cf.arg2.uint16);
335     				
336     			case DIVARG1_REMOVE:
337     				return remove_port(div_blk->tcp_dst,
338     						   div_cf.arg2.uint16);
339     
340     			default:
341     				return -EINVAL;
342     			};
343     
344     			break;
345     
346     		case DIVCMD_TCPSRC:
347     			switch(div_cf.arg1.int32) {
348     			case DIVARG1_ADD:
349     				return add_port(div_blk->tcp_src,
350     						div_cf.arg2.uint16);
351     
352     			case DIVARG1_REMOVE:
353     				return remove_port(div_blk->tcp_src,
354     						   div_cf.arg2.uint16);
355     
356     			default:
357     				return -EINVAL;
358     			};
359     
360     			break;
361     
362     		case DIVCMD_UDP:
363     			switch(div_cf.arg1.int32) {
364     			case DIVARG1_ENABLE:
365     				if (div_blk->protos & DIVERT_PROTO_UDP)
366     					return -EALREADY;
367     				div_blk->protos |= DIVERT_PROTO_UDP;
368     				break;
369     
370     			case DIVARG1_DISABLE:
371     				if (!(div_blk->protos & DIVERT_PROTO_UDP))
372     					return -EALREADY;
373     				div_blk->protos &= ~DIVERT_PROTO_UDP;
374     				break;
375     
376     			default:
377     				return -EINVAL;
378     			};
379     
380     			break;
381     
382     		case DIVCMD_UDPDST:
383     			switch(div_cf.arg1.int32) {
384     			case DIVARG1_ADD:
385     				return add_port(div_blk->udp_dst,
386     						div_cf.arg2.uint16);
387     
388     			case DIVARG1_REMOVE:
389     				return remove_port(div_blk->udp_dst,
390     						   div_cf.arg2.uint16);
391     
392     			default:
393     				return -EINVAL;
394     			};
395     
396     			break;
397     
398     		case DIVCMD_UDPSRC:
399     			switch(div_cf.arg1.int32) {
400     			case DIVARG1_ADD:
401     				return add_port(div_blk->udp_src,
402     						div_cf.arg2.uint16);
403     
404     			case DIVARG1_REMOVE:
405     				return remove_port(div_blk->udp_src,
406     						   div_cf.arg2.uint16);
407     
408     			default:
409     				return -EINVAL;
410     			};
411     
412     			break;
413     
414     		case DIVCMD_ICMP:
415     			switch(div_cf.arg1.int32) {
416     			case DIVARG1_ENABLE:
417     				if (div_blk->protos & DIVERT_PROTO_ICMP)
418     					return -EALREADY;
419     				div_blk->protos |= DIVERT_PROTO_ICMP;
420     				break;
421     
422     			case DIVARG1_DISABLE:
423     				if (!(div_blk->protos & DIVERT_PROTO_ICMP))
424     					return -EALREADY;
425     				div_blk->protos &= ~DIVERT_PROTO_ICMP;
426     				break;
427     
428     			default:
429     				return -EINVAL;
430     			};
431     
432     			break;
433     
434     		default:
435     			return -EINVAL;
436     		};
437     
438     		break;
439     
440     	default:
441     		return -EINVAL;
442     	};
443     
444     	return 0;
445     }
446     
447     
448     /*
449      * Check if packet should have its dest mac address set to the box itself
450      * for diversion
451      */
452     
453     #define	ETH_DIVERT_FRAME(skb) \
454     	memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); \
455     	skb->pkt_type=PACKET_HOST
456     		
457     void divert_frame(struct sk_buff *skb)
458     {
459     	struct ethhdr			*eth = skb->mac.ethernet;
460     	struct iphdr			*iph;
461     	struct tcphdr			*tcph;
462     	struct udphdr			*udph;
463     	struct divert_blk		*divert = skb->dev->divert;
464     	int				i, src, dst;
465     	unsigned char			*skb_data_end = skb->data + skb->len;
466     
467     	/* Packet is already aimed at us, return */
468     	if (!memcmp(eth, skb->dev->dev_addr, ETH_ALEN))
469     		return;
470     	
471     	/* proto is not IP, do nothing */
472     	if (eth->h_proto != htons(ETH_P_IP))
473     		return;
474     	
475     	/* Divert all IP frames ? */
476     	if (divert->protos & DIVERT_PROTO_IP) {
477     		ETH_DIVERT_FRAME(skb);
478     		return;
479     	}
480     	
481     	/* Check for possible (maliciously) malformed IP frame (thanks Dave) */
482     	iph = (struct iphdr *) skb->data;
483     	if (((iph->ihl<<2)+(unsigned char*)(iph)) >= skb_data_end) {
484     		printk(KERN_INFO "divert: malformed IP packet !\n");
485     		return;
486     	}
487     
488     	switch (iph->protocol) {
489     	/* Divert all ICMP frames ? */
490     	case IPPROTO_ICMP:
491     		if (divert->protos & DIVERT_PROTO_ICMP) {
492     			ETH_DIVERT_FRAME(skb);
493     			return;
494     		}
495     		break;
496     
497     	/* Divert all TCP frames ? */
498     	case IPPROTO_TCP:
499     		if (divert->protos & DIVERT_PROTO_TCP) {
500     			ETH_DIVERT_FRAME(skb);
501     			return;
502     		}
503     
504     		/* Check for possible (maliciously) malformed IP
505     		 * frame (thanx Dave)
506     		 */
507     		tcph = (struct tcphdr *)
508     			(((unsigned char *)iph) + (iph->ihl<<2));
509     		if (((unsigned char *)(tcph+1)) >= skb_data_end) {
510     			printk(KERN_INFO "divert: malformed TCP packet !\n");
511     			return;
512     		}
513     
514     		/* Divert some tcp dst/src ports only ?*/
515     		for (i = 0; i < MAX_DIVERT_PORTS; i++) {
516     			dst = divert->tcp_dst[i];
517     			src = divert->tcp_src[i];
518     			if ((dst && dst == tcph->dest) ||
519     			    (src && src == tcph->source)) {
520     				ETH_DIVERT_FRAME(skb);
521     				return;
522     			}
523     		}
524     		break;
525     
526     	/* Divert all UDP frames ? */
527     	case IPPROTO_UDP:
528     		if (divert->protos & DIVERT_PROTO_UDP) {
529     			ETH_DIVERT_FRAME(skb);
530     			return;
531     		}
532     
533     		/* Check for possible (maliciously) malformed IP
534     		 * packet (thanks Dave)
535     		 */
536     		udph = (struct udphdr *)
537     			(((unsigned char *)iph) + (iph->ihl<<2));
538     		if (((unsigned char *)(udph+1)) >= skb_data_end) {
539     			printk(KERN_INFO
540     			       "divert: malformed UDP packet !\n");
541     			return;
542     		}
543     
544     		/* Divert some udp dst/src ports only ? */
545     		for (i = 0; i < MAX_DIVERT_PORTS; i++) {
546     			dst = divert->udp_dst[i];
547     			src = divert->udp_src[i];
548     			if ((dst && dst == udph->dest) ||
549     			    (src && src == udph->source)) {
550     				ETH_DIVERT_FRAME(skb);
551     				return;
552     			}
553     		}
554     		break;
555     	};
556     
557     	return;
558     }
559     
560