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
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>
19 #include <linux/socket.h>
21 #include <linux/inet.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>
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)
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",
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",
66 memset(dev
->divert
, 0, sizeof(struct divert_blk
));
70 printk(KERN_DEBUG
"divert: not allocating divert_blk for non-ethernet device %s\n",
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
)
88 printk(KERN_DEBUG
"divert: freeing divert_blk for %s\n",
91 printk(KERN_DEBUG
"divert: no divert_blk to free, %s not ethernet\n",
97 * Adds a tcp/udp (source or dest) port to an array
99 static int add_port(u16 ports
[], u16 port
)
106 /* Storing directly in network format for performance,
111 for (i
= 0; i
< MAX_DIVERT_PORTS
; i
++) {
112 if (ports
[i
] == port
)
116 for (i
= 0; i
< MAX_DIVERT_PORTS
; i
++) {
127 * Removes a port from an array tcp/udp (source or dest)
129 static int remove_port(u16 ports
[], u16 port
)
136 /* Storing directly in network format for performance,
141 for (i
= 0; i
< MAX_DIVERT_PORTS
; i
++) {
142 if (ports
[i
] == port
) {
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
)
160 /* GETVERSION: all other args are unused */
161 if (div_cf
->cmd
== DIVCMD_GETVERSION
)
164 /* Network device index should reasonably be between 0 and 1000 :) */
165 if (div_cf
->dev_index
< 0 || div_cf
->dev_index
> 1000)
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 */
178 /* user issuing the ioctl must be a super one :) */
179 if (!capable(CAP_SYS_ADMIN
)) {
184 /* Device must have a divert_blk member NOT null */
185 if ((*dev
)->divert
== NULL
)
193 * control function of the diverter
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
;
207 DVDBG("SIOCGIFDIVERT, copy_from_user");
208 if (copy_from_user(&div_cf
, arg
, sizeof(struct divert_cf
)))
210 DVDBG("before check_args");
211 ret
= check_args(&div_cf
, &dev
);
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
)))
228 case DIVCMD_GETVERSION
:
229 DVDBG("GETVERSION: checking ptr");
230 if (div_cf
.arg1
.ptr
== NULL
)
232 DVDBG("GETVERSION: copying data to userland");
233 if (copy_to_user(div_cf
.arg1
.ptr
,
234 sysctl_divert_version
, 32))
236 DVDBG("GETVERSION: data copied");
246 if (copy_from_user(&div_cf
, arg
, sizeof(struct divert_cf
)))
249 ret
= check_args(&div_cf
, &dev
);
253 div_blk
= dev
->divert
;
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
));
270 switch(div_cf
.arg1
.int32
) {
277 case DIVARG1_DISABLE
:
278 if (!div_blk
->divert
)
290 switch(div_cf
.arg1
.int32
) {
292 if (div_blk
->protos
& DIVERT_PROTO_IP
)
294 div_blk
->protos
|= DIVERT_PROTO_IP
;
297 case DIVARG1_DISABLE
:
298 if (!(div_blk
->protos
& DIVERT_PROTO_IP
))
300 div_blk
->protos
&= ~DIVERT_PROTO_IP
;
310 switch(div_cf
.arg1
.int32
) {
312 if (div_blk
->protos
& DIVERT_PROTO_TCP
)
314 div_blk
->protos
|= DIVERT_PROTO_TCP
;
317 case DIVARG1_DISABLE
:
318 if (!(div_blk
->protos
& DIVERT_PROTO_TCP
))
320 div_blk
->protos
&= ~DIVERT_PROTO_TCP
;
330 switch(div_cf
.arg1
.int32
) {
332 return add_port(div_blk
->tcp_dst
,
336 return remove_port(div_blk
->tcp_dst
,
346 switch(div_cf
.arg1
.int32
) {
348 return add_port(div_blk
->tcp_src
,
352 return remove_port(div_blk
->tcp_src
,
362 switch(div_cf
.arg1
.int32
) {
364 if (div_blk
->protos
& DIVERT_PROTO_UDP
)
366 div_blk
->protos
|= DIVERT_PROTO_UDP
;
369 case DIVARG1_DISABLE
:
370 if (!(div_blk
->protos
& DIVERT_PROTO_UDP
))
372 div_blk
->protos
&= ~DIVERT_PROTO_UDP
;
382 switch(div_cf
.arg1
.int32
) {
384 return add_port(div_blk
->udp_dst
,
388 return remove_port(div_blk
->udp_dst
,
398 switch(div_cf
.arg1
.int32
) {
400 return add_port(div_blk
->udp_src
,
404 return remove_port(div_blk
->udp_src
,
414 switch(div_cf
.arg1
.int32
) {
416 if (div_blk
->protos
& DIVERT_PROTO_ICMP
)
418 div_blk
->protos
|= DIVERT_PROTO_ICMP
;
421 case DIVARG1_DISABLE
:
422 if (!(div_blk
->protos
& DIVERT_PROTO_ICMP
))
424 div_blk
->protos
&= ~DIVERT_PROTO_ICMP
;
448 * Check if packet should have its dest mac address set to the box itself
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
);
462 struct divert_blk
*divert
= skb
->dev
->divert
;
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
))
470 /* proto is not IP, do nothing */
471 if (eth
->h_proto
!= htons(ETH_P_IP
))
474 /* Divert all IP frames ? */
475 if (divert
->protos
& DIVERT_PROTO_IP
) {
476 ETH_DIVERT_FRAME(skb
);
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");
487 switch (iph
->protocol
) {
488 /* Divert all ICMP frames ? */
490 if (divert
->protos
& DIVERT_PROTO_ICMP
) {
491 ETH_DIVERT_FRAME(skb
);
496 /* Divert all TCP frames ? */
498 if (divert
->protos
& DIVERT_PROTO_TCP
) {
499 ETH_DIVERT_FRAME(skb
);
503 /* Check for possible (maliciously) malformed IP
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");
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
);
525 /* Divert all UDP frames ? */
527 if (divert
->protos
& DIVERT_PROTO_UDP
) {
528 ETH_DIVERT_FRAME(skb
);
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
) {
539 "divert: malformed UDP packet !\n");
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
);