MOXA linux-2.6.x / linux-2.6.9-uc0 from sdlinux-moxaart.tgz
[linux-2.6.9-moxart.git] / net / core / dv.c
blob05c76cb9c8fd72ee6e466f1420cebaaf6c6274b6
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.
6 * Generic frame diversion
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)
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>
40 const char sysctl_divert_version[32]="0.46"; /* Current version */
42 static int __init dv_init(void)
44 return 0;
46 module_init(dv_init);
49 * Allocate a divert_blk for a device. This must be an ethernet nic.
51 int alloc_divert_blk(struct net_device *dev)
53 int alloc_size = (sizeof(struct divert_blk) + 3) & ~3;
55 if (dev->type == ARPHRD_ETHER) {
56 printk(KERN_DEBUG "divert: allocating divert_blk for %s\n",
57 dev->name);
59 dev->divert = (struct divert_blk *)
60 kmalloc(alloc_size, GFP_KERNEL);
61 if (dev->divert == NULL) {
62 printk(KERN_DEBUG "divert: unable to allocate divert_blk for %s\n",
63 dev->name);
64 return -ENOMEM;
65 } else {
66 memset(dev->divert, 0, sizeof(struct divert_blk));
68 dev_hold(dev);
69 } else {
70 printk(KERN_DEBUG "divert: not allocating divert_blk for non-ethernet device %s\n",
71 dev->name);
73 dev->divert = NULL;
75 return 0;
79 * Free a divert_blk allocated by the above function, if it was
80 * allocated on that device.
82 void free_divert_blk(struct net_device *dev)
84 if (dev->divert) {
85 kfree(dev->divert);
86 dev->divert=NULL;
87 dev_put(dev);
88 printk(KERN_DEBUG "divert: freeing divert_blk for %s\n",
89 dev->name);
90 } else {
91 printk(KERN_DEBUG "divert: no divert_blk to free, %s not ethernet\n",
92 dev->name);
97 * Adds a tcp/udp (source or dest) port to an array
99 static int add_port(u16 ports[], u16 port)
101 int i;
103 if (port == 0)
104 return -EINVAL;
106 /* Storing directly in network format for performance,
107 * thanks Dave :)
109 port = htons(port);
111 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
112 if (ports[i] == port)
113 return -EALREADY;
116 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
117 if (ports[i] == 0) {
118 ports[i] = port;
119 return 0;
123 return -ENOBUFS;
127 * Removes a port from an array tcp/udp (source or dest)
129 static int remove_port(u16 ports[], u16 port)
131 int i;
133 if (port == 0)
134 return -EINVAL;
136 /* Storing directly in network format for performance,
137 * thanks Dave !
139 port = htons(port);
141 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
142 if (ports[i] == port) {
143 ports[i] = 0;
144 return 0;
148 return -EINVAL;
151 /* Some basic sanity checks on the arguments passed to divert_ioctl() */
152 static int check_args(struct divert_cf *div_cf, struct net_device **dev)
154 char devname[32];
155 int ret;
157 if (dev == NULL)
158 return -EFAULT;
160 /* GETVERSION: all other args are unused */
161 if (div_cf->cmd == DIVCMD_GETVERSION)
162 return 0;
164 /* Network device index should reasonably be between 0 and 1000 :) */
165 if (div_cf->dev_index < 0 || div_cf->dev_index > 1000)
166 return -EINVAL;
168 /* Let's try to find the ifname */
169 sprintf(devname, "eth%d", div_cf->dev_index);
170 *dev = dev_get_by_name(devname);
172 /* dev should NOT be null */
173 if (*dev == NULL)
174 return -EINVAL;
176 ret = 0;
178 /* user issuing the ioctl must be a super one :) */
179 if (!capable(CAP_SYS_ADMIN)) {
180 ret = -EPERM;
181 goto out;
184 /* Device must have a divert_blk member NOT null */
185 if ((*dev)->divert == NULL)
186 ret = -EINVAL;
187 out:
188 dev_put(*dev);
189 return ret;
193 * control function of the diverter
195 #define DVDBG(a) \
196 printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a))
198 int divert_ioctl(unsigned int cmd, struct divert_cf __user *arg)
200 struct divert_cf div_cf;
201 struct divert_blk *div_blk;
202 struct net_device *dev;
203 int ret;
205 switch (cmd) {
206 case SIOCGIFDIVERT:
207 DVDBG("SIOCGIFDIVERT, copy_from_user");
208 if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
209 return -EFAULT;
210 DVDBG("before check_args");
211 ret = check_args(&div_cf, &dev);
212 if (ret)
213 return ret;
214 DVDBG("after checkargs");
215 div_blk = dev->divert;
217 DVDBG("befre switch()");
218 switch (div_cf.cmd) {
219 case DIVCMD_GETSTATUS:
220 /* Now, just give the user the raw divert block
221 * for him to play with :)
223 if (copy_to_user(div_cf.arg1.ptr, dev->divert,
224 sizeof(struct divert_blk)))
225 return -EFAULT;
226 break;
228 case DIVCMD_GETVERSION:
229 DVDBG("GETVERSION: checking ptr");
230 if (div_cf.arg1.ptr == NULL)
231 return -EINVAL;
232 DVDBG("GETVERSION: copying data to userland");
233 if (copy_to_user(div_cf.arg1.ptr,
234 sysctl_divert_version, 32))
235 return -EFAULT;
236 DVDBG("GETVERSION: data copied");
237 break;
239 default:
240 return -EINVAL;
243 break;
245 case SIOCSIFDIVERT:
246 if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
247 return -EFAULT;
249 ret = check_args(&div_cf, &dev);
250 if (ret)
251 return ret;
253 div_blk = dev->divert;
255 switch(div_cf.cmd) {
256 case DIVCMD_RESET:
257 div_blk->divert = 0;
258 div_blk->protos = DIVERT_PROTO_NONE;
259 memset(div_blk->tcp_dst, 0,
260 MAX_DIVERT_PORTS * sizeof(u16));
261 memset(div_blk->tcp_src, 0,
262 MAX_DIVERT_PORTS * sizeof(u16));
263 memset(div_blk->udp_dst, 0,
264 MAX_DIVERT_PORTS * sizeof(u16));
265 memset(div_blk->udp_src, 0,
266 MAX_DIVERT_PORTS * sizeof(u16));
267 return 0;
269 case DIVCMD_DIVERT:
270 switch(div_cf.arg1.int32) {
271 case DIVARG1_ENABLE:
272 if (div_blk->divert)
273 return -EALREADY;
274 div_blk->divert = 1;
275 break;
277 case DIVARG1_DISABLE:
278 if (!div_blk->divert)
279 return -EALREADY;
280 div_blk->divert = 0;
281 break;
283 default:
284 return -EINVAL;
287 break;
289 case DIVCMD_IP:
290 switch(div_cf.arg1.int32) {
291 case DIVARG1_ENABLE:
292 if (div_blk->protos & DIVERT_PROTO_IP)
293 return -EALREADY;
294 div_blk->protos |= DIVERT_PROTO_IP;
295 break;
297 case DIVARG1_DISABLE:
298 if (!(div_blk->protos & DIVERT_PROTO_IP))
299 return -EALREADY;
300 div_blk->protos &= ~DIVERT_PROTO_IP;
301 break;
303 default:
304 return -EINVAL;
307 break;
309 case DIVCMD_TCP:
310 switch(div_cf.arg1.int32) {
311 case DIVARG1_ENABLE:
312 if (div_blk->protos & DIVERT_PROTO_TCP)
313 return -EALREADY;
314 div_blk->protos |= DIVERT_PROTO_TCP;
315 break;
317 case DIVARG1_DISABLE:
318 if (!(div_blk->protos & DIVERT_PROTO_TCP))
319 return -EALREADY;
320 div_blk->protos &= ~DIVERT_PROTO_TCP;
321 break;
323 default:
324 return -EINVAL;
327 break;
329 case DIVCMD_TCPDST:
330 switch(div_cf.arg1.int32) {
331 case DIVARG1_ADD:
332 return add_port(div_blk->tcp_dst,
333 div_cf.arg2.uint16);
335 case DIVARG1_REMOVE:
336 return remove_port(div_blk->tcp_dst,
337 div_cf.arg2.uint16);
339 default:
340 return -EINVAL;
343 break;
345 case DIVCMD_TCPSRC:
346 switch(div_cf.arg1.int32) {
347 case DIVARG1_ADD:
348 return add_port(div_blk->tcp_src,
349 div_cf.arg2.uint16);
351 case DIVARG1_REMOVE:
352 return remove_port(div_blk->tcp_src,
353 div_cf.arg2.uint16);
355 default:
356 return -EINVAL;
359 break;
361 case DIVCMD_UDP:
362 switch(div_cf.arg1.int32) {
363 case DIVARG1_ENABLE:
364 if (div_blk->protos & DIVERT_PROTO_UDP)
365 return -EALREADY;
366 div_blk->protos |= DIVERT_PROTO_UDP;
367 break;
369 case DIVARG1_DISABLE:
370 if (!(div_blk->protos & DIVERT_PROTO_UDP))
371 return -EALREADY;
372 div_blk->protos &= ~DIVERT_PROTO_UDP;
373 break;
375 default:
376 return -EINVAL;
379 break;
381 case DIVCMD_UDPDST:
382 switch(div_cf.arg1.int32) {
383 case DIVARG1_ADD:
384 return add_port(div_blk->udp_dst,
385 div_cf.arg2.uint16);
387 case DIVARG1_REMOVE:
388 return remove_port(div_blk->udp_dst,
389 div_cf.arg2.uint16);
391 default:
392 return -EINVAL;
395 break;
397 case DIVCMD_UDPSRC:
398 switch(div_cf.arg1.int32) {
399 case DIVARG1_ADD:
400 return add_port(div_blk->udp_src,
401 div_cf.arg2.uint16);
403 case DIVARG1_REMOVE:
404 return remove_port(div_blk->udp_src,
405 div_cf.arg2.uint16);
407 default:
408 return -EINVAL;
411 break;
413 case DIVCMD_ICMP:
414 switch(div_cf.arg1.int32) {
415 case DIVARG1_ENABLE:
416 if (div_blk->protos & DIVERT_PROTO_ICMP)
417 return -EALREADY;
418 div_blk->protos |= DIVERT_PROTO_ICMP;
419 break;
421 case DIVARG1_DISABLE:
422 if (!(div_blk->protos & DIVERT_PROTO_ICMP))
423 return -EALREADY;
424 div_blk->protos &= ~DIVERT_PROTO_ICMP;
425 break;
427 default:
428 return -EINVAL;
431 break;
433 default:
434 return -EINVAL;
437 break;
439 default:
440 return -EINVAL;
443 return 0;
448 * Check if packet should have its dest mac address set to the box itself
449 * for diversion
452 #define ETH_DIVERT_FRAME(skb) \
453 memcpy(eth_hdr(skb), skb->dev->dev_addr, ETH_ALEN); \
454 skb->pkt_type=PACKET_HOST
456 void divert_frame(struct sk_buff *skb)
458 struct ethhdr *eth = eth_hdr(skb);
459 struct iphdr *iph;
460 struct tcphdr *tcph;
461 struct udphdr *udph;
462 struct divert_blk *divert = skb->dev->divert;
463 int i, src, dst;
464 unsigned char *skb_data_end = skb->data + skb->len;
466 /* Packet is already aimed at us, return */
467 if (!memcmp(eth, skb->dev->dev_addr, ETH_ALEN))
468 return;
470 /* proto is not IP, do nothing */
471 if (eth->h_proto != htons(ETH_P_IP))
472 return;
474 /* Divert all IP frames ? */
475 if (divert->protos & DIVERT_PROTO_IP) {
476 ETH_DIVERT_FRAME(skb);
477 return;
480 /* Check for possible (maliciously) malformed IP frame (thanks Dave) */
481 iph = (struct iphdr *) skb->data;
482 if (((iph->ihl<<2)+(unsigned char*)(iph)) >= skb_data_end) {
483 printk(KERN_INFO "divert: malformed IP packet !\n");
484 return;
487 switch (iph->protocol) {
488 /* Divert all ICMP frames ? */
489 case IPPROTO_ICMP:
490 if (divert->protos & DIVERT_PROTO_ICMP) {
491 ETH_DIVERT_FRAME(skb);
492 return;
494 break;
496 /* Divert all TCP frames ? */
497 case IPPROTO_TCP:
498 if (divert->protos & DIVERT_PROTO_TCP) {
499 ETH_DIVERT_FRAME(skb);
500 return;
503 /* Check for possible (maliciously) malformed IP
504 * frame (thanx Dave)
506 tcph = (struct tcphdr *)
507 (((unsigned char *)iph) + (iph->ihl<<2));
508 if (((unsigned char *)(tcph+1)) >= skb_data_end) {
509 printk(KERN_INFO "divert: malformed TCP packet !\n");
510 return;
513 /* Divert some tcp dst/src ports only ?*/
514 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
515 dst = divert->tcp_dst[i];
516 src = divert->tcp_src[i];
517 if ((dst && dst == tcph->dest) ||
518 (src && src == tcph->source)) {
519 ETH_DIVERT_FRAME(skb);
520 return;
523 break;
525 /* Divert all UDP frames ? */
526 case IPPROTO_UDP:
527 if (divert->protos & DIVERT_PROTO_UDP) {
528 ETH_DIVERT_FRAME(skb);
529 return;
532 /* Check for possible (maliciously) malformed IP
533 * packet (thanks Dave)
535 udph = (struct udphdr *)
536 (((unsigned char *)iph) + (iph->ihl<<2));
537 if (((unsigned char *)(udph+1)) >= skb_data_end) {
538 printk(KERN_INFO
539 "divert: malformed UDP packet !\n");
540 return;
543 /* Divert some udp dst/src ports only ? */
544 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
545 dst = divert->udp_dst[i];
546 src = divert->udp_src[i];
547 if ((dst && dst == udph->dest) ||
548 (src && src == udph->source)) {
549 ETH_DIVERT_FRAME(skb);
550 return;
553 break;