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