Linux 2.6.19-rc6
[cris-mirror.git] / net / core / dv.c
blob29ee77f15932465115d64f7f5d01d395e69af6d6
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/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>
41 const char sysctl_divert_version[32]="0.46"; /* Current version */
43 static int __init dv_init(void)
45 return 0;
47 module_init(dv_init);
50 * Allocate a divert_blk for a device. This must be an ethernet nic.
52 int alloc_divert_blk(struct net_device *dev)
54 int alloc_size = (sizeof(struct divert_blk) + 3) & ~3;
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;
64 dev_hold(dev);
67 return 0;
71 * Free a divert_blk allocated by the above function, if it was
72 * allocated on that device.
74 void free_divert_blk(struct net_device *dev)
76 if (dev->divert) {
77 kfree(dev->divert);
78 dev->divert=NULL;
79 dev_put(dev);
84 * Adds a tcp/udp (source or dest) port to an array
86 static int add_port(u16 ports[], u16 port)
88 int i;
90 if (port == 0)
91 return -EINVAL;
93 /* Storing directly in network format for performance,
94 * thanks Dave :)
96 port = htons(port);
98 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
99 if (ports[i] == port)
100 return -EALREADY;
103 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
104 if (ports[i] == 0) {
105 ports[i] = port;
106 return 0;
110 return -ENOBUFS;
114 * Removes a port from an array tcp/udp (source or dest)
116 static int remove_port(u16 ports[], u16 port)
118 int i;
120 if (port == 0)
121 return -EINVAL;
123 /* Storing directly in network format for performance,
124 * thanks Dave !
126 port = htons(port);
128 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
129 if (ports[i] == port) {
130 ports[i] = 0;
131 return 0;
135 return -EINVAL;
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)
141 char devname[32];
142 int ret;
144 if (dev == NULL)
145 return -EFAULT;
147 /* GETVERSION: all other args are unused */
148 if (div_cf->cmd == DIVCMD_GETVERSION)
149 return 0;
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;
155 /* Let's try to find the ifname */
156 sprintf(devname, "eth%d", div_cf->dev_index);
157 *dev = dev_get_by_name(devname);
159 /* dev should NOT be null */
160 if (*dev == NULL)
161 return -EINVAL;
163 ret = 0;
165 /* user issuing the ioctl must be a super one :) */
166 if (!capable(CAP_SYS_ADMIN)) {
167 ret = -EPERM;
168 goto out;
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;
180 * control function of the diverter
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
189 int divert_ioctl(unsigned int cmd, struct divert_cf __user *arg)
191 struct divert_cf div_cf;
192 struct divert_blk *div_blk;
193 struct net_device *dev;
194 int ret;
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;
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 :)
214 if (copy_to_user(div_cf.arg1.ptr, dev->divert,
215 sizeof(struct divert_blk)))
216 return -EFAULT;
217 break;
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;
230 default:
231 return -EINVAL;
234 break;
236 case SIOCSIFDIVERT:
237 if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
238 return -EFAULT;
240 ret = check_args(&div_cf, &dev);
241 if (ret)
242 return ret;
244 div_blk = dev->divert;
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;
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;
268 case DIVARG1_DISABLE:
269 if (!div_blk->divert)
270 return -EALREADY;
271 div_blk->divert = 0;
272 break;
274 default:
275 return -EINVAL;
278 break;
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;
288 case DIVARG1_DISABLE:
289 if (!(div_blk->protos & DIVERT_PROTO_IP))
290 return -EALREADY;
291 div_blk->protos &= ~DIVERT_PROTO_IP;
292 break;
294 default:
295 return -EINVAL;
298 break;
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;
308 case DIVARG1_DISABLE:
309 if (!(div_blk->protos & DIVERT_PROTO_TCP))
310 return -EALREADY;
311 div_blk->protos &= ~DIVERT_PROTO_TCP;
312 break;
314 default:
315 return -EINVAL;
318 break;
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);
326 case DIVARG1_REMOVE:
327 return remove_port(div_blk->tcp_dst,
328 div_cf.arg2.uint16);
330 default:
331 return -EINVAL;
334 break;
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);
342 case DIVARG1_REMOVE:
343 return remove_port(div_blk->tcp_src,
344 div_cf.arg2.uint16);
346 default:
347 return -EINVAL;
350 break;
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;
360 case DIVARG1_DISABLE:
361 if (!(div_blk->protos & DIVERT_PROTO_UDP))
362 return -EALREADY;
363 div_blk->protos &= ~DIVERT_PROTO_UDP;
364 break;
366 default:
367 return -EINVAL;
370 break;
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);
378 case DIVARG1_REMOVE:
379 return remove_port(div_blk->udp_dst,
380 div_cf.arg2.uint16);
382 default:
383 return -EINVAL;
386 break;
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);
394 case DIVARG1_REMOVE:
395 return remove_port(div_blk->udp_src,
396 div_cf.arg2.uint16);
398 default:
399 return -EINVAL;
402 break;
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;
412 case DIVARG1_DISABLE:
413 if (!(div_blk->protos & DIVERT_PROTO_ICMP))
414 return -EALREADY;
415 div_blk->protos &= ~DIVERT_PROTO_ICMP;
416 break;
418 default:
419 return -EINVAL;
422 break;
424 default:
425 return -EINVAL;
428 break;
430 default:
431 return -EINVAL;
434 return 0;
439 * Check if packet should have its dest mac address set to the box itself
440 * for diversion
443 #define ETH_DIVERT_FRAME(skb) \
444 memcpy(eth_hdr(skb), skb->dev->dev_addr, ETH_ALEN); \
445 skb->pkt_type=PACKET_HOST
447 void divert_frame(struct sk_buff *skb)
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;
457 /* Packet is already aimed at us, return */
458 if (!compare_ether_addr(eth->h_dest, skb->dev->dev_addr))
459 return;
461 /* proto is not IP, do nothing */
462 if (eth->h_proto != htons(ETH_P_IP))
463 return;
465 /* Divert all IP frames ? */
466 if (divert->protos & DIVERT_PROTO_IP) {
467 ETH_DIVERT_FRAME(skb);
468 return;
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;
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;
485 break;
487 /* Divert all TCP frames ? */
488 case IPPROTO_TCP:
489 if (divert->protos & DIVERT_PROTO_TCP) {
490 ETH_DIVERT_FRAME(skb);
491 return;
494 /* Check for possible (maliciously) malformed IP
495 * frame (thanx Dave)
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;
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;
514 break;
516 /* Divert all UDP frames ? */
517 case IPPROTO_UDP:
518 if (divert->protos & DIVERT_PROTO_UDP) {
519 ETH_DIVERT_FRAME(skb);
520 return;
523 /* Check for possible (maliciously) malformed IP
524 * packet (thanks Dave)
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;
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;
544 break;