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 
dv_init(void)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  */
alloc_divert_blk(struct net_device * dev)52 int alloc_divert_blk(struct net_device *dev)
53 {
54 	int alloc_size = (sizeof(struct divert_blk) + 3) & ~3;
55 
56 	if (dev->type == ARPHRD_ETHER) {
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  */
free_divert_blk(struct net_device * dev)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  */
add_port(u16 ports[],u16 port)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  */
remove_port(u16 ports[],u16 port)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() */
check_args(struct divert_cf * div_cf,struct net_device ** dev)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 
divert_ioctl(unsigned int cmd,struct divert_cf * arg)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 
divert_frame(struct sk_buff * skb)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