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