[ovs-dev] [PATCH 04/13] Upstream GRE: Add segmentation offload for GRE TAP device.
Pravin B Shelar
pshelar at nicira.com
Thu Nov 22 15:56:41 UTC 2012
From: Pravin Shelar <pshelar at nicira.com>
Signed-off-by: Pravin B Shelar <pshelar at nicira.com>
---
include/linux/skbuff.h | 12 +++++
net/ipv4/af_inet.c | 1 +
net/ipv4/gre.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++--
net/ipv4/ip_gre.c | 7 +++
net/ipv4/tcp.c | 1 +
net/ipv4/udp.c | 3 +-
net/ipv6/ip6_offload.c | 1 +
net/ipv6/udp_offload.c | 3 +-
8 files changed, 142 insertions(+), 6 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 24b384b..9a3d5d2 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1306,6 +1306,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..7568853 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,6 +339,91 @@ 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,
@@ -342,28 +435,48 @@ static const struct gre_protocol ipgre_protocol = {
.err_handler = ipgre_err_v0,
};
+static const struct net_offload gre_offload = {
+ .callbacks = {
+ .gso_send_check = gre_gso_send_check,
+ .gso_segment = gre_gso_segment,
+ },
+};
+
static int __init gre_init(void)
{
pr_info("GRE over IPv4 demultiplexor driver\n");
if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) {
pr_err("can't add protocol\n");
- return -EAGAIN;
+ goto err;
}
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;
+ goto err_gre;
}
rtnl_unlock();
+ if (inet_add_offload(&gre_offload, IPPROTO_GRE)) {
+ pr_err("can't add protocol offload\n");
+ goto err_gso;
+ }
return 0;
+err_gso:
+ rtnl_lock();
+ gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO);
+ rtnl_unlock();
+
+err_gre:
+ inet_del_protocol(&net_gre_protocol, IPPROTO_GRE);
+err:
+ return -EAGAIN;
}
static void __exit gre_exit(void)
{
+ inet_del_offload(&gre_offload, IPPROTO_GRE);
rtnl_lock();
if (gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO) < 0)
pr_info("%s: can't remove protocol\n", __func__);
@@ -378,4 +491,3 @@ module_exit(gre_exit);
MODULE_DESCRIPTION("GRE over IPv4 demultiplexer driver");
MODULE_AUTHOR("D. Kozlov (xeb at mail.ru)");
MODULE_LICENSE("GPL");
-
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 829fe3d..6b4e57f 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -594,7 +594,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 e6eace1..541f9b6 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -3024,6 +3024,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/ip6_offload.c b/net/ipv6/ip6_offload.c
index f26f0da..8234c1d 100644
--- a/net/ipv6/ip6_offload.c
+++ b/net/ipv6/ip6_offload.c
@@ -99,6 +99,7 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
~(SKB_GSO_UDP |
SKB_GSO_DODGY |
SKB_GSO_TCP_ECN |
+ SKB_GSO_GRE |
SKB_GSO_TCPV6 |
0)))
goto out;
diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c
index 0c8934a..cf05cf0 100644
--- a/net/ipv6/udp_offload.c
+++ b/net/ipv6/udp_offload.c
@@ -56,7 +56,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.1
More information about the dev
mailing list