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