Currently only one protocol handler of GREPROTO_CISCO protocol
is allowed. Soon we will have ovs tunnel registering for same protocol
as GRE device.
Following patch extends GRE de-multiplexer so that it can multiple GRE
modules can register GRE protocol handler.

Signed-off-by: Pravin B Shelar <[email protected]>
---
 include/net/gre.h  |   17 +++
 include/net/ipip.h |    9 ++
 net/ipv4/gre.c     |  276 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 302 insertions(+), 0 deletions(-)

diff --git a/include/net/gre.h b/include/net/gre.h
index 8266547..1eb3166 100644
--- a/include/net/gre.h
+++ b/include/net/gre.h
@@ -2,6 +2,7 @@
 #define __LINUX_GRE_H
 
 #include <linux/skbuff.h>
+#include <net/ipip.h>
 
 #define GREPROTO_CISCO         0
 #define GREPROTO_PPTP          1
@@ -15,4 +16,20 @@ struct gre_protocol {
 int gre_add_protocol(const struct gre_protocol *proto, u8 version);
 int gre_del_protocol(const struct gre_protocol *proto, u8 version);
 
+struct gre_protocol_v0 {
+       int (*handler)(struct sk_buff *skb, const struct tnl_ptk_info *tpi);
+       int (*err_handler)(struct sk_buff *skb, u32 info,
+                          const struct tnl_ptk_info *tpi);
+};
+
+int gre_add_protocol_v0(const struct gre_protocol_v0 *proto, u8 priority);
+int gre_del_protocol_v0(const struct gre_protocol_v0 *proto, u8 priority);
+
+void gre_update_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi);
+struct gre_base_hdr {
+       __be16 flags;
+       __be16 protocol;
+};
+#define GRE_HEADER_SECTION 4
+
 #endif
diff --git a/include/net/ipip.h b/include/net/ipip.h
index ddc077c..7de2500 100644
--- a/include/net/ipip.h
+++ b/include/net/ipip.h
@@ -41,6 +41,15 @@ struct ip_tunnel {
        struct gro_cells                gro_cells;
 };
 
+struct tnl_ptk_info {
+       __be16 flags;
+       __be16 proto;
+       __be32 key;
+       __be32 seq;
+       int hdr_len;
+       __sum16 csum;
+};
+
 struct ip_tunnel_prl_entry {
        struct ip_tunnel_prl_entry __rcu *next;
        __be32                          addr;
diff --git a/net/ipv4/gre.c b/net/ipv4/gre.c
index 5a903dc..f62725e 100644
--- a/net/ipv4/gre.c
+++ b/net/ipv4/gre.c
@@ -16,15 +16,21 @@
 #include <linux/kernel.h>
 #include <linux/kmod.h>
 #include <linux/skbuff.h>
+#include <linux/if.h>
+#include <linux/icmp.h>
 #include <linux/in.h>
 #include <linux/ip.h>
+#include <linux/if_tunnel.h>
 #include <linux/netdevice.h>
 #include <linux/spinlock.h>
 #include <net/protocol.h>
 #include <net/gre.h>
+#include <net/icmp.h>
 
+#define GREPROTO_V0_MAX 2
 
 static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly;
+static const struct gre_protocol_v0 __rcu *gre_proto_v0[GREPROTO_V0_MAX] 
__read_mostly;
 
 int gre_add_protocol(const struct gre_protocol *proto, u8 version)
 {
@@ -104,12 +110,269 @@ static void gre_err(struct sk_buff *skb, u32 info)
        rcu_read_unlock();
 }
 
+int gre_add_protocol_v0(const struct gre_protocol_v0 *proto, u8 priority)
+{
+       ASSERT_RTNL();
+       if (priority >= GREPROTO_V0_MAX)
+               goto err_out;
+
+       if (gre_proto_v0[priority])
+               goto err_out;
+
+       RCU_INIT_POINTER(gre_proto_v0[priority], proto);
+       return 0;
+
+err_out:
+       return -1;
+}
+EXPORT_SYMBOL_GPL(gre_add_protocol_v0);
+
+int gre_del_protocol_v0(const struct gre_protocol_v0 *proto, u8 priority)
+{
+       ASSERT_RTNL();
+       if (priority >= GREPROTO_V0_MAX)
+               goto err_out;
+
+       if (rtnl_dereference(gre_proto_v0[priority]) != proto)
+               goto err_out;
+       RCU_INIT_POINTER(gre_proto_v0[priority], NULL);
+       synchronize_rcu();
+       return 0;
+
+err_out:
+       return -1;
+}
+EXPORT_SYMBOL_GPL(gre_del_protocol_v0);
+
+void gre_update_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
+{
+       struct iphdr *iph = ip_hdr(skb);
+       struct gre_base_hdr *greh = (struct gre_base_hdr *)&iph[1];
+
+       greh->flags = tpi->flags;
+       greh->protocol = tpi->proto;
+
+       if (tpi->flags&(GRE_KEY|GRE_CSUM|GRE_SEQ)) {
+               __be32 *ptr = (__be32 *)(((u8 *)greh) + tpi->hdr_len - 4);
+
+               if (tpi->flags&GRE_SEQ) {
+                       *ptr = tpi->seq;
+                       ptr--;
+               }
+               if (tpi->flags&GRE_KEY) {
+                       *ptr = tpi->key;
+                       ptr--;
+               }
+               if (tpi->flags&GRE_CSUM) {
+                       *(__sum16 *)ptr = 0;
+                       *(__sum16 *)ptr = csum_fold(skb_checksum(skb,
+                                               skb_transport_offset(skb),
+                                               skb->len - 
skb_transport_offset(skb),
+                                               0));
+               }
+       }
+}
+EXPORT_SYMBOL(gre_update_header);
+
+
+static __sum16 check_checksum(struct sk_buff *skb)
+{
+       struct iphdr *iph = ip_hdr(skb);
+       struct gre_base_hdr *greh = (struct gre_base_hdr *)(iph + 1);
+       __sum16 csum = 0;
+
+       if (greh->flags & GRE_CSUM) {
+               switch (skb->ip_summed) {
+               case CHECKSUM_COMPLETE:
+                       csum = csum_fold(skb->csum);
+
+                       if (!csum)
+                               break;
+                       /* Fall through. */
+
+               case CHECKSUM_NONE:
+                       skb->csum = 0;
+                       csum = __skb_checksum_complete(skb);
+                       skb->ip_summed = CHECKSUM_COMPLETE;
+                       break;
+               }
+       }
+
+       return csum;
+}
+
+static int parse_gre_header(struct sk_buff *skb, struct tnl_ptk_info *tpi)
+{
+       struct gre_base_hdr *greh = (struct gre_base_hdr *)skb->data;
+       __be32 *options = (__be32 *)(greh + 1);
+
+       if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
+               return -EINVAL;
+
+       tpi->flags = greh->flags;
+       tpi->proto = greh->protocol;
+
+       tpi->hdr_len = GRE_HEADER_SECTION;
+       tpi->csum = check_checksum(skb);
+
+       if (tpi->csum)
+               return -EINVAL;
+
+       if (greh->flags & GRE_CSUM) {
+               tpi->hdr_len += GRE_HEADER_SECTION;
+               options++;
+       }
+
+       if (greh->flags & GRE_KEY) {
+               if ((void *)(options + 1) > (void *)skb_tail_pointer(skb))
+                       return -1;
+               tpi->hdr_len += GRE_HEADER_SECTION;
+               tpi->key = *options;
+               options++;
+       } else
+               tpi->key = 0;
+
+       if (unlikely(greh->flags & GRE_SEQ)) {
+               if ((void *) (options + 1) > (void *)skb_tail_pointer(skb))
+                       return -1;
+
+               tpi->seq = *options;
+               tpi->hdr_len += GRE_HEADER_SECTION;
+               options++;
+       } else
+               tpi->seq = 0;
+
+       /* WCCP version 1 and 2 protocol decoding.
+        * - Change protocol to IP
+        * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
+        */
+       if (tpi->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) {
+               tpi->proto = htons(ETH_P_IP);
+               if ((*(u8 *)options & 0xF0) != 0x40)
+                       tpi->hdr_len += 4;
+       }
+
+       return 0;
+}
+
+static int ipgre_rcv_v0(struct sk_buff *skb)
+{
+       struct tnl_ptk_info tpi;
+       int i;
+
+       if (!pskb_may_pull(skb, 16))
+               goto drop;
+
+       if (parse_gre_header(skb, &tpi) < 0)
+               goto drop;
+
+       for (i = 0; i < GREPROTO_V0_MAX; i++) {
+               const struct gre_protocol_v0 __rcu *proto = gre_proto_v0[i];
+
+               if (proto) {
+                       int ret;
+
+                       ret = proto->handler(skb, &tpi);
+                       if (ret <= 0)
+                               return ret;
+               }
+
+       }
+       icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+
+drop:
+       kfree_skb(skb);
+       return 0;
+}
+
+static void ipgre_err_v0(struct sk_buff *skb, u32 info)
+{
+
+       /* All the routers (except for Linux) return only
+        * 8 bytes of packet payload. It means, that precise relaying of
+        * ICMP in the real Internet is absolutely infeasible.
+        *
+        * Moreover, Cisco "wise men" put GRE key to the third word
+        * in GRE header. It makes impossible maintaining even soft
+        * state for keyed
+        * GRE tunnels with enabled checksum. Tell them "thank you".
+        *
+        * Well, I wonder, rfc1812 was written by Cisco employee,
+        * what the hell these idiots break standards established
+        * by themselves???
+        **/
+
+       const int type = icmp_hdr(skb)->type;
+       const int code = icmp_hdr(skb)->code;
+       struct tnl_ptk_info tpi;
+       int i;
+
+       if (!pskb_may_pull(skb, sizeof(struct gre_base_hdr) + ETH_HLEN))
+               return;
+
+       parse_gre_header(skb, &tpi);
+
+       if (tpi.csum)
+               return;
+
+       /* If only 8 bytes returned, keyed message will be dropped here */
+       if (tpi.flags & GRE_KEY) {
+               if ((tpi.flags & GRE_CSUM) && (tpi.hdr_len < 12))
+                       return;
+               if (tpi.hdr_len < 8)
+                       return;
+       }
+
+       switch (type) {
+       default:
+       case ICMP_PARAMETERPROB:
+               return;
+
+       case ICMP_DEST_UNREACH:
+               switch (code) {
+               case ICMP_SR_FAILED:
+               case ICMP_PORT_UNREACH:
+                       /* Impossible event. */
+               return;
+               default:
+                       /* All others are translated to HOST_UNREACH.
+                          rfc2003 contains "deep thoughts" about NET_UNREACH,
+                          I believe they are just ether pollution. --ANK
+                        */
+               break;
+               }
+               break;
+       case ICMP_TIME_EXCEEDED:
+               if (code != ICMP_EXC_TTL)
+                       return;
+               break;
+
+       case ICMP_REDIRECT:
+               break;
+       }
+
+       for (i = 0; i < GREPROTO_V0_MAX; i++) {
+               const struct gre_protocol_v0 __rcu *proto = gre_proto_v0[i];
+
+               if (proto) {
+                       if (proto->err_handler(skb, info, &tpi) <= 0)
+                               return;
+               }
+
+       }
+}
+
 static const struct net_protocol net_gre_protocol = {
        .handler     = gre_rcv,
        .err_handler = gre_err,
        .netns_ok    = 1,
 };
 
+static const struct gre_protocol ipgre_protocol = {
+       .handler     = ipgre_rcv_v0,
+       .err_handler = ipgre_err_v0,
+};
+
 static int __init gre_init(void)
 {
        pr_info("GRE over IPv4 demultiplexor driver\n");
@@ -118,12 +381,25 @@ static int __init gre_init(void)
                pr_err("can't add protocol\n");
                return -EAGAIN;
        }
+       rtnl_lock();
+       if (gre_add_protocol(&ipgre_protocol, GREPROTO_CISCO) < 0) {
+               pr_info("%s: can't add ipgre handler\n", __func__);
+               rtnl_unlock();
+               inet_del_protocol(&net_gre_protocol, IPPROTO_GRE);
+               return -EAGAIN;
+       }
+       rtnl_unlock();
 
        return 0;
 }
 
 static void __exit gre_exit(void)
 {
+       rtnl_lock();
+       if (gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO) < 0)
+               pr_info("%s: can't remove protocol\n", __func__);
+       rtnl_unlock();
+
        inet_del_protocol(&net_gre_protocol, IPPROTO_GRE);
 }
 
-- 
1.7.1

_______________________________________________
dev mailing list
[email protected]
http://openvswitch.org/mailman/listinfo/dev

Reply via email to