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