]> bbs.cooldavid.org Git - net-next-2.6.git/blob - net/core/dv.c
[PATCH] capable/capability.h (net/)
[net-next-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/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 = (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  */
77 void 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  */
89 static 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  */
119 static 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() */
142 static 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;
177 out:
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
192 int 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                 
450 void 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 */
461         if (!compare_ether_addr(eth->h_dest, skb->dev->dev_addr))
462                 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 }