summaryrefslogtreecommitdiff
path: root/drivers/pptp/gre.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pptp/gre.c')
-rw-r--r--drivers/pptp/gre.c220
1 files changed, 220 insertions, 0 deletions
diff --git a/drivers/pptp/gre.c b/drivers/pptp/gre.c
new file mode 100644
index 0000000..77886d5
--- /dev/null
+++ b/drivers/pptp/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);