diff options
author | Kozlov Dmitry <dima@server> | 2010-10-06 16:55:05 +0400 |
---|---|---|
committer | Kozlov Dmitry <dima@server> | 2010-10-06 16:55:05 +0400 |
commit | 45b3c9c5bdd896f51f47e29069e3c030ddb17d51 (patch) | |
tree | cbec5824ffb2eee20b98ad9892a357304384ff01 /driver/gre.c | |
parent | ba3db9f17477ea4b49c266c5cb50f63f3b074db2 (diff) | |
parent | 01ccd98495c9da1e79f7867bf52416b23f20200d (diff) | |
download | accel-ppp-xebd-45b3c9c5bdd896f51f47e29069e3c030ddb17d51.tar.gz accel-ppp-xebd-45b3c9c5bdd896f51f47e29069e3c030ddb17d51.zip |
merged branch accel-pptpd
Diffstat (limited to 'driver/gre.c')
-rw-r--r-- | driver/gre.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/driver/gre.c b/driver/gre.c new file mode 100644 index 0000000..77886d5 --- /dev/null +++ b/driver/gre.c @@ -0,0 +1,220 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/in.h> +#include <linux/netdevice.h> +#include <linux/version.h> +#include <linux/spinlock.h> +#include <net/protocol.h> + +#include "gre.h" + +struct gre_protocol *gre_proto[GREPROTO_MAX] ____cacheline_aligned_in_smp; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static rwlock_t gre_proto_lock=RW_LOCK_UNLOCKED; +#else +static DEFINE_SPINLOCK(gre_proto_lock); +#endif + +int gre_add_protocol(struct gre_protocol *proto, u8 version) +{ + int ret; + + if (version >= GREPROTO_MAX) + return -EINVAL; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + write_lock_bh(&gre_proto_lock); +#else + spin_lock(&gre_proto_lock); +#endif + if (gre_proto[version]) { + ret = -EAGAIN; + } else { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + gre_proto[version] = proto; +#else + rcu_assign_pointer(gre_proto[version], proto); +#endif + ret = 0; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + write_unlock_bh(&gre_proto_lock); +#else + spin_unlock(&gre_proto_lock); +#endif + + return ret; +} + +int gre_del_protocol(struct gre_protocol *proto, u8 version) +{ + if (version >= GREPROTO_MAX) + goto out_err; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + write_lock_bh(&gre_proto_lock); +#else + spin_lock(&gre_proto_lock); +#endif + if (gre_proto[version] == proto) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + gre_proto[version] = NULL; +#else + rcu_assign_pointer(gre_proto[version], NULL); +#endif + else + goto out_err_unlock; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + write_unlock_bh(&gre_proto_lock); +#else + spin_unlock(&gre_proto_lock); + synchronize_rcu(); +#endif + return 0; + +out_err_unlock: +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + write_unlock_bh(&gre_proto_lock); +#else + spin_unlock(&gre_proto_lock); +#endif +out_err: + return -EINVAL; +} + +static int gre_rcv(struct sk_buff *skb) +{ + u8 ver; + int ret; + struct gre_protocol *proto; + + if (!pskb_may_pull(skb, 12)) + goto drop_nolock; + + ver = skb->data[1]&0x7f; + if (ver >= GREPROTO_MAX) + goto drop_nolock; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + read_lock(&gre_proto_lock); + proto = gre_proto[ver]; +#else + rcu_read_lock(); + proto = rcu_dereference(gre_proto[ver]); +#endif + if (!proto || !proto->handler) + goto drop; + + ret = proto->handler(skb); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + read_unlock(&gre_proto_lock); +#else + rcu_read_unlock(); +#endif + + return ret; + +drop: +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + read_unlock(&gre_proto_lock); +#else + rcu_read_unlock(); +#endif +drop_nolock: + kfree_skb(skb); + return NET_RX_DROP; +} + +static void gre_err(struct sk_buff *skb, u32 info) +{ + u8 ver; + struct gre_protocol *proto; + + if (!pskb_may_pull(skb, 12)) + goto drop_nolock; + + ver=skb->data[1]&0x7f; + if (ver>=GREPROTO_MAX) + goto drop_nolock; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + read_lock(&gre_proto_lock); + proto = gre_proto[ver]; +#else + rcu_read_lock(); + proto = rcu_dereference(gre_proto[ver]); +#endif + if (!proto || !proto->err_handler) + goto drop; + + proto->err_handler(skb, info); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + read_unlock(&gre_proto_lock); +#else + rcu_read_unlock(); +#endif + + return; + +drop: +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + read_unlock(&gre_proto_lock); +#else + rcu_read_unlock(); +#endif +drop_nolock: + kfree_skb(skb); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static struct inet_protocol net_gre_protocol = { + .handler = gre_rcv, + .err_handler = gre_err, + .protocol = IPPROTO_GRE, + .name = "GRE", +}; +#else +static struct net_protocol net_gre_protocol = { + .handler = gre_rcv, + .err_handler = gre_err, +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,24) + .netns_ok=1, +#endif +}; +#endif + +static int __init gre_init(void) +{ + printk(KERN_INFO "GRE over IPv4 demultiplexor driver"); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + inet_add_protocol(&net_gre_protocol); +#else + if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) { + printk(KERN_INFO "gre: can't add protocol\n"); + return -EAGAIN; + } +#endif + return 0; +} + +static void __exit gre_exit(void) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + inet_del_protocol(&net_gre_protocol); +#else + inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); +#endif +} + +module_init(gre_init); +module_exit(gre_exit); + +MODULE_DESCRIPTION("GRE over IPv4 demultiplexor driver"); +MODULE_AUTHOR("Kozlov D. (xeb@mail.ru)"); +MODULE_LICENSE("GPL"); +EXPORT_SYMBOL_GPL(gre_add_protocol); +EXPORT_SYMBOL_GPL(gre_del_protocol); |