Signed-off-by: Pravin B Shelar <pshe...@nicira.com>
---
 include/linux/skbuff.h |   12 ++++++
 net/ipv4/af_inet.c     |    1 +
 net/ipv4/gre.c         |   95 ++++++++++++++++++++++++++++++++++++++++++++++++
 net/ipv4/ip_gre.c      |    7 ++++
 net/ipv4/tcp.c         |    1 +
 net/ipv4/udp.c         |    3 +-
 net/ipv6/af_inet6.c    |    1 +
 net/ipv6/udp.c         |    3 +-
 8 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index f2af494..585aca7 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -307,6 +307,8 @@ enum {
        SKB_GSO_TCPV6 = 1 << 4,
 
        SKB_GSO_FCOE = 1 << 5,
+
+       SKB_GSO_GRE = 1 << 6,
 };
 
 #if BITS_PER_LONG > 32
@@ -787,6 +789,16 @@ static inline int skb_cloned(const struct sk_buff *skb)
               (atomic_read(&skb_shinfo(skb)->dataref) & SKB_DATAREF_MASK) != 1;
 }
 
+static inline int unclone_skb(struct sk_buff *skb, gfp_t pri)
+{
+       might_sleep_if(pri & __GFP_WAIT);
+
+       if (skb_cloned(skb))
+               return pskb_expand_head(skb, 0, 0, pri);
+
+       return 0;
+}
+
 /**
  *     skb_header_cloned - is the header a clone
  *     @skb: buffer to check
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index 766c596..55cecba 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1303,6 +1303,7 @@ static struct sk_buff *inet_gso_segment(struct sk_buff 
*skb,
                       SKB_GSO_UDP |
                       SKB_GSO_DODGY |
                       SKB_GSO_TCP_ECN |
+                      SKB_GSO_GRE |
                       0)))
                goto out;
 
diff --git a/net/ipv4/gre.c b/net/ipv4/gre.c
index b837551..ef83605 100644
--- a/net/ipv4/gre.c
+++ b/net/ipv4/gre.c
@@ -138,6 +138,14 @@ struct sk_buff *gre_build_header(struct sk_buff *skb,
        skb->local_df = 1;
        __ip_select_ident(ip_hdr(skb), dst, 0);
 
+       if (skb_is_gso(skb)) {
+               if (unlikely(unclone_skb(skb, GFP_ATOMIC))) {
+                       kfree_skb(skb);
+                       return NULL;
+               }
+               skb_shinfo(skb)->gso_type |= SKB_GSO_GRE;
+       }
+
        return skb;
 }
 EXPORT_SYMBOL(gre_build_header);
@@ -331,9 +339,96 @@ static void ipgre_err_v0(struct sk_buff *skb, u32 info)
        }
 }
 
+static int gre_header_len(struct gre_base_hdr *greh)
+{
+       int len = GRE_HEADER_SECTION;
+
+       if (greh->flags & GRE_KEY)
+               len += GRE_HEADER_SECTION;
+       if (greh->flags & GRE_CSUM)
+               len += GRE_HEADER_SECTION;
+
+       return len;
+}
+
+static struct sk_buff *gre_gso_segment(struct sk_buff *skb,
+                                      netdev_features_t features)
+{
+       struct sk_buff *segs = ERR_PTR(-EINVAL);
+       struct gre_base_hdr *greh;
+       unsigned char *mac = skb_mac_header(skb);
+       int mac_len = skb->mac_len;
+       int net_hlen = skb_network_header_len(skb);
+       int doffset;
+       int ghl;
+
+       if (unlikely(skb_shinfo(skb)->gso_type &
+                               ~(SKB_GSO_TCPV4 |
+                                       SKB_GSO_UDP |
+                                       SKB_GSO_DODGY |
+                                       SKB_GSO_TCP_ECN |
+                                       SKB_GSO_GRE |
+                                       0)))
+               goto out;
+
+       if (unlikely(!pskb_may_pull(skb, sizeof(*greh))))
+               goto out;
+
+       greh = (struct gre_base_hdr *)skb_transport_header(skb);
+       ghl = gre_header_len(greh);
+
+       if (unlikely(!pskb_may_pull(skb, ghl)))
+               goto out;
+
+       __skb_pull(skb, ghl);
+       skb_reset_mac_header(skb);
+       skb_set_network_header(skb, skb->mac_len);
+       doffset = skb_mac_header(skb) - mac;
+       /* segment inner packet. */
+       segs = skb_gso_segment(skb, 0);
+       if (!segs || IS_ERR(segs))
+               goto out;
+
+       skb = segs;
+       do {
+               unsigned char *smac;
+
+               skb_push(skb, doffset);
+
+               skb_reset_mac_header(skb);
+               skb_set_network_header(skb, mac_len);
+               skb_set_transport_header(skb,
+                                        skb_network_offset(skb) + net_hlen);
+               smac = skb_mac_header(skb);
+               skb->mac_len = mac_len;
+               /* Copy entire outer header from original skb. */
+               memmove(smac, mac, doffset);
+
+               greh = (struct gre_base_hdr *)skb_transport_header(skb);
+               if (greh->flags & GRE_CSUM) {
+                       __be32 *gre_csum = (__be32 *)(greh + 1);
+                       *gre_csum = 0;
+                       *(__sum16 *)gre_csum = csum_fold(skb_checksum(skb,
+                                               skb_transport_offset(skb),
+                                               skb->len - 
skb_transport_offset(skb),
+                                               0));
+               }
+       } while ((skb = skb->next));
+
+out:
+       return segs;
+}
+
+static int gre_gso_send_check(struct sk_buff *skb)
+{
+       return 0;
+}
+
 static const struct net_protocol net_gre_protocol = {
        .handler     = gre_rcv,
        .err_handler = gre_err,
+       .gso_send_check = gre_gso_send_check,
+       .gso_segment    = gre_gso_segment,
        .netns_ok    = 1,
 };
 
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 243be2f..c8133e2 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -469,7 +469,14 @@ static int ipgre_tunnel_init(struct net_device *dev)
 
 static int ipgre_tap_init(struct net_device *dev)
 {
+       struct ip_tunnel *tunnel = netdev_priv(dev);
        __gre_tunnel_init(dev);
+
+       if (!(tunnel->parms.o_flags & TUNNEL_SEQ)) {
+               dev->features           |= NETIF_F_TSO;
+               dev->hw_features        |= NETIF_F_TSO;
+       }
+
        return ip_tunnel_init(dev);
 }
 
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index eace049..362f447 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -3022,6 +3022,7 @@ struct sk_buff *tcp_tso_segment(struct sk_buff *skb,
                               SKB_GSO_DODGY |
                               SKB_GSO_TCP_ECN |
                               SKB_GSO_TCPV6 |
+                              SKB_GSO_GRE |
                               0) ||
                             !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))
                        goto out;
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 79c8dbe..c256008 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -2279,7 +2279,8 @@ struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
                /* Packet is from an untrusted source, reset gso_segs. */
                int type = skb_shinfo(skb)->gso_type;
 
-               if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
+               if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY |
+                                     SKB_GSO_GRE) ||
                             !(type & (SKB_GSO_UDP))))
                        goto out;
 
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index a974247..820fc1f 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -780,6 +780,7 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
                       SKB_GSO_DODGY |
                       SKB_GSO_TCP_ECN |
                       SKB_GSO_TCPV6 |
+                      SKB_GSO_GRE |
                       0)))
                goto out;
 
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index fc99972..e247a2b 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -1383,7 +1383,8 @@ static struct sk_buff *udp6_ufo_fragment(struct sk_buff 
*skb,
                /* Packet is from an untrusted source, reset gso_segs. */
                int type = skb_shinfo(skb)->gso_type;
 
-               if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
+               if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY |
+                                     SKB_GSO_GRE) ||
                             !(type & (SKB_GSO_UDP))))
                        goto out;
 
-- 
1.7.10

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to