udp: Add GRO/GSO support
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
diff --git a/include/linux/netdev_features.h b/include/linux/netdev_features.h
index 1d4737c..5eb77de 100644
--- a/include/linux/netdev_features.h
+++ b/include/linux/netdev_features.h
@@ -55,8 +55,9 @@ enum {
NETIF_F_GSO_TUNNEL_REMCSUM_BIT, /* ... TUNNEL with TSO & REMCSUM */
NETIF_F_GSO_SCTP_BIT, /* ... SCTP fragmentation */
NETIF_F_GSO_ESP_BIT, /* ... ESP with TSO */
+ NETIF_F_GSO_UDP_BYFRAGS_BIT, /* ... UDP generic */
/**/NETIF_F_GSO_LAST = /* last bit, see GSO_MASK */
- NETIF_F_GSO_ESP_BIT,
+ NETIF_F_GSO_UDP_BYFRAGS_BIT,
NETIF_F_FCOE_CRC_BIT, /* FCoE CRC32 */
NETIF_F_SCTP_CRC_BIT, /* SCTP checksum offload */
@@ -133,6 +134,7 @@ enum {
#define NETIF_F_GSO_TUNNEL_REMCSUM __NETIF_F(GSO_TUNNEL_REMCSUM)
#define NETIF_F_GSO_SCTP __NETIF_F(GSO_SCTP)
#define NETIF_F_GSO_ESP __NETIF_F(GSO_ESP)
+#define NETIF_F_GSO_UDP_BYFRAGS __NETIF_F(GSO_UDP_BYFRAGS)
#define NETIF_F_HW_VLAN_STAG_FILTER __NETIF_F(HW_VLAN_STAG_FILTER)
#define NETIF_F_HW_VLAN_STAG_RX __NETIF_F(HW_VLAN_STAG_RX)
#define NETIF_F_HW_VLAN_STAG_TX __NETIF_F(HW_VLAN_STAG_TX)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index d707afd..6dc3221 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -4109,6 +4109,7 @@ static inline bool net_gso_ok(netdev_features_t features, int gso_type)
BUILD_BUG_ON(SKB_GSO_TUNNEL_REMCSUM != (NETIF_F_GSO_TUNNEL_REMCSUM >> NETIF_F_GSO_SHIFT));
BUILD_BUG_ON(SKB_GSO_SCTP != (NETIF_F_GSO_SCTP >> NETIF_F_GSO_SHIFT));
BUILD_BUG_ON(SKB_GSO_ESP != (NETIF_F_GSO_ESP >> NETIF_F_GSO_SHIFT));
+ BUILD_BUG_ON(SKB_GSO_UDP_BYFRAGS != (NETIF_F_GSO_UDP_BYFRAGS >> NETIF_F_GSO_SHIFT));
return (features & feature) == feature;
}
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index a17e235..c39ff4b 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -496,6 +496,8 @@ enum {
SKB_GSO_SCTP = 1 << 15,
SKB_GSO_ESP = 1 << 16,
+
+ SKB_GSO_UDP_BYFRAGS = 1 << 17,
};
#if BITS_PER_LONG > 32
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index be26034..93c171d 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3217,6 +3217,9 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
if (unlikely(!proto))
return ERR_PTR(-EINVAL);
+ if (skb_shinfo(head_skb)->gso_type & SKB_GSO_UDP_BYFRAGS)
+ return skb_segment_fraglist(head_skb, doffset);
+
sg = !!(features & NETIF_F_SG);
csum = !!can_checksum_protocol(features, proto);
diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index 7812501..fe704fb 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -248,10 +248,73 @@ static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
* inet_gso_segment()
*/
segs = skb_segment(skb, features);
+
out:
return segs;
}
+static struct sk_buff *udp4_gso_segment(struct sk_buff *skb,
+ netdev_features_t features)
+{
+ struct sk_buff *segs = ERR_PTR(-EINVAL);
+
+ if (!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP_BYFRAGS))
+ return udp4_ufo_fragment(skb, features);
+
+ if (!pskb_may_pull(skb, sizeof(struct udphdr)))
+ goto out;
+
+ segs = skb_segment(skb, features);
+
+out:
+ return segs;
+}
+
+
+static struct sk_buff **udp_gro_ffwd_receive(struct sk_buff **head, struct sk_buff *skb,
+ struct udphdr *uh)
+{
+ struct sk_buff *p = NULL;
+ struct sk_buff **pp = NULL;
+ struct udphdr *uh2;
+ unsigned int off = skb_gro_offset(skb);
+ int flush = 0;
+
+ for (; (p = *head); head = &p->next) {
+
+ if (!NAPI_GRO_CB(p)->same_flow)
+ continue;
+
+ uh2 = (struct udphdr *)(p->data + off);
+
+ /* Match ports and either checksums are either both zero
+ * or nonzero.
+ */
+ if ((*(u32 *)&uh->source != *(u32 *)&uh2->source) ||
+ (!uh->check ^ !uh2->check)) {
+ NAPI_GRO_CB(p)->same_flow = 0;
+ continue;
+ }
+ goto found;
+ }
+
+ goto out;
+
+found:
+ p = *head;
+
+ if (skb_gro_receive(head, skb)) {
+ flush = 1;
+ }
+
+out:
+ if (p && (!NAPI_GRO_CB(skb)->same_flow || flush))
+ pp = head;
+
+ NAPI_GRO_CB(skb)->flush |= flush;
+ return pp;
+}
+
struct sk_buff **udp_gro_receive(struct sk_buff **head, struct sk_buff *skb,
struct udphdr *uh, udp_lookup_t lookup)
{
@@ -320,6 +383,9 @@ static struct sk_buff **udp4_gro_receive(struct sk_buff **head,
if (NAPI_GRO_CB(skb)->flush)
goto skip;
+ if (NAPI_GRO_CB(skb)->is_ffwd)
+ return udp_gro_ffwd_receive(head, skb, uh);
+
if (skb_gro_checksum_validate_zero_check(skb, IPPROTO_UDP, uh->check,
inet_gro_compute_pseudo))
goto flush;
@@ -335,6 +401,13 @@ static struct sk_buff **udp4_gro_receive(struct sk_buff **head,
return NULL;
}
+static int udp_gro_ffwd_complete(struct sk_buff *skb, int nhoff)
+{
+ skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_BYFRAGS;
+
+ return 0;
+}
+
int udp_gro_complete(struct sk_buff *skb, int nhoff,
udp_lookup_t lookup)
{
@@ -369,6 +442,9 @@ static int udp4_gro_complete(struct sk_buff *skb, int nhoff)
const struct iphdr *iph = ip_hdr(skb);
struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
+ if (NAPI_GRO_CB(skb)->is_ffwd)
+ return udp_gro_ffwd_complete(skb, nhoff);
+
if (uh->check) {
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL_CSUM;
uh->check = ~udp_v4_check(skb->len - nhoff, iph->saddr,
@@ -382,7 +458,7 @@ static int udp4_gro_complete(struct sk_buff *skb, int nhoff)
static const struct net_offload udpv4_offload = {
.callbacks = {
- .gso_segment = udp4_ufo_fragment,
+ .gso_segment = udp4_gso_segment,
.gro_receive = udp4_gro_receive,
.gro_complete = udp4_gro_complete,
},