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