2005-04-16 15:20:36 -07:00
/*
* 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>
2006-01-11 12:17:47 -08:00
# include <linux/capability.h>
2005-04-16 15:20:36 -07:00
# 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 */
2006-01-11 16:12:41 -08:00
if ( ! compare_ether_addr ( eth - > h_dest , skb - > dev - > dev_addr ) )
2005-04-16 15:20:36 -07:00
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 ;
}
}