0e5b3e671620524655cb5d3e27dbefa6ab43ef27
[linux-flexiantxendom0-3.2.10.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  * 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         } 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                 printk(KERN_DEBUG "divert: freeing divert_blk for %s\n",
88                        dev->name);
89         } else {
90                 printk(KERN_DEBUG "divert: no divert_blk to free, %s not ethernet\n",
91                        dev->name);
92         }
93 }
94
95 /*
96  * Adds a tcp/udp (source or dest) port to an array
97  */
98 int add_port(u16 ports[], u16 port)
99 {
100         int i;
101
102         if (port == 0)
103                 return -EINVAL;
104
105         /* Storing directly in network format for performance,
106          * thanks Dave :)
107          */
108         port = htons(port);
109
110         for (i = 0; i < MAX_DIVERT_PORTS; i++) {
111                 if (ports[i] == port)
112                         return -EALREADY;
113         }
114         
115         for (i = 0; i < MAX_DIVERT_PORTS; i++) {
116                 if (ports[i] == 0) {
117                         ports[i] = port;
118                         return 0;
119                 }
120         }
121
122         return -ENOBUFS;
123 }
124
125 /*
126  * Removes a port from an array tcp/udp (source or dest)
127  */
128 int remove_port(u16 ports[], u16 port)
129 {
130         int i;
131
132         if (port == 0)
133                 return -EINVAL;
134         
135         /* Storing directly in network format for performance,
136          * thanks Dave !
137          */
138         port = htons(port);
139
140         for (i = 0; i < MAX_DIVERT_PORTS; i++) {
141                 if (ports[i] == port) {
142                         ports[i] = 0;
143                         return 0;
144                 }
145         }
146
147         return -EINVAL;
148 }
149
150 /* Some basic sanity checks on the arguments passed to divert_ioctl() */
151 int check_args(struct divert_cf *div_cf, struct net_device **dev)
152 {
153         char devname[32];
154                 
155         if (dev == NULL)
156                 return -EFAULT;
157         
158         /* GETVERSION: all other args are unused */
159         if (div_cf->cmd == DIVCMD_GETVERSION)
160                 return 0;
161         
162         /* Network device index should reasonably be between 0 and 1000 :) */
163         if (div_cf->dev_index < 0 || div_cf->dev_index > 1000) 
164                 return -EINVAL;
165                         
166         /* Let's try to find the ifname */
167         sprintf(devname, "eth%d", div_cf->dev_index);
168         *dev = dev_get_by_name(devname);
169         
170         /* dev should NOT be null */
171         if (*dev == NULL)
172                 return -EINVAL;
173         
174         /* user issuing the ioctl must be a super one :) */
175         if (!suser())
176                 return -EPERM;
177
178         /* Device must have a divert_blk member NOT null */
179         if ((*dev)->divert == NULL)
180                 return -EFAULT;
181
182         return 0;
183 }
184
185 /*
186  * control function of the diverter
187  */
188 #define DVDBG(a)        \
189         printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a))
190
191 int divert_ioctl(unsigned int cmd, struct divert_cf *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(skb->mac.ethernet, 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 = skb->mac.ethernet;
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
549         return;
550 }
551