/* * 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; } }