diff -Naur linux-2.4.9/Documentation/Configure.help linux-2.4.9-dv/Documentation/Configure.help --- linux-2.4.9/Documentation/Configure.help Sun Aug 12 17:51:41 2001 +++ linux-2.4.9-dv/Documentation/Configure.help Sun Sep 23 15:55:21 2001 @@ -4899,19 +4899,21 @@ with the Frames Diverter on, can do some *really* transparent www caching using a Squid proxy for example. - This is very useful when you don't want to change your router's - config (or if you simply don't have access to it). + It also allows diversion of selected frames towards another machine + on the LAN, through the use of the ethernet bridge. + This is very usefull when you don't want to change your router's + config (or if you simply don't have access to it). + The other possible usages of diverting Ethernet Frames are numberous: - reroute smtp traffic to another interface - traffic-shape certain network streams - transparently proxy smtp connections - etc... - + For more informations, please refer to: - http://www.freshmeat.net/projects/etherdivert - http://perso.wanadoo.fr/magpie/EtherDivert.html - + http://diverter.sourceforge.net + If unsure, say N 802.1d Ethernet Bridging diff -Naur linux-2.4.9/MAINTAINERS linux-2.4.9-dv/MAINTAINERS --- linux-2.4.9/MAINTAINERS Mon Aug 13 00:36:24 2001 +++ linux-2.4.9-dv/MAINTAINERS Mon Sep 24 00:36:54 2001 @@ -525,6 +525,12 @@ W: http://suburbia.net/~billm/floating-point/emulator/ S: Maintained +FRAME DIVERTER +P: Benoit Locher +M: Benoit.Locher@skf.com +S: Maintained +W: http://diverter.sourceforge.net + FRAME RELAY DLCI/FRAD (Sangoma drivers too) P: Mike McLagan M: mike.mclagan@linux.org diff -Naur linux-2.4.9/drivers/net/Space.c linux-2.4.9-dv/drivers/net/Space.c --- linux-2.4.9/drivers/net/Space.c Wed Jul 18 01:53:55 2001 +++ linux-2.4.9-dv/drivers/net/Space.c Sun Sep 23 15:52:20 2001 @@ -136,25 +136,31 @@ { struct devprobe *p = plist; unsigned long base_addr = dev->base_addr; -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) int ret; #endif /* CONFIG_NET_DIVERT */ while (p->probe != NULL) { if (base_addr && p->probe(dev) == 0) { /* probe given addr */ -#ifdef CONFIG_NET_DIVERT - ret = alloc_divert_blk(dev); - if (ret) - return ret; +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) + if (alloc_divert_blk_hook) + { + ret = alloc_divert_blk_hook(dev); + if (ret) + return ret; + } #endif /* CONFIG_NET_DIVERT */ return 0; } else if (p->status == 0) { /* has autoprobe failed yet? */ p->status = p->probe(dev); /* no, try autoprobe */ if (p->status == 0) { -#ifdef CONFIG_NET_DIVERT - ret = alloc_divert_blk(dev); - if (ret) - return ret; +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) + if (alloc_divert_blk_hook) + { + ret = alloc_divert_blk_hook(dev); + if (ret) + return ret; + } #endif /* CONFIG_NET_DIVERT */ return 0; } diff -Naur linux-2.4.9/include/linux/divert.h linux-2.4.9-dv/include/linux/divert.h --- linux-2.4.9/include/linux/divert.h Wed Aug 15 21:22:45 2001 +++ linux-2.4.9-dv/include/linux/divert.h Sun Sep 23 21:44:12 2001 @@ -3,115 +3,225 @@ * * Changes: * 06/09/2000 BL: initial version - * + * 15/10/2000 BL: changed to a rule based structure */ #ifndef _LINUX_DIVERT_H #define _LINUX_DIVERT_H -#include +#include +#include -#define MAX_DIVERT_PORTS 8 /* Max number of ports to divert (tcp, udp) */ +//#ifdef __KERNEL__ +//#include +//#endif /* __KERNEL__ */ + + +struct sk_buff; +struct divert_rule; +typedef int (*process_rule_func)(struct sk_buff *, struct divert_rule *); + + +enum DIVERT_PROTO +{ + DIVERT_PROTO_IP=10, + DIVERT_PROTO_TCP=11, + DIVERT_PROTO_UDP=12, + DIVERT_PROTO_ICMP=13, + DIVERT_PROTO_ARP=20, + DIVERT_PROTO_IPX=30, +}; + + +typedef struct _divert_cidr +{ + unsigned long ipnum, + ipmask; +} divert_cidr; + + +typedef struct _divert_icmp_blk +{ + unsigned long scidr_negate; + divert_cidr scidr; + unsigned long dcidr_negate; + divert_cidr dcidr; + unsigned short type_negate, + type; +} divert_icmp_blk; + + +typedef struct _divert_tcp_blk +{ + unsigned long scidr_negate; + divert_cidr scidr; + unsigned long dcidr_negate; + divert_cidr dcidr; + unsigned short sport_negate, + sport_start, + sport_end, + dport_negate, + dport_start, + dport_end; +} divert_tcp_blk; + + +typedef struct _divert_udp_blk +{ + unsigned long scidr_negate; + divert_cidr scidr; + unsigned long dcidr_negate; + divert_cidr dcidr; + unsigned short sport_negate, + sport_start, + sport_end, + dport_negate, + dport_start, + dport_end; +} divert_udp_blk; + + +typedef struct _divert_ip_blk +{ + unsigned long scidr_negate; + divert_cidr scidr; + unsigned long dcidr_negate; + divert_cidr dcidr; +} divert_ip_blk; + + +typedef struct _divert_arp_blk +{ + +} divert_arp_blk; + + +typedef struct _divert_ipx_blk +{ + +} divert_ipx_blk; + + +typedef struct _divert_spx_blk +{ + +} divert_spx_blk; + + +typedef union _divert_proto +{ + divert_ip_blk ip; + divert_tcp_blk tcp; + divert_udp_blk udp; + divert_icmp_blk icmp; + divert_arp_blk arp; + divert_ipx_blk ipx; + divert_spx_blk spx; +} divert_proto; + + +#ifdef __KERNEL__ + +struct divert_rule +{ + struct divert_rule *prev, + *next; + unsigned long proto_num; + divert_proto proto_blk; + int divert_locally; + unsigned char dest_mac[ETH_ALEN]; + process_rule_func process_rule; +}; -/* Divertable protocols */ -#define DIVERT_PROTO_NONE 0x0000 -#define DIVERT_PROTO_IP 0x0001 -#define DIVERT_PROTO_ICMP 0x0002 -#define DIVERT_PROTO_TCP 0x0004 -#define DIVERT_PROTO_UDP 0x0008 -/* - * This is an Ethernet Frame Diverter option block - */ struct divert_blk { - int divert; /* are we active */ - unsigned int protos; /* protocols */ - u16 tcp_dst[MAX_DIVERT_PORTS]; /* specific tcp dst ports to divert */ - u16 tcp_src[MAX_DIVERT_PORTS]; /* specific tcp src ports to divert */ - u16 udp_dst[MAX_DIVERT_PORTS]; /* specific udp dst ports to divert */ - u16 udp_src[MAX_DIVERT_PORTS]; /* specific udp src ports to divert */ + int divert; /* diverter status on|off */ + rwlock_t rwlock; /* for rule list interprocess locking */ + struct divert_rule *rule; /* the rule doubly-linked list */ }; +#endif /* __KERNEL__ */ + /* * Diversion control block, for configuration with the userspace tool * divert */ -typedef union _divert_cf_arg -{ - s16 int16; - u16 uint16; - s32 int32; - u32 uint32; - s64 int64; - u64 uint64; - void *ptr; -} divert_cf_arg; - - struct divert_cf { - int cmd; /* Command */ - divert_cf_arg arg1, - arg2, - arg3; - int dev_index; /* device index (eth0=0, etc...) */ + int cmd; /* Command */ + unsigned int rule_num; /* Used to retrieve a specific rule */ + unsigned int proto_num; /* Protocol this rule works on */ + divert_proto proto_blk; /* Rule protocol description */ + int status; /* Used to get the status of the diverter */ + int divert_locally; /* acts locally to the specified interface */ + unsigned char dest_mac[ETH_ALEN]; /* If not divert_locally, mac address it should + be forwarded to */ + char dev_name[32]; /* Device name (eth0...) */ }; /* Diversion commands */ -#define DIVCMD_DIVERT 1 /* ENABLE/DISABLE diversion */ -#define DIVCMD_IP 2 /* ENABLE/DISABLE whold IP diversion */ -#define DIVCMD_TCP 3 /* ENABLE/DISABLE whold TCP diversion */ -#define DIVCMD_TCPDST 4 /* ADD/REMOVE TCP DST port for diversion */ -#define DIVCMD_TCPSRC 5 /* ADD/REMOVE TCP SRC port for diversion */ -#define DIVCMD_UDP 6 /* ENABLE/DISABLE whole UDP diversion */ -#define DIVCMD_UDPDST 7 /* ADD/REMOVE UDP DST port for diversion */ -#define DIVCMD_UDPSRC 8 /* ADD/REMOVE UDP SRC port for diversion */ -#define DIVCMD_ICMP 9 /* ENABLE/DISABLE whole ICMP diversion */ -#define DIVCMD_GETSTATUS 10 /* GET the status of the diverter */ -#define DIVCMD_RESET 11 /* Reset the diverter on the specified dev */ -#define DIVCMD_GETVERSION 12 /* Retrieve the diverter code version (char[32]) */ - -/* General syntax of the commands: - * - * DIVCMD_xxxxxx(arg1, arg2, arg3, dev_index) - * - * SIOCSIFDIVERT: - * DIVCMD_DIVERT(DIVARG1_ENABLE|DIVARG1_DISABLE, , ,ifindex) - * DIVCMD_IP(DIVARG1_ENABLE|DIVARG1_DISABLE, , , ifindex) - * DIVCMD_TCP(DIVARG1_ENABLE|DIVARG1_DISABLE, , , ifindex) - * DIVCMD_TCPDST(DIVARG1_ADD|DIVARG1_REMOVE, port, , ifindex) - * DIVCMD_TCPSRC(DIVARG1_ADD|DIVARG1_REMOVE, port, , ifindex) - * DIVCMD_UDP(DIVARG1_ENABLE|DIVARG1_DISABLE, , , ifindex) - * DIVCMD_UDPDST(DIVARG1_ADD|DIVARG1_REMOVE, port, , ifindex) - * DIVCMD_UDPSRC(DIVARG1_ADD|DIVARG1_REMOVE, port, , ifindex) - * DIVCMD_ICMP(DIVARG1_ENABLE|DIVARG1_DISABLE, , , ifindex) - * DIVCMD_RESET(, , , ifindex) - * - * SIOGIFDIVERT: - * DIVCMD_GETSTATUS(divert_blk, , , ifindex) - * DIVCMD_GETVERSION(string[3]) - */ +#define DIVERT_SET_STATE 1 /* Switch diversion on or off */ +#define DIVERT_APPEND_RULE 2 /* Add a rule */ +#define DIVERT_INSERT_RULE 3 /* Insert a rule */ +#define DIVERT_REPLACE_RULE 4 /* Replace a rule */ +#define DIVERT_DELETE_RULE 5 /* Delete a rule */ +#define DIVERT_FLUSH_RULES 6 /* Flush all the rules */ +#define DIVERT_GET_RULE 7 /* Retrieve a rule */ +#define DIVERT_GET_STATUS 8 /* retrieve the version + diverter status */ -/* Possible values for arg1 */ -#define DIVARG1_ENABLE 0 /* ENABLE something */ -#define DIVARG1_DISABLE 1 /* DISABLE something */ -#define DIVARG1_ADD 2 /* ADD something */ -#define DIVARG1_REMOVE 3 /* REMOVE something */ +#ifdef __KERNEL__ +// Linux kernel 2.4.0+ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) +# define NETDEVICE struct net_device +// Linux kernel 2.3.0+ not supported +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0) +# error "Linux kernel 2.3.x not supported !" +// Linux kernel 2.2.18+ +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,18) +# define NETDEVICE struct device +# define dev_put(a) +#else +# error "This Linux kernel is not supported !" +#endif + +extern int (*divert_ioctl_hook)(unsigned int, struct divert_cf *); +extern void (*divert_frame_hook)(struct sk_buff *skb); +extern int (*alloc_divert_blk_hook)(NETDEVICE *); +extern void (*free_divert_blk_hook)(NETDEVICE *); +extern int (*dv_init_hook)(void); +extern char sysctl_divert_version_hook[32]; -#ifdef __KERNEL__ /* diverter functions */ #include -int alloc_divert_blk(struct net_device *); -void free_divert_blk(struct net_device *); -int divert_ioctl(unsigned int cmd, struct divert_cf *arg); -void divert_frame(struct sk_buff *skb); +int dv_init(void); +void dv_cleanup(void); +int alloc_divert_blk(NETDEVICE *); +void free_divert_blk(NETDEVICE *); +int divert_ioctl(unsigned int, struct divert_cf *); +void divert_frame(struct sk_buff *); + +/* Rule processing routines */ +int process_ip_rule(struct sk_buff *, struct divert_rule *); +int process_ip_tcp_rule(struct sk_buff *, struct divert_rule *); +int process_ip_udp_rule(struct sk_buff *, struct divert_rule *); +int process_ip_icmp_rule(struct sk_buff *, struct divert_rule *); + +/* Rule manipulation routines */ +struct divert_rule *divert_alloc_rule(void); +void divert_free_rule(struct divert_rule *); +struct divert_rule *divert_get_rule(NETDEVICE *, int); +int divert_append_rule(NETDEVICE *, struct divert_rule *); +int divert_insert_rule(NETDEVICE *, struct divert_rule *, int); +int divert_delete_rule(NETDEVICE *, int); +int divert_replace_rule(NETDEVICE *, struct divert_rule *, int); +int divert_flush_rules(NETDEVICE *); -#endif +#endif /* __KERNEL__ */ #endif /* _LINUX_DIVERT_H */ diff -Naur linux-2.4.9/include/linux/netdevice.h linux-2.4.9-dv/include/linux/netdevice.h --- linux-2.4.9/include/linux/netdevice.h Wed Aug 15 21:21:32 2001 +++ linux-2.4.9-dv/include/linux/netdevice.h Sun Sep 23 21:41:55 2001 @@ -403,7 +403,7 @@ rwlock_t fastpath_lock; struct dst_entry *fastpath[NETDEV_FASTROUTE_HMASK+1]; #endif -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) /* this will get initialized at each interface type init routine */ struct divert_blk *divert; #endif /* CONFIG_NET_DIVERT */ diff -Naur linux-2.4.9/net/Config.in linux-2.4.9-dv/net/Config.in --- linux-2.4.9/net/Config.in Wed Mar 7 06:44:15 2001 +++ linux-2.4.9-dv/net/Config.in Sun Sep 23 15:46:15 2001 @@ -63,7 +63,7 @@ tristate 'CCITT X.25 Packet Layer (EXPERIMENTAL)' CONFIG_X25 tristate 'LAPB Data Link Driver (EXPERIMENTAL)' CONFIG_LAPB bool '802.2 LLC (EXPERIMENTAL)' CONFIG_LLC - bool 'Frame Diverter (EXPERIMENTAL)' CONFIG_NET_DIVERT + tristate 'Frame Diverter (EXPERIMENTAL)' CONFIG_NET_DIVERT # if [ "$CONFIG_LLC" = "y" ]; then # bool ' Netbeui (EXPERIMENTAL)' CONFIG_NETBEUI # fi diff -Naur linux-2.4.9/net/Makefile linux-2.4.9-dv/net/Makefile --- linux-2.4.9/net/Makefile Tue Jun 12 02:15:27 2001 +++ linux-2.4.9-dv/net/Makefile Sun Sep 23 22:12:39 2001 @@ -11,7 +11,7 @@ export-objs := netsyms.o subdir-y := core ethernet -subdir-m := ipv4 # hum? +subdir-m := core ipv4 # hum? subdir-$(CONFIG_NET) += 802 sched diff -Naur linux-2.4.9/net/core/dev.c linux-2.4.9-dv/net/core/dev.c --- linux-2.4.9/net/core/dev.c Tue Jul 31 17:34:02 2001 +++ linux-2.4.9-dv/net/core/dev.c Sun Sep 23 15:33:37 2001 @@ -1387,13 +1387,29 @@ return ret; } +#if defined(CONFIG_NET_DIVERT) + void (*divert_frame_hook)(struct sk_buff *skb) = divert_frame; + int (*divert_ioctl_hook)(unsigned int, struct divert_cf *) =divert_ioctl; + int (*alloc_divert_blk_hook)(NETDEVICE *dev) = alloc_divert_blk; + void (*free_divert_blk_hook)(NETDEVICE *dev) = free_divert_blk; + int (*dv_init_hook)(void) = dv_init; + char sysctl_divert_version_hook[32] = "\0"; +#elif defined(CONFIG_NET_DIVERT_MODULE) + void (*divert_frame_hook)(struct sk_buff *skb) = NULL; + int (*divert_ioctl_hook)(unsigned int, struct divert_cf *) = NULL; + int (*alloc_divert_blk_hook)(NETDEVICE *dev) = NULL; + void (*free_divert_blk_hook)(NETDEVICE *dev) = NULL; + int (*dv_init_hook)(void) = NULL; + char sysctl_divert_version_hook[32] = "\0"; +#endif + -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) static inline void handle_diverter(struct sk_buff *skb) { - /* if diversion is supported on device, then divert */ - if (skb->dev->divert && skb->dev->divert->divert) - divert_frame(skb); + /* if diversion is supported on device, then divert */ + if (skb->dev->divert && skb->dev->divert->divert) + divert_frame_hook(skb); } #endif /* CONFIG_NET_DIVERT */ @@ -1452,11 +1468,11 @@ } } -#ifdef CONFIG_NET_DIVERT - if (skb->dev->divert && skb->dev->divert->divert) - handle_diverter(skb); -#endif /* CONFIG_NET_DIVERT */ +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) + if (divert_frame_hook) + handle_diverter(skb); +#endif /* CONFIG_NET_DIVERT */ #if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) if (skb->dev->br_port != NULL && @@ -2401,7 +2417,7 @@ int register_netdevice(struct net_device *dev) { struct net_device *d, **dp; -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) int ret; #endif @@ -2415,10 +2431,15 @@ if (dev_boot_phase) net_dev_init(); -#ifdef CONFIG_NET_DIVERT - ret = alloc_divert_blk(dev); - if (ret) - return ret; +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) + + if (alloc_divert_blk_hook) + { + ret = alloc_divert_blk_hook(dev); + if (ret) + return ret; + } + #endif /* CONFIG_NET_DIVERT */ dev->iflink = -1; @@ -2567,9 +2588,12 @@ /* Notifier chain MUST detach us from master device. */ BUG_TRAP(dev->master==NULL); -#ifdef CONFIG_NET_DIVERT - free_divert_blk(dev); -#endif +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) + if (free_divert_blk_hook) + { + free_divert_blk_hook(dev); + } +#endif /* CONFIG_NET_DIVERT */ if (dev->features & NETIF_F_DYNALLOC) { #ifdef NET_REFCNT_DEBUG @@ -2643,9 +2667,6 @@ extern void net_device_init(void); extern void ip_auto_config(void); -#ifdef CONFIG_NET_DIVERT -extern void dv_init(void); -#endif /* CONFIG_NET_DIVERT */ /* @@ -2660,8 +2681,9 @@ if (!dev_boot_phase) return 0; -#ifdef CONFIG_NET_DIVERT - dv_init(); +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) + if (dv_init_hook) + dv_init_hook(); #endif /* CONFIG_NET_DIVERT */ /* diff -Naur linux-2.4.9/net/core/dv.c linux-2.4.9-dv/net/core/dv.c --- linux-2.4.9/net/core/dv.c Tue Jul 10 23:11:43 2001 +++ linux-2.4.9-dv/net/core/dv.c Sun Sep 23 22:10:48 2001 @@ -5,7 +5,7 @@ * * Generic frame diversion * - * Version: @(#)eth.c 0.41 09/09/2000 + * Version: @(#)eth.c 0.5 04/03/2001 * * Authors: * Benoit LOCHER: initial integration within the kernel with support for ethernet @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -37,411 +38,803 @@ #include #include #include +#include + + +const char sysctl_divert_version[32]="0.52"; /* Current version */ + +int dv_init(void); +void dv_cleanup(void); + + + +/* + * Uncomment this *only* to trace a suspect behaviour of the diverter + * this will produce lots of debug info in the log + */ + +// #define DIVERT_DEBUG + + +#ifdef DIVERT_DEBUG +# define MSGDBG(m) printk(KERN_DEBUG "divert:%s (line %d in %s)\n", m, __LINE__, __FILE__) +#else +# define MSGDBG(m) +#endif + +#ifdef MODULE + + +int init_module(void) +{ + int if_index, ret_code; + NETDEVICE *dev; + + printk(KERN_INFO "divert: loading module...\n"); + dv_init(); + + /* + * Allocate a divert_blk for each ethernet interface of the system. + */ + if_index=0; + printk(KERN_DEBUG "divert: looping through devices...\n"); + for(dev=dev_base; dev != NULL; dev=dev->next) + { + printk(KERN_DEBUG "divert: checking for device %s\n", dev->name); + if (!strncmp(dev->name, "eth", 3)) + { + printk(KERN_DEBUG "divert: allocating divert_blk for %s\n", dev->name); + ret_code=alloc_divert_blk(dev); + if (ret_code) + return ret_code; + } + if_index++; + } + printk(KERN_DEBUG "divert: finished looping through devices (%d found)\n", if_index); + printk(KERN_INFO "divert: module loaded.\n"); + return 0; +} + + +void cleanup_module(void) +{ + int if_index; + NETDEVICE *dev; + + printk(KERN_INFO "divert: unloading module...\n"); + /* + * Free the divert_blk allocated for ethernet interfaces of the system + */ + if_index=0; + printk(KERN_DEBUG "divert: looping through devices...\n"); + for(dev=dev_base; dev != NULL; dev=dev->next) + { + if (!strncmp(dev->name, "eth", 3)) + { + printk(KERN_DEBUG "divert: freeing divert_blk for %s\n", dev->name); + free_divert_blk(dev); + } + if_index++; + } + printk(KERN_DEBUG "divert: finished looping through devices (%d found)\n", if_index); + dv_cleanup(); + printk(KERN_INFO "divert: module unloaded.\n"); + return; +} + +#endif /* MODULE */ -const char sysctl_divert_version[32]="0.46"; /* Current version */ int __init dv_init(void) { printk(KERN_INFO "NET4: Frame Diverter %s\n", sysctl_divert_version); + divert_frame_hook = divert_frame; + divert_ioctl_hook = divert_ioctl; + alloc_divert_blk_hook = alloc_divert_blk; + free_divert_blk_hook = free_divert_blk; + strcpy(sysctl_divert_version_hook, sysctl_divert_version); + dv_init_hook = dv_init; return 0; } + +void dv_cleanup(void) +{ + divert_frame_hook = NULL; + divert_ioctl_hook = NULL; + alloc_divert_blk_hook = NULL; + free_divert_blk_hook = NULL; + strcpy(sysctl_divert_version_hook, ""); + dv_init_hook = NULL; + return; +} + + /* * Allocate a divert_blk for a device. This must be an ethernet nic. */ -int alloc_divert_blk(struct net_device *dev) + +int alloc_divert_blk(NETDEVICE *dev) { - int alloc_size = (sizeof(struct divert_blk) + 3) & ~3; - - if (!strncmp(dev->name, "eth", 3)) { + int alloc_size=(sizeof(struct divert_blk)+3)&~3; + + if (!strncmp(dev->name, "eth", 3)) + { printk(KERN_DEBUG "divert: allocating divert_blk for %s\n", - dev->name); - - dev->divert = (struct divert_blk *) - kmalloc(alloc_size, GFP_KERNEL); - if (dev->divert == NULL) { + dev->name); + dev->divert=(struct divert_blk *)kmalloc(alloc_size, GFP_KERNEL); + if (dev->divert==NULL) + { printk(KERN_DEBUG "divert: unable to allocate divert_blk for %s\n", - dev->name); - return -ENOMEM; - } else { - memset(dev->divert, 0, sizeof(struct divert_blk)); + dev->name); + return -EFAULT; + } + else + { + dev->divert->rwlock= RW_LOCK_UNLOCKED; + dev->divert->divert=0; + dev->divert->rule=NULL; } - dev_hold(dev); - } else { + } + else + { printk(KERN_DEBUG "divert: not allocating divert_blk for non-ethernet device %s\n", - dev->name); - - dev->divert = NULL; + dev->name); + dev->divert=NULL; } return 0; -} +} + + /* - * Free a divert_blk allocated by the above function, if it was + * Free a divert_blk allocated by the above function, if it was * allocated on that device. + * */ -void free_divert_blk(struct net_device *dev) +void free_divert_blk(NETDEVICE *dev) { - if (dev->divert) { + if (dev->divert) + { + if (dev->divert->rule != NULL) + { + printk(KERN_INFO "divert: flushing rules\n"); + divert_flush_rules(dev); + } kfree(dev->divert); dev->divert=NULL; - dev_put(dev); - printk(KERN_DEBUG "divert: freeing divert_blk for %s\n", - dev->name); - } else { + printk(KERN_DEBUG "divert: freed divert_blk for %s\n", + dev->name); + } + else + { printk(KERN_DEBUG "divert: no divert_blk to free, %s not ethernet\n", - dev->name); + dev->name); } + return; +} + + +struct divert_rule *divert_alloc_rule(void) +{ + struct divert_rule *rule; + int alloc_size=(sizeof(struct divert_rule)+3)&~3; + + MSGDBG("entering divert_alloc_rule"); + rule=(struct divert_rule *)kmalloc(alloc_size, GFP_KERNEL); + if (rule==NULL) + return NULL; + memset(rule, 0, alloc_size); + rule->prev=NULL; + rule->next=NULL; + MSGDBG("leaving divert_alloc_rule"); + return rule; } -/* - * Adds a tcp/udp (source or dest) port to an array - */ -int add_port(u16 ports[], u16 port) + +void divert_free_rule(struct divert_rule *rule) { - int i; + MSGDBG("entering divert_free_rule"); + kfree(rule); + MSGDBG("leaving divert_free_rule"); +} - if (port == 0) - return -EINVAL; - /* Storing directly in network format for performance, - * thanks Dave :) - */ - port = htons(port); +int divert_append_rule(NETDEVICE *dev, struct divert_rule *new_rule) +{ + struct divert_rule *rule; + unsigned long flags; + + MSGDBG("entering divert_append_rule"); + + if (dev->divert==NULL) + return -EFAULT; + + /* write-lock the rules */ + write_lock_irqsave(&dev->divert->rwlock, flags); + + /* special case, 1st rule appended */ + new_rule->next=NULL; + + rule=dev->divert->rule; - for (i = 0; i < MAX_DIVERT_PORTS; i++) { - if (ports[i] == port) - return -EALREADY; + if (dev->divert->rule==NULL) + { + dev->divert->rule=new_rule; } + else + { + rule=dev->divert->rule; - for (i = 0; i < MAX_DIVERT_PORTS; i++) { - if (ports[i] == 0) { - ports[i] = port; - return 0; - } + while(rule->next!=NULL) + rule=rule->next; + rule->next=new_rule; + new_rule->prev=rule; + } - - return -ENOBUFS; + + /* release the write-lock on the rules */ + write_unlock_irqrestore(&dev->divert->rwlock, flags); +#if defined(CONFIG_NET_DIVERT_MODULE) + /* Increment use count for module if one rule is present on this device */ + if (dev->divert->rule->next == NULL) + MOD_INC_USE_COUNT; +#endif /* CONFIG_NET_DIVERT_MODULE */ + + MSGDBG("leaving divert_append_rule"); + + return 0; } -/* - * Removes a port from an array tcp/udp (source or dest) - */ -int remove_port(u16 ports[], u16 port) + +int divert_insert_rule(NETDEVICE *dev, struct divert_rule *new_rule, int position) { - int i; + struct divert_rule *rule=NULL, + *prev=NULL; + unsigned long flags; + + MSGDBG("entering divert_insert_rule"); + + if (dev->divert==NULL) + return -EFAULT; - if (port == 0) - return -EINVAL; + /* write-lock the rules */ + write_lock_irqsave(&dev->divert->rwlock, flags); - /* Storing directly in network format for performance, - * thanks Dave ! - */ - port = htons(port); + rule=divert_get_rule(dev, position); + if (rule==NULL) + { + write_unlock_irqrestore(&dev->divert->rwlock, flags); + return -EINVAL; + } - for (i = 0; i < MAX_DIVERT_PORTS; i++) { - if (ports[i] == port) { - ports[i] = 0; - return 0; - } + prev=rule->prev; + + if (prev != NULL) + { + prev->next=new_rule; } - return -EINVAL; + new_rule->prev=prev; + new_rule->next=rule; + + rule->prev=new_rule; + + if (position==1) + dev->divert->rule=new_rule; + + /* release the write-lock */ + write_unlock_irqrestore(&dev->divert->rwlock, flags); + +#if defined(CONFIG_NET_DIVERT_MODULE) + /* Increment use count for module if one rule is present to this interface */ + if (dev->divert->rule->next==NULL) + MOD_INC_USE_COUNT; +#endif /* CONFIG_NET_DIVERT_MODULE */ + + MSGDBG("leaving divert_insert_rule"); + + return 0; } -/* Some basic sanity checks on the arguments passed to divert_ioctl() */ -int check_args(struct divert_cf *div_cf, struct net_device **dev) + +int divert_delete_rule(NETDEVICE *dev, int position) { - char devname[32]; - int ret; + struct divert_rule *rule, + *prev=NULL, + *next=NULL; + unsigned long flags; - if (dev == NULL) - return -EFAULT; + MSGDBG("entering divert_delete_rule"); - /* GETVERSION: all other args are unused */ - if (div_cf->cmd == DIVCMD_GETVERSION) - return 0; + /* write-lock the rules */ + write_lock_irqsave(&dev->divert->rwlock, flags); - /* 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) + rule=divert_get_rule(dev, position); + if (rule==NULL) + { + write_unlock_irqrestore(&dev->divert->rwlock, flags); return -EINVAL; + } - ret = 0; + prev=rule->prev; + next=rule->next; - /* user issuing the ioctl must be a super one :) */ - if (!capable(CAP_SYS_ADMIN)) { - ret = -EPERM; - goto out; + if (next != NULL) + { + next->prev=prev; } - /* Device must have a divert_blk member NOT null */ - if ((*dev)->divert == NULL) - ret = -EINVAL; -out: - dev_put(*dev); - return ret; + if (prev != NULL) + { + prev->next=next; + } + + if (position==1) + dev->divert->rule=next; + + divert_free_rule(rule); + + /* release the write-lock on the rules */ + write_unlock_irqrestore(&dev->divert->rwlock, flags); + +#if defined(CONFIG_NET_DIVERT_MODULE) + /* Decrement use count on this device if the last rule has been removed */ + if (dev->divert->rule == NULL) + MOD_DEC_USE_COUNT; +#endif /* CONFIG_NET_DIVERT_MODULE */ + + MSGDBG("leaving divert_delete_rule"); + + return 0; } -/* - * control function of the diverter - */ -#define DVDBG(a) \ - printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a)) -int divert_ioctl(unsigned int cmd, struct divert_cf *arg) +int divert_replace_rule(NETDEVICE *dev, struct divert_rule *new_rule, int position) { - 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; + struct divert_rule *rule; + unsigned long flags; - 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; + MSGDBG("entering divert_replace_rule"); + + /* write-lock the rules */ + write_lock_irqsave(&dev->divert->rwlock, flags); + + rule=divert_get_rule(dev, position); + if (rule==NULL) + { + /* release the write-lock on the rules */ + write_unlock_irqrestore(&dev->divert->rwlock, flags); + return -EINVAL; + } - default: - return -EINVAL; - }; + if (rule->next != NULL) + rule->next->prev=new_rule; + + if (rule->prev != NULL) + rule->prev->next=new_rule; + + new_rule->next=rule->next; + new_rule->prev=rule->prev; - break; + if (position==1) + dev->divert->rule=new_rule; + + divert_free_rule(rule); - case SIOCSIFDIVERT: - if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf))) - return -EFAULT; + /* release the write-lock */ + write_unlock_irqrestore(&dev->divert->rwlock, flags); + + MSGDBG("leaving divert_replace_rule"); + + return 0; +} + - 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; +int divert_flush_rules(NETDEVICE *dev) +{ + struct divert_rule *rule, + *next; + unsigned long flags; - case DIVARG1_DISABLE: - if (!div_blk->divert) - return -EALREADY; - div_blk->divert = 0; - break; + MSGDBG("entering divert_flush_rules"); + + /* No diversion possible on this interface ? */ + if (!dev->divert) + return -EINVAL; - default: - return -EINVAL; - }; + /* there are no rule set on this interface */ + if (!dev->divert->rule) + return -EINVAL; + + /* write-lock the rules */ + write_lock_irqsave(&dev->divert->rwlock, flags); + + rule=dev->divert->rule; + dev->divert->rule=NULL; + while(rule!=NULL) + { + next=rule->next; + divert_free_rule(rule); + rule=next; + } - break; + /* release the write-lock */ + write_unlock_irqrestore(&dev->divert->rwlock, flags); - 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; +#if defined(CONFIG_NET_DIVERT_MODULE) + /* Decrement use count on this module when rules are empty on a device */ + MOD_DEC_USE_COUNT; +#endif /* CONFIG_NET_DIVERT_MODULE */ + + MSGDBG("leaving divert_flush_rules"); + + return 0; +} - case DIVARG1_DISABLE: - if (!(div_blk->protos & DIVERT_PROTO_IP)) - return -EALREADY; - div_blk->protos &= ~DIVERT_PROTO_IP; - break; - default: - return -EINVAL; - }; +/* + * + * Must be called with divert->rwlock locked + * + */ +struct divert_rule *divert_get_rule(NETDEVICE *dev, int rule_num) +{ + struct divert_rule *rule; + int num=1; - break; + MSGDBG("entering divert_get_rule"); + + if (dev==NULL) + return NULL; - 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; + if (dev->divert==NULL) + return NULL; + + rule=dev->divert->rule; + while(rule!=NULL && rule_num!=num) + { + num++; + rule=rule->next; + } - case DIVARG1_DISABLE: - if (!(div_blk->protos & DIVERT_PROTO_TCP)) - return -EALREADY; - div_blk->protos &= ~DIVERT_PROTO_TCP; - break; + printk(KERN_INFO "divert_get_rule: returning rule #%d(%p)\n", rule_num, rule); + + MSGDBG("leaving divert_get_rule"); + + return rule; +} - default: - return -EINVAL; - }; - break; +/* Some basic sanity checks on the arguments passed to divert_ioctl() */ +int check_args(struct divert_cf *div_cf, NETDEVICE **dev) +{ + MSGDBG("entering check_args"); + + if (dev==NULL) + return -EFAULT; - 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); + /* Let's try to find the ifname */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) // Linux kernel 2.4.0+ + *dev=(NETDEVICE *)dev_get_by_name(div_cf->dev_name); +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)) \ + && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,18)) // Linux kernel 2.2.18+ + *dev=(NETDEVICE *)dev_get(div_cf->dev_name); +#else +#error "Not a 2.2.x nor 2.4.x kernel !" +#endif - default: - return -EINVAL; - }; + /* dev should NOT be null */ + if (*dev==NULL) + { + return -EINVAL; + } + + /* user issuing the ioctl must be a super one :) */ + if (!suser()) + { + dev_put(*dev); + return -EPERM; + } - break; + /* Device must have a divert_blk member NOT null */ + if ((*dev)->divert==NULL) + { + dev_put(*dev); + return -EFAULT; + } - case DIVCMD_TCPSRC: - switch(div_cf.arg1.int32) { - case DIVARG1_ADD: - return add_port(div_blk->tcp_src, - div_cf.arg2.uint16); + MSGDBG("leaving check_args"); + + + return 0; +} - case DIVARG1_REMOVE: - return remove_port(div_blk->tcp_src, - div_cf.arg2.uint16); - default: - return -EINVAL; - }; +void hton_cidr(divert_cidr *cidr) +{ + cidr->ipnum=htonl(cidr->ipnum); + cidr->ipmask=htonl(cidr->ipmask); +} - 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; +void ntoh_cidr(divert_cidr *cidr) +{ + cidr->ipnum=ntohl(cidr->ipnum); + cidr->ipmask=ntohl(cidr->ipmask); +} - case DIVARG1_DISABLE: - if (!(div_blk->protos & DIVERT_PROTO_UDP)) - return -EALREADY; - div_blk->protos &= ~DIVERT_PROTO_UDP; - break; - default: - return -EINVAL; - }; +int fill_rule_from_arg(struct divert_cf *div_cf, struct divert_rule *rule) +{ + divert_cidr scidr, dcidr; + rule->proto_num=div_cf->proto_num; + rule->proto_blk=div_cf->proto_blk; + rule->divert_locally=div_cf->divert_locally; + memcpy(rule->dest_mac, div_cf->dest_mac, ETH_ALEN); + switch(div_cf->proto_num) + { + case DIVERT_PROTO_IP: + rule->process_rule=process_ip_rule; + scidr=rule->proto_blk.ip.scidr; + dcidr=rule->proto_blk.ip.dcidr; + hton_cidr(&rule->proto_blk.ip.scidr); + hton_cidr(&rule->proto_blk.ip.dcidr); + break; + case DIVERT_PROTO_ICMP: + rule->process_rule=process_ip_icmp_rule; + scidr=rule->proto_blk.icmp.scidr; + dcidr=rule->proto_blk.icmp.dcidr; + hton_cidr(&rule->proto_blk.icmp.scidr); + hton_cidr(&rule->proto_blk.icmp.dcidr); break; - case DIVCMD_UDPDST: - switch(div_cf.arg1.int32) { - case DIVARG1_ADD: - return add_port(div_blk->udp_dst, - div_cf.arg2.uint16); + case DIVERT_PROTO_TCP: + rule->process_rule=process_ip_tcp_rule; + scidr=rule->proto_blk.tcp.scidr; + dcidr=rule->proto_blk.tcp.dcidr; + hton_cidr(&rule->proto_blk.tcp.scidr); + hton_cidr(&rule->proto_blk.tcp.dcidr); + break; - case DIVARG1_REMOVE: - return remove_port(div_blk->udp_dst, - div_cf.arg2.uint16); + case DIVERT_PROTO_UDP: + rule->process_rule=process_ip_udp_rule; + scidr=rule->proto_blk.udp.scidr; + dcidr=rule->proto_blk.udp.dcidr; + hton_cidr(&rule->proto_blk.udp.scidr); + hton_cidr(&rule->proto_blk.udp.dcidr); + break; - default: - return -EINVAL; - }; + default: + printk(KERN_INFO "divert: unsupported protocol: %d\n", div_cf->proto_num); + } + printk(KERN_INFO "divert: proto=%d, source=%08X/%08X, dest=%08X/%08X\n", + div_cf->proto_num, + (int)scidr.ipnum, (int)scidr.ipmask, + (int)dcidr.ipnum, (int)dcidr.ipmask); + return 0; +} - break; - case DIVCMD_UDPSRC: - switch(div_cf.arg1.int32) { - case DIVARG1_ADD: - return add_port(div_blk->udp_src, - div_cf.arg2.uint16); +int fill_arg_from_rule(struct divert_rule *rule, struct divert_cf *div_cf) +{ + div_cf->proto_num=rule->proto_num; + div_cf->proto_blk=rule->proto_blk; + div_cf->divert_locally=rule->divert_locally; + memcpy(div_cf->dest_mac, rule->dest_mac, ETH_ALEN); + printk(KERN_INFO "divert: (*) proto=%d, source=%08X/%08X, dest=%08X/%08X\n", + div_cf->proto_num, + (int)div_cf->proto_blk.ip.scidr.ipnum, (int)div_cf->proto_blk.ip.scidr.ipmask, + (int)div_cf->proto_blk.ip.dcidr.ipnum, (int)div_cf->proto_blk.ip.dcidr.ipmask); + switch(div_cf->proto_num) + { + case DIVERT_PROTO_IP: + ntoh_cidr(&div_cf->proto_blk.ip.scidr); + ntoh_cidr(&div_cf->proto_blk.ip.dcidr); + break; - case DIVARG1_REMOVE: - return remove_port(div_blk->udp_src, - div_cf.arg2.uint16); + case DIVERT_PROTO_TCP: + ntoh_cidr(&div_cf->proto_blk.tcp.scidr); + ntoh_cidr(&div_cf->proto_blk.tcp.dcidr); + break; - default: - return -EINVAL; - }; + case DIVERT_PROTO_UDP: + ntoh_cidr(&div_cf->proto_blk.udp.scidr); + ntoh_cidr(&div_cf->proto_blk.udp.dcidr); + break; + case DIVERT_PROTO_ICMP: + ntoh_cidr(&div_cf->proto_blk.icmp.scidr); + ntoh_cidr(&div_cf->proto_blk.icmp.dcidr); break; + } + return 0; +} - 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; +int divert_ioctl(unsigned int cmd, struct divert_cf *arg) +{ + struct divert_cf div_cf; + NETDEVICE *dev; + int oper, ret; + struct divert_rule *rule; - default: - return -EINVAL; - }; + if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf))) + return -EFAULT; + + ret=check_args(&div_cf, &dev); + if (ret) + { + return ret; + } + + // Here, we've locked the device (ref_cnt) with check_args + + oper=div_cf.cmd; + + switch(cmd) + { + case SIOCGIFDIVERT: + switch(oper) + { + case DIVERT_GET_RULE: + rule=divert_get_rule(dev, div_cf.rule_num); + if (rule==NULL) + { + dev_put(dev); + return -EINVAL; + } + ret=fill_arg_from_rule(rule, &div_cf); + if (ret) + { + dev_put(dev); + return ret; + } + break; + + case DIVERT_GET_STATUS: + div_cf.status=dev->divert->divert; + break; + + default: + { + dev_put(dev); + return -EINVAL; + } + } + if (copy_to_user(arg, &div_cf ,sizeof(struct divert_cf))) + { + dev_put(dev); + return -EFAULT; + } + break; + case SIOCSIFDIVERT: + switch(oper) + { + case DIVERT_SET_STATE: + if (div_cf.status ^ dev->divert->divert) + { + dev->divert->divert=!dev->divert->divert; + return 0; + } + else + { + dev_put(dev); + return -EALREADY; + } + break; + + case DIVERT_APPEND_RULE: + rule=divert_alloc_rule(); + if (rule==NULL) + { + dev_put(dev); + return -EFAULT; + } + ret=fill_rule_from_arg(&div_cf, rule); + if (ret) + { + /* Cleanup allocated memory */ + divert_free_rule(rule); + dev_put(dev); + return ret; + } + ret=divert_append_rule(dev, rule); + if (ret) + { + /* Cleanup allocated memory */ + divert_free_rule(rule); + dev_put(dev); + return ret; + } + break; + + case DIVERT_INSERT_RULE: + rule=divert_alloc_rule(); + if (rule==NULL) + { + dev_put(dev); + return -EFAULT; + } + ret=fill_rule_from_arg(&div_cf, rule); + if (ret) + { + /* Cleanup allocated memory */ + divert_free_rule(rule); + dev_put(dev); + return ret; + } + ret=divert_insert_rule(dev, rule, div_cf.rule_num); + if (ret) + { + /* Cleanup allocated memory */ + divert_free_rule(rule); + dev_put(dev); + return ret; + } + break; + + case DIVERT_REPLACE_RULE: + rule=divert_alloc_rule(); + if (rule==NULL) + { + dev_put(dev); + return -EFAULT; + } + ret=fill_rule_from_arg(&div_cf, rule); + if (ret) + { + /* Cleanup allocated memory */ + divert_free_rule(rule); + dev_put(dev); + return ret; + } + ret=divert_replace_rule(dev, rule, div_cf.rule_num); + if (ret) + { + /* Cleanup allocated memory */ + divert_free_rule(rule); + dev_put(dev); + return ret; + } + break; + + case DIVERT_DELETE_RULE: + ret=divert_delete_rule(dev, div_cf.rule_num); + if (ret) + { + dev_put(dev); + return ret; + } + break; + + case DIVERT_FLUSH_RULES: + ret=divert_flush_rules(dev); + if (ret) + { + dev_put(dev); + return ret; + } + break; + + default: + { + dev_put(dev); + return -EINVAL; + } + } break; default: - return -EINVAL; - }; - - break; - - default: - return -EINVAL; - }; - - return 0; + { + dev_put(dev); + return -EINVAL; + } + } + dev_put(dev); + return ret; } @@ -453,107 +846,278 @@ #define ETH_DIVERT_FRAME(skb) \ memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); \ skb->pkt_type=PACKET_HOST - -void divert_frame(struct sk_buff *skb) + + +#define CHECK_CIDR(ip, cidr, negate) ((negate)? \ + (((ip)&(cidr.ipmask))!=(cidr.ipnum)) : (((ip)&(cidr.ipmask))==(cidr.ipnum))) + +#define CHECK_PORT(port, start, end, negate) ((negate) ? \ + (((port) < (start)) && ((port) > (end))) :\ + (((port) >= (start)) && ((port) <= (end)))) + + +int process_ip_rule(struct sk_buff *skb, struct divert_rule *rule) { - struct ethhdr *eth = skb->mac.ethernet; - 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; + struct iphdr *iph; + unsigned char *skb_data_end=skb->data+skb->len; + +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "entering process_ip_rule()\n"); +#endif + + iph=(struct iphdr *)(skb->data); + /* Check for possible (maliciously) malformed IP frame (thanks Dave) */ + if (((iph->ihl<<2)+(unsigned char*)(iph))>=skb_data_end) + { + printk(KERN_INFO "divert: malformed IP packet !\n"); + return 0; /* don't eat the frame */ } + if (!CHECK_CIDR(iph->saddr, rule->proto_blk.ip.scidr, rule->proto_blk.ip.scidr_negate) || + !CHECK_CIDR(iph->daddr, rule->proto_blk.ip.dcidr, rule->proto_blk.ip.dcidr_negate)) + return 0; + if (rule->divert_locally) + { + skb->pkt_type=PACKET_HOST; + memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); + } + else + memcpy(skb->mac.ethernet, rule->dest_mac, ETH_ALEN); + return 1; /* packet has been diverted */ +} + + +int process_ip_tcp_rule(struct sk_buff *skb, struct divert_rule *rule) +{ + struct iphdr *iph; + struct tcphdr *tcph; + unsigned char *skb_data_end=skb->data+skb->len; + unsigned short sport, dport; + +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "divert: entering process_ip_tcp_rule()\n"); +#endif + iph=(struct iphdr *)(skb->data); /* Check for possible (maliciously) malformed IP frame (thanks Dave) */ - iph = (struct iphdr *) skb->data; - if (((iph->ihl<<2)+(unsigned char*)(iph)) >= skb_data_end) { + if (((iph->ihl<<2)+(unsigned char*)(iph))>=skb_data_end) + { printk(KERN_INFO "divert: malformed IP packet !\n"); - return; + return 0; /* don't eat the frame */ + } + tcph=(struct tcphdr *)(((unsigned char *)iph) + (iph->ihl<<2)); + /* Check for possible (maliciously) malformed IP frame (thanx Dave) */ + if (((unsigned char *)(tcph+1))>=skb_data_end) + { + printk(KERN_INFO "divert: malformed TCP packet !\n"); + return 0; + } + sport=ntohs(tcph->source); + dport=ntohs(tcph->dest); + if (!CHECK_CIDR(iph->saddr, rule->proto_blk.tcp.scidr, rule->proto_blk.tcp.scidr_negate) || + !CHECK_CIDR(iph->daddr, rule->proto_blk.tcp.dcidr, rule->proto_blk.tcp.dcidr_negate)) + return 0; /* ip addresses do not match */ +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "divert: tcp addresses matched, checking ports\n"); + + printk(KERN_DEBUG "sport=%d(%s %d, %d), dport=%d(%s %d, %d)\n", + sport, + rule->proto_blk.tcp.sport_negate?"!":"", + rule->proto_blk.tcp.sport_start, + rule->proto_blk.tcp.sport_end, + dport, + rule->proto_blk.tcp.dport_negate?"!":"", + rule->proto_blk.tcp.dport_start, + rule->proto_blk.tcp.dport_end + ); + +#endif + if (!CHECK_PORT(sport, rule->proto_blk.tcp.sport_start, rule->proto_blk.tcp.sport_end, rule->proto_blk.tcp.sport_negate) || + !CHECK_PORT(dport, rule->proto_blk.tcp.dport_start, rule->proto_blk.tcp.dport_end, rule->proto_blk.tcp.dport_negate)) + return 0; /* ports do not match */ +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "divert: tcp ports matched, diverting\n"); +#endif + if (rule->divert_locally) + { + skb->pkt_type=PACKET_HOST; + memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); } + else + memcpy(skb->mac.ethernet, rule->dest_mac, ETH_ALEN); +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "divert: tcp packet diverted\n"); +#endif + return 1; /* packet has been diverted */ +} - 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; - } +int process_ip_udp_rule(struct sk_buff *skb, struct divert_rule *rule) +{ + struct iphdr *iph; + struct udphdr *udph; + unsigned char *skb_data_end=skb->data+skb->len; + unsigned short sport, dport; + +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "entering process_ip_udp_rule()\n"); +#endif + + iph=(struct iphdr *)(skb->data); + /* Check for possible (maliciously) malformed IP frame (thanks Dave) */ + if (((iph->ihl<<2)+(unsigned char*)(iph))>=skb_data_end) + { + printk(KERN_INFO "divert: malformed IP packet !\n"); + return 0; /* don't eat the frame */ + } + udph=(struct udphdr *)(((unsigned char *)iph) + (iph->ihl<<2)); + /* Check for possible (maliciously) malformed IP packet (thanks Dave) */ + if (((unsigned char *)(udph+1))>=skb_data_end) + { + printk(KERN_INFO "divert: malformed UDP packet !\n"); + return 0; /* don't eat the frame */ + } + sport=ntohs(udph->source); + dport=ntohs(udph->dest); + if (!CHECK_CIDR(iph->saddr, rule->proto_blk.udp.scidr, rule->proto_blk.udp.scidr_negate) || + !CHECK_CIDR(iph->daddr, rule->proto_blk.udp.dcidr, rule->proto_blk.udp.dcidr_negate)) + return 0; /* ip addresses do not match */ + if (!CHECK_PORT(sport, rule->proto_blk.udp.sport_start, rule->proto_blk.udp.sport_end, rule->proto_blk.udp.sport_negate) || + !CHECK_PORT(dport, rule->proto_blk.udp.dport_start, rule->proto_blk.udp.dport_end, rule->proto_blk.udp.dport_negate)) + return 0; /* ports do not match */ + if (rule->divert_locally) + { + skb->pkt_type=PACKET_HOST; + memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); + } + else + memcpy(skb->mac.ethernet, rule->dest_mac, ETH_ALEN); + return 1; /* packet has been diverted */ +} - /* 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; - } + +int process_ip_icmp_rule(struct sk_buff *skb, struct divert_rule *rule) +{ + struct iphdr *iph; + struct icmphdr *icmph; + unsigned char *skb_data_end=skb->data+skb->len; + unsigned short type; + +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "entering process_ip_icmp_rule()\n"); +#endif + + iph=(struct iphdr *)(skb->data); + /* Check for possible (maliciously) malformed IP frame (thanks Dave) */ + if (((iph->ihl<<2)+(unsigned char*)(iph))>=skb_data_end) + { + printk(KERN_INFO "divert: malformed IP packet !\n"); + return 0; /* don't eat the packet */ + } + icmph=(struct icmphdr *)(((unsigned char *)iph) + (iph->ihl<<2)); + /* Check for possible (maliciously) malformed IP packet (thanks Dave) */ + if (((unsigned char *)(icmph+1))>=skb_data_end) + { + printk(KERN_INFO "divert: malformed ICMP packet !\n"); + return 0; /* don't eat the frame */ + } +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "icmp: %08X(%08X/%08X):%d --> %08X(%08X/%08X):%d (%d)\n", + iph->saddr, (int)rule->proto_blk.icmp.scidr.ipnum, (int)rule->proto_blk.icmp.scidr.ipmask, + CHECK_CIDR(iph->saddr, rule->proto_blk.icmp.scidr, rule->proto_blk.icmp.scidr_negate), + iph->daddr, (int)rule->proto_blk.icmp.dcidr.ipnum, (int)rule->proto_blk.icmp.dcidr.ipmask, + CHECK_CIDR(iph->daddr, rule->proto_blk.icmp.dcidr, rule->proto_blk.icmp.dcidr_negate), + icmph->type); +#endif + if (!CHECK_CIDR(iph->saddr, rule->proto_blk.icmp.scidr, rule->proto_blk.icmp.scidr_negate) || + !CHECK_CIDR(iph->daddr, rule->proto_blk.icmp.dcidr, rule->proto_blk.icmp.dcidr_negate)) + return 0; /* ip addresses do not match */ + type=icmph->type; + if (rule->proto_blk.icmp.type!=255) + { + if ( (!(rule->proto_blk.icmp.type_negate)? + (type!=rule->proto_blk.icmp.type): + (type==rule->proto_blk.icmp.type) )) + return 0; /* icmp types do not match */ + } + if (rule->divert_locally) + { + skb->pkt_type=PACKET_HOST; + memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); + } + else + memcpy(skb->mac.ethernet, rule->dest_mac, ETH_ALEN); + return 1; +} - /* 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; - } +#define HTONS(a) ((((a)>>8)&0x00FF) | (((a)<<8)&0xFF00)) - /* 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; +void divert_frame(struct sk_buff *skb) +{ + struct ethhdr *eth=skb->mac.ethernet; + struct iphdr *iph; + struct divert_rule *rule=skb->dev->divert->rule; + unsigned long flags; + unsigned int proto1, proto2, proto3; + + /* read-lock the rule list for this interface */ + read_lock_irqsave(&skb->dev->divert->rwlock, flags); + + proto1=proto2=proto3=0; +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "eth->h_proto=%04X\n", eth->h_proto); +#endif + + switch(eth->h_proto) + { + case HTONS(ETH_P_IP): + proto1=DIVERT_PROTO_IP; + iph=(struct iphdr *)(skb->data); +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "iph->protocol=%04X\n", iph->protocol); +#endif + switch(iph->protocol) + { + case IPPROTO_TCP: + proto2=DIVERT_PROTO_TCP; + break; + + case IPPROTO_UDP: + proto2=DIVERT_PROTO_UDP; + break; + + case IPPROTO_ICMP: + proto2=DIVERT_PROTO_ICMP; + break; } + break; + + case HTONS(ETH_P_ARP): + proto1=DIVERT_PROTO_ARP; + break; + + } +#ifdef DIVERT_DEBUG + printk(KERN_DEBUG "entering divert_frame(%d, %d, %d)\n", proto1, proto2, proto3); +#endif + + while(rule) + { + if (rule->proto_num==proto1 || rule->proto_num==proto2 || rule->proto_num==proto3) + { + /* + * if rule_process() returns !=0 then stop further + * diversion on this packet + */ + if (rule->process_rule && rule->process_rule(skb, rule)) + break; } - break; - }; + rule=rule->next; - return; + } + + /* release the read lock on this interface */ + read_unlock_irqrestore(&skb->dev->divert->rwlock, flags); } diff -Naur linux-2.4.9/net/core/sysctl_net_core.c linux-2.4.9-dv/net/core/sysctl_net_core.c --- linux-2.4.9/net/core/sysctl_net_core.c Tue Oct 10 17:33:52 2000 +++ linux-2.4.9-dv/net/core/sysctl_net_core.c Sun Sep 23 21:18:26 2001 @@ -29,8 +29,8 @@ extern int sysctl_optmem_max; extern int sysctl_hot_list_len; -#ifdef CONFIG_NET_DIVERT -extern char sysctl_divert_version[]; +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) +extern char sysctl_divert_version_hook[]; #endif /* CONFIG_NET_DIVERT */ ctl_table core_table[] = { @@ -79,9 +79,9 @@ {NET_CORE_HOT_LIST_LENGTH, "hot_list_length", &sysctl_hot_list_len, sizeof(int), 0644, NULL, &proc_dointvec}, -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) {NET_CORE_DIVERT_VERSION, "divert_version", - (void *)sysctl_divert_version, 32, 0444, NULL, + (void *)sysctl_divert_version_hook, 32, 0444, NULL, &proc_dostring}, #endif /* CONFIG_NET_DIVERT */ #endif /* CONFIG_NET */ diff -Naur linux-2.4.9/net/ipv4/af_inet.c linux-2.4.9-dv/net/ipv4/af_inet.c --- linux-2.4.9/net/ipv4/af_inet.c Tue Aug 7 15:30:50 2001 +++ linux-2.4.9-dv/net/ipv4/af_inet.c Sun Sep 23 15:45:26 2001 @@ -110,7 +110,7 @@ #ifdef CONFIG_KMOD #include #endif -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) #include #endif /* CONFIG_NET_DIVERT */ #if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO) @@ -884,11 +884,20 @@ case SIOCGIFDIVERT: case SIOCSIFDIVERT: -#ifdef CONFIG_NET_DIVERT - return divert_ioctl(cmd, (struct divert_cf *) arg); -#else +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) +#ifdef CONFIG_INET +#ifdef CONFIG_KMOD + if (divert_ioctl_hook == NULL) + request_module("dv"); +#endif /* CONFIG_KMOD */ + if (divert_ioctl_hook != NULL) + return(divert_ioctl_hook(cmd, (struct divert_cf *) arg)); + else + return -ENOPKG; +#endif /* CONFIG_INET */ return -ENOPKG; #endif /* CONFIG_NET_DIVERT */ + return -ENOPKG; case SIOCADDDLCI: case SIOCDELDLCI: diff -Naur linux-2.4.9/net/netsyms.c linux-2.4.9-dv/net/netsyms.c --- linux-2.4.9/net/netsyms.c Tue Aug 7 15:30:50 2001 +++ linux-2.4.9-dv/net/netsyms.c Sun Sep 23 21:21:04 2001 @@ -31,7 +31,7 @@ #include #include #include -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) #include #endif /* CONFIG_NET_DIVERT */ @@ -225,11 +225,16 @@ #endif #endif -#ifdef CONFIG_NET_DIVERT -EXPORT_SYMBOL(alloc_divert_blk); -EXPORT_SYMBOL(free_divert_blk); -EXPORT_SYMBOL(divert_ioctl); + +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) +EXPORT_SYMBOL(dv_init_hook); +EXPORT_SYMBOL(alloc_divert_blk_hook); +EXPORT_SYMBOL(free_divert_blk_hook); +EXPORT_SYMBOL(divert_ioctl_hook); +EXPORT_SYMBOL(divert_frame_hook); +EXPORT_SYMBOL(sysctl_divert_version_hook); #endif /* CONFIG_NET_DIVERT */ + #ifdef CONFIG_INET /* Internet layer registration */ diff -Naur linux-2.4.9/net/packet/af_packet.c linux-2.4.9-dv/net/packet/af_packet.c --- linux-2.4.9/net/packet/af_packet.c Tue Aug 7 15:30:50 2001 +++ linux-2.4.9-dv/net/packet/af_packet.c Sun Sep 23 15:48:55 2001 @@ -69,7 +69,7 @@ #include #include -#ifdef CONFIG_NET_DIVERT +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) #include #endif /* CONFIG_NET_DIVERT */ @@ -1492,11 +1492,21 @@ case SIOCGIFDIVERT: case SIOCSIFDIVERT: -#ifdef CONFIG_NET_DIVERT - return divert_ioctl(cmd, (struct divert_cf *) arg); -#else +#if defined(CONFIG_NET_DIVERT) || defined(CONFIG_NET_DIVERT_MODULE) +#ifdef CONFIG_INET +#ifdef CONFIG_KMOD + if (divert_ioctl_hook == NULL) + request_module("dv"); +#endif /* CONFIG_KMOD */ + if (divert_ioctl_hook != NULL) + return(divert_ioctl_hook(cmd, (struct divert_cf *) arg)); + else + return -ENOPKG; +#endif /* CONFIG_INET */ return -ENOPKG; -#endif /* CONFIG_NET_DIVERT */ +#endif /* CONFIG_NET_DIVERT */ + return -ENOPKG; + #ifdef CONFIG_INET case SIOCADDRT: