Linux kernel & device driver programming

Cross-Referenced Linux and Device Driver Code

[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ]
Version: [ 2.6.11.8 ] [ 2.6.25 ] [ 2.6.25.8 ] [ 2.6.31.13 ] Architecture: [ i386 ]
  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 }
549 
  This page was automatically generated by the LXR engine.