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 (!strncmp(dev->name, "eth", 3)) {
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 } else {
70 printk(KERN_DEBUG "divert: not allocating divert_blk for non-ethernet device %s\n",
71 dev->name);
72
73 dev->divert = NULL;
74 }
75 return 0;
76 }
77
78 /*
79 * Free a divert_blk allocated by the above function, if it was
80 * allocated on that device.
81 */
82 void free_divert_blk(struct net_device *dev)
83 {
84 if (dev->divert) {
85 kfree(dev->divert);
86 dev->divert=NULL;
87 printk(KERN_DEBUG "divert: freeing divert_blk for %s\n",
88 dev->name);
89 } else {
90 printk(KERN_DEBUG "divert: no divert_blk to free, %s not ethernet\n",
91 dev->name);
92 }
93 }
94
95 /*
96 * Adds a tcp/udp (source or dest) port to an array
97 */
98 int add_port(u16 ports[], u16 port)
99 {
100 int i;
101
102 if (port == 0)
103 return -EINVAL;
104
105 /* Storing directly in network format for performance,
106 * thanks Dave :)
107 */
108 port = htons(port);
109
110 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
111 if (ports[i] == port)
112 return -EALREADY;
113 }
114
115 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
116 if (ports[i] == 0) {
117 ports[i] = port;
118 return 0;
119 }
120 }
121
122 return -ENOBUFS;
123 }
124
125 /*
126 * Removes a port from an array tcp/udp (source or dest)
127 */
128 int remove_port(u16 ports[], u16 port)
129 {
130 int i;
131
132 if (port == 0)
133 return -EINVAL;
134
135 /* Storing directly in network format for performance,
136 * thanks Dave !
137 */
138 port = htons(port);
139
140 for (i = 0; i < MAX_DIVERT_PORTS; i++) {
141 if (ports[i] == port) {
142 ports[i] = 0;
143 return 0;
144 }
145 }
146
147 return -EINVAL;
148 }
149
150 /* Some basic sanity checks on the arguments passed to divert_ioctl() */
151 int check_args(struct divert_cf *div_cf, struct net_device **dev)
152 {
153 char devname[32];
154
155 if (dev == NULL)
156 return -EFAULT;
157
158 /* GETVERSION: all other args are unused */
159 if (div_cf->cmd == DIVCMD_GETVERSION)
160 return 0;
161
162 /* Network device index should reasonably be between 0 and 1000 :) */
163 if (div_cf->dev_index < 0 || div_cf->dev_index > 1000)
164 return -EINVAL;
165
166 /* Let's try to find the ifname */
167 sprintf(devname, "eth%d", div_cf->dev_index);
168 *dev = dev_get_by_name(devname);
169
170 /* dev should NOT be null */
171 if (*dev == NULL)
172 return -EINVAL;
173
174 /* user issuing the ioctl must be a super one :) */
175 if (!suser())
176 return -EPERM;
177
178 /* Device must have a divert_blk member NOT null */
179 if ((*dev)->divert == NULL)
180 return -EFAULT;
181
182 return 0;
183 }
184
185 /*
186 * control function of the diverter
187 */
188 #define DVDBG(a) \
189 printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a))
190
191 int divert_ioctl(unsigned int cmd, struct divert_cf *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(skb->mac.ethernet, 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 = skb->mac.ethernet;
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 return;
550 }
551
552
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.