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