| /* | |
| * INET An implementation of the TCP/IP protocol suite for the LINUX | |
| * operating system. INET is implemented using the BSD Socket | |
| * interface as the means of communication with the user level. | |
| * | |
| * Generic frame diversion | |
| * | |
| * Authors: | |
| * Benoit LOCHER: initial integration within the kernel with support for ethernet | |
| * Dave Miller: improvement on the code (correctness, performance and source files) | |
| * | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/types.h> | |
| #include <linux/kernel.h> | |
| #include <linux/sched.h> | |
| #include <linux/string.h> | |
| #include <linux/mm.h> | |
| #include <linux/socket.h> | |
| #include <linux/in.h> | |
| #include <linux/inet.h> | |
| #include <linux/ip.h> | |
| #include <linux/udp.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/etherdevice.h> | |
| #include <linux/skbuff.h> | |
| #include <linux/errno.h> | |
| #include <linux/init.h> | |
| #include <net/dst.h> | |
| #include <net/arp.h> | |
| #include <net/sock.h> | |
| #include <net/ipv6.h> | |
| #include <net/ip.h> | |
| #include <asm/uaccess.h> | |
| #include <asm/system.h> | |
| #include <asm/checksum.h> | |
| #include <linux/divert.h> | |
| #include <linux/sockios.h> | |
| const char sysctl_divert_version[32]="0.46"; /* Current version */ | |
| static int __init dv_init(void) | |
| { | |
| return 0; | |
| } | |
| module_init(dv_init); | |
| /* | |
| * Allocate a divert_blk for a device. This must be an ethernet nic. | |
| */ | |
| int alloc_divert_blk(struct net_device *dev) | |
| { | |
| int alloc_size = (sizeof(struct divert_blk) + 3) & ~3; | |
| dev->divert = NULL; | |
| if (dev->type == ARPHRD_ETHER) { | |
| dev->divert = (struct divert_blk *) | |
| kmalloc(alloc_size, GFP_KERNEL); | |
| if (dev->divert == NULL) { | |
| printk(KERN_INFO "divert: unable to allocate divert_blk for %s\n", | |
| dev->name); | |
| return -ENOMEM; | |
| } | |
| memset(dev->divert, 0, sizeof(struct divert_blk)); | |
| dev_hold(dev); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Free a divert_blk allocated by the above function, if it was | |
| * allocated on that device. | |
| */ | |
| void free_divert_blk(struct net_device *dev) | |
| { | |
| if (dev->divert) { | |
| kfree(dev->divert); | |
| dev->divert=NULL; | |
| dev_put(dev); | |
| } | |
| } | |
| /* | |
| * Adds a tcp/udp (source or dest) port to an array | |
| */ | |
| static int add_port(u16 ports[], u16 port) | |
| { | |
| int i; | |
| if (port == 0) | |
| return -EINVAL; | |
| /* Storing directly in network format for performance, | |
| * thanks Dave :) | |
| */ | |
| port = htons(port); | |
| for (i = 0; i < MAX_DIVERT_PORTS; i++) { | |
| if (ports[i] == port) | |
| return -EALREADY; | |
| } | |
| for (i = 0; i < MAX_DIVERT_PORTS; i++) { | |
| if (ports[i] == 0) { | |
| ports[i] = port; | |
| return 0; | |
| } | |
| } | |
| return -ENOBUFS; | |
| } | |
| /* | |
| * Removes a port from an array tcp/udp (source or dest) | |
| */ | |
| static int remove_port(u16 ports[], u16 port) | |
| { | |
| int i; | |
| if (port == 0) | |
| return -EINVAL; | |
| /* Storing directly in network format for performance, | |
| * thanks Dave ! | |
| */ | |
| port = htons(port); | |
| for (i = 0; i < MAX_DIVERT_PORTS; i++) { | |
| if (ports[i] == port) { | |
| ports[i] = 0; | |
| return 0; | |
| } | |
| } | |
| return -EINVAL; | |
| } | |
| /* Some basic sanity checks on the arguments passed to divert_ioctl() */ | |
| static int check_args(struct divert_cf *div_cf, struct net_device **dev) | |
| { | |
| char devname[32]; | |
| int ret; | |
| if (dev == NULL) | |
| return -EFAULT; | |
| /* GETVERSION: all other args are unused */ | |
| if (div_cf->cmd == DIVCMD_GETVERSION) | |
| return 0; | |
| /* Network device index should reasonably be between 0 and 1000 :) */ | |
| if (div_cf->dev_index < 0 || div_cf->dev_index > 1000) | |
| return -EINVAL; | |
| /* Let's try to find the ifname */ | |
| sprintf(devname, "eth%d", div_cf->dev_index); | |
| *dev = dev_get_by_name(devname); | |
| /* dev should NOT be null */ | |
| if (*dev == NULL) | |
| return -EINVAL; | |
| ret = 0; | |
| /* user issuing the ioctl must be a super one :) */ | |
| if (!capable(CAP_SYS_ADMIN)) { | |
| ret = -EPERM; | |
| goto out; | |
| } | |
| /* Device must have a divert_blk member NOT null */ | |
| if ((*dev)->divert == NULL) | |
| ret = -EINVAL; | |
| out: | |
| dev_put(*dev); | |
| return ret; | |
| } | |
| /* | |
| * control function of the diverter | |
| */ | |
| #if 0 | |
| #define DVDBG(a) \ | |
| printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a)) | |
| #else | |
| #define DVDBG(a) | |
| #endif | |
| int divert_ioctl(unsigned int cmd, struct divert_cf __user *arg) | |
| { | |
| struct divert_cf div_cf; | |
| struct divert_blk *div_blk; | |
| struct net_device *dev; | |
| int ret; | |
| switch (cmd) { | |
| case SIOCGIFDIVERT: | |
| DVDBG("SIOCGIFDIVERT, copy_from_user"); | |
| if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf))) | |
| return -EFAULT; | |
| DVDBG("before check_args"); | |
| ret = check_args(&div_cf, &dev); | |
| if (ret) | |
| return ret; | |
| DVDBG("after checkargs"); | |
| div_blk = dev->divert; | |
| DVDBG("befre switch()"); | |
| switch (div_cf.cmd) { | |
| case DIVCMD_GETSTATUS: | |
| /* Now, just give the user the raw divert block | |
| * for him to play with :) | |
| */ | |
| if (copy_to_user(div_cf.arg1.ptr, dev->divert, | |
| sizeof(struct divert_blk))) | |
| return -EFAULT; | |
| break; | |
| case DIVCMD_GETVERSION: | |
| DVDBG("GETVERSION: checking ptr"); | |
| if (div_cf.arg1.ptr == NULL) | |
| return -EINVAL; | |
| DVDBG("GETVERSION: copying data to userland"); | |
| if (copy_to_user(div_cf.arg1.ptr, | |
| sysctl_divert_version, 32)) | |
| return -EFAULT; | |
| DVDBG("GETVERSION: data copied"); | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case SIOCSIFDIVERT: | |
| if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf))) | |
| return -EFAULT; | |
| ret = check_args(&div_cf, &dev); | |
| if (ret) | |
| return ret; | |
| div_blk = dev->divert; | |
| switch(div_cf.cmd) { | |
| case DIVCMD_RESET: | |
| div_blk->divert = 0; | |
| div_blk->protos = DIVERT_PROTO_NONE; | |
| memset(div_blk->tcp_dst, 0, | |
| MAX_DIVERT_PORTS * sizeof(u16)); | |
| memset(div_blk->tcp_src, 0, | |
| MAX_DIVERT_PORTS * sizeof(u16)); | |
| memset(div_blk->udp_dst, 0, | |
| MAX_DIVERT_PORTS * sizeof(u16)); | |
| memset(div_blk->udp_src, 0, | |
| MAX_DIVERT_PORTS * sizeof(u16)); | |
| return 0; | |
| case DIVCMD_DIVERT: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ENABLE: | |
| if (div_blk->divert) | |
| return -EALREADY; | |
| div_blk->divert = 1; | |
| break; | |
| case DIVARG1_DISABLE: | |
| if (!div_blk->divert) | |
| return -EALREADY; | |
| div_blk->divert = 0; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_IP: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ENABLE: | |
| if (div_blk->protos & DIVERT_PROTO_IP) | |
| return -EALREADY; | |
| div_blk->protos |= DIVERT_PROTO_IP; | |
| break; | |
| case DIVARG1_DISABLE: | |
| if (!(div_blk->protos & DIVERT_PROTO_IP)) | |
| return -EALREADY; | |
| div_blk->protos &= ~DIVERT_PROTO_IP; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_TCP: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ENABLE: | |
| if (div_blk->protos & DIVERT_PROTO_TCP) | |
| return -EALREADY; | |
| div_blk->protos |= DIVERT_PROTO_TCP; | |
| break; | |
| case DIVARG1_DISABLE: | |
| if (!(div_blk->protos & DIVERT_PROTO_TCP)) | |
| return -EALREADY; | |
| div_blk->protos &= ~DIVERT_PROTO_TCP; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_TCPDST: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ADD: | |
| return add_port(div_blk->tcp_dst, | |
| div_cf.arg2.uint16); | |
| case DIVARG1_REMOVE: | |
| return remove_port(div_blk->tcp_dst, | |
| div_cf.arg2.uint16); | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_TCPSRC: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ADD: | |
| return add_port(div_blk->tcp_src, | |
| div_cf.arg2.uint16); | |
| case DIVARG1_REMOVE: | |
| return remove_port(div_blk->tcp_src, | |
| div_cf.arg2.uint16); | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_UDP: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ENABLE: | |
| if (div_blk->protos & DIVERT_PROTO_UDP) | |
| return -EALREADY; | |
| div_blk->protos |= DIVERT_PROTO_UDP; | |
| break; | |
| case DIVARG1_DISABLE: | |
| if (!(div_blk->protos & DIVERT_PROTO_UDP)) | |
| return -EALREADY; | |
| div_blk->protos &= ~DIVERT_PROTO_UDP; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_UDPDST: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ADD: | |
| return add_port(div_blk->udp_dst, | |
| div_cf.arg2.uint16); | |
| case DIVARG1_REMOVE: | |
| return remove_port(div_blk->udp_dst, | |
| div_cf.arg2.uint16); | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_UDPSRC: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ADD: | |
| return add_port(div_blk->udp_src, | |
| div_cf.arg2.uint16); | |
| case DIVARG1_REMOVE: | |
| return remove_port(div_blk->udp_src, | |
| div_cf.arg2.uint16); | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| case DIVCMD_ICMP: | |
| switch(div_cf.arg1.int32) { | |
| case DIVARG1_ENABLE: | |
| if (div_blk->protos & DIVERT_PROTO_ICMP) | |
| return -EALREADY; | |
| div_blk->protos |= DIVERT_PROTO_ICMP; | |
| break; | |
| case DIVARG1_DISABLE: | |
| if (!(div_blk->protos & DIVERT_PROTO_ICMP)) | |
| return -EALREADY; | |
| div_blk->protos &= ~DIVERT_PROTO_ICMP; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Check if packet should have its dest mac address set to the box itself | |
| * for diversion | |
| */ | |
| #define ETH_DIVERT_FRAME(skb) \ | |
| memcpy(eth_hdr(skb), skb->dev->dev_addr, ETH_ALEN); \ | |
| skb->pkt_type=PACKET_HOST | |
| void divert_frame(struct sk_buff *skb) | |
| { | |
| struct ethhdr *eth = eth_hdr(skb); | |
| struct iphdr *iph; | |
| struct tcphdr *tcph; | |
| struct udphdr *udph; | |
| struct divert_blk *divert = skb->dev->divert; | |
| int i, src, dst; | |
| unsigned char *skb_data_end = skb->data + skb->len; | |
| /* Packet is already aimed at us, return */ | |
| if (!memcmp(eth, skb->dev->dev_addr, ETH_ALEN)) | |
| return; | |
| /* proto is not IP, do nothing */ | |
| if (eth->h_proto != htons(ETH_P_IP)) | |
| return; | |
| /* Divert all IP frames ? */ | |
| if (divert->protos & DIVERT_PROTO_IP) { | |
| ETH_DIVERT_FRAME(skb); | |
| return; | |
| } | |
| /* Check for possible (maliciously) malformed IP frame (thanks Dave) */ | |
| iph = (struct iphdr *) skb->data; | |
| if (((iph->ihl<<2)+(unsigned char*)(iph)) >= skb_data_end) { | |
| printk(KERN_INFO "divert: malformed IP packet !\n"); | |
| return; | |
| } | |
| switch (iph->protocol) { | |
| /* Divert all ICMP frames ? */ | |
| case IPPROTO_ICMP: | |
| if (divert->protos & DIVERT_PROTO_ICMP) { | |
| ETH_DIVERT_FRAME(skb); | |
| return; | |
| } | |
| break; | |
| /* Divert all TCP frames ? */ | |
| case IPPROTO_TCP: | |
| if (divert->protos & DIVERT_PROTO_TCP) { | |
| ETH_DIVERT_FRAME(skb); | |
| return; | |
| } | |
| /* Check for possible (maliciously) malformed IP | |
| * frame (thanx Dave) | |
| */ | |
| tcph = (struct tcphdr *) | |
| (((unsigned char *)iph) + (iph->ihl<<2)); | |
| if (((unsigned char *)(tcph+1)) >= skb_data_end) { | |
| printk(KERN_INFO "divert: malformed TCP packet !\n"); | |
| return; | |
| } | |
| /* Divert some tcp dst/src ports only ?*/ | |
| for (i = 0; i < MAX_DIVERT_PORTS; i++) { | |
| dst = divert->tcp_dst[i]; | |
| src = divert->tcp_src[i]; | |
| if ((dst && dst == tcph->dest) || | |
| (src && src == tcph->source)) { | |
| ETH_DIVERT_FRAME(skb); | |
| return; | |
| } | |
| } | |
| break; | |
| /* Divert all UDP frames ? */ | |
| case IPPROTO_UDP: | |
| if (divert->protos & DIVERT_PROTO_UDP) { | |
| ETH_DIVERT_FRAME(skb); | |
| return; | |
| } | |
| /* Check for possible (maliciously) malformed IP | |
| * packet (thanks Dave) | |
| */ | |
| udph = (struct udphdr *) | |
| (((unsigned char *)iph) + (iph->ihl<<2)); | |
| if (((unsigned char *)(udph+1)) >= skb_data_end) { | |
| printk(KERN_INFO | |
| "divert: malformed UDP packet !\n"); | |
| return; | |
| } | |
| /* Divert some udp dst/src ports only ? */ | |
| for (i = 0; i < MAX_DIVERT_PORTS; i++) { | |
| dst = divert->udp_dst[i]; | |
| src = divert->udp_src[i]; | |
| if ((dst && dst == udph->dest) || | |
| (src && src == udph->source)) { | |
| ETH_DIVERT_FRAME(skb); | |
| return; | |
| } | |
| } | |
| break; | |
| } | |
| } |