udp: Generalize skb_udp_segment
authorTom Herbert <therbert@google.com>
Tue, 30 Sep 2014 03:22:29 +0000 (20:22 -0700)
committerDavid S. Miller <davem@davemloft.net>
Thu, 2 Oct 2014 01:35:51 +0000 (21:35 -0400)
skb_udp_segment is the function called from udp4_ufo_fragment to
segment a UDP tunnel packet. This function currently assumes
segmentation is transparent Ethernet bridging (i.e. VXLAN
encapsulation). This patch generalizes the function to
operate on either Ethertype or IP protocol.

The inner_protocol field must be set to the protocol of the inner
header. This can now be either an Ethertype or an IP protocol
(in a union). A new flag in the skbuff indicates which type is
effective. skb_set_inner_protocol and skb_set_inner_ipproto
helper functions were added to set the inner_protocol. These
functions are called from the point where the tunnel encapsulation
is occuring.

When skb_udp_tunnel_segment is called, the function to segment the
inner packet is selected based on the inner IP or Ethertype. In the
case of an IP protocol encapsulation, the function is derived from
inet[6]_offloads. In the case of Ethertype, skb->protocol is
set to the inner_protocol and skb_mac_gso_segment is called. (GRE
currently does this, but it might be possible to lookup the protocol
in offload_base and call the appropriate segmenation function
directly).

Signed-off-by: Tom Herbert <therbert@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/skbuff.h
include/net/udp.h
net/ipv4/udp_offload.c
net/ipv6/udp_offload.c

index d8f7d74d5a4db5fb0a7fb8df71c2d56157b92578..7c5036d11feb93f42a6a64bd36616a50a45b0d71 100644 (file)
@@ -596,7 +596,8 @@ struct sk_buff {
        __u8                    ndisc_nodetype:2;
 #endif
        __u8                    ipvs_property:1;
-       /* 5 or 7 bit hole */
+       __u8                    inner_protocol_type:1;
+       /* 4 or 6 bit hole */
 
 #ifdef CONFIG_NET_SCHED
        __u16                   tc_index;       /* traffic control index */
@@ -632,7 +633,11 @@ struct sk_buff {
                __u32           reserved_tailroom;
        };
 
-       __be16                  inner_protocol;
+       union {
+               __be16          inner_protocol;
+               __u8            inner_ipproto;
+       };
+
        __u16                   inner_transport_header;
        __u16                   inner_network_header;
        __u16                   inner_mac_header;
@@ -1762,6 +1767,23 @@ static inline void skb_reserve(struct sk_buff *skb, int len)
        skb->tail += len;
 }
 
+#define ENCAP_TYPE_ETHER       0
+#define ENCAP_TYPE_IPPROTO     1
+
+static inline void skb_set_inner_protocol(struct sk_buff *skb,
+                                         __be16 protocol)
+{
+       skb->inner_protocol = protocol;
+       skb->inner_protocol_type = ENCAP_TYPE_ETHER;
+}
+
+static inline void skb_set_inner_ipproto(struct sk_buff *skb,
+                                        __u8 ipproto)
+{
+       skb->inner_ipproto = ipproto;
+       skb->inner_protocol_type = ENCAP_TYPE_IPPROTO;
+}
+
 static inline void skb_reset_inner_headers(struct sk_buff *skb)
 {
        skb->inner_mac_header = skb->mac_header;
index 16f4e80f0519c6dc0b180a9aec58c9231eda506e..07f9b70962f64f0a7e80fe1ebbc81996be2d4567 100644 (file)
@@ -239,7 +239,8 @@ int udp_ioctl(struct sock *sk, int cmd, unsigned long arg);
 int udp_disconnect(struct sock *sk, int flags);
 unsigned int udp_poll(struct file *file, struct socket *sock, poll_table *wait);
 struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
-                                      netdev_features_t features);
+                                      netdev_features_t features,
+                                      bool is_ipv6);
 int udp_lib_getsockopt(struct sock *sk, int level, int optname,
                       char __user *optval, int __user *optlen);
 int udp_lib_setsockopt(struct sock *sk, int level, int optname,
index 19ebe6a39ddcf37fc67e123a7e1f10cf3d6b7208..8c35f2c939ee55247e9c95d21bd4c9395628f8fa 100644 (file)
@@ -25,8 +25,11 @@ struct udp_offload_priv {
        struct udp_offload_priv __rcu *next;
 };
 
-struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
-                                      netdev_features_t features)
+static struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb,
+       netdev_features_t features,
+       struct sk_buff *(*gso_inner_segment)(struct sk_buff *skb,
+                                            netdev_features_t features),
+       __be16 new_protocol)
 {
        struct sk_buff *segs = ERR_PTR(-EINVAL);
        u16 mac_offset = skb->mac_header;
@@ -48,7 +51,7 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
        skb_reset_mac_header(skb);
        skb_set_network_header(skb, skb_inner_network_offset(skb));
        skb->mac_len = skb_inner_network_offset(skb);
-       skb->protocol = htons(ETH_P_TEB);
+       skb->protocol = new_protocol;
 
        need_csum = !!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM);
        if (need_csum)
@@ -56,7 +59,7 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
 
        /* segment inner packet. */
        enc_features = skb->dev->hw_enc_features & netif_skb_features(skb);
-       segs = skb_mac_gso_segment(skb, enc_features);
+       segs = gso_inner_segment(skb, enc_features);
        if (IS_ERR_OR_NULL(segs)) {
                skb_gso_error_unwind(skb, protocol, tnl_hlen, mac_offset,
                                     mac_len);
@@ -101,6 +104,44 @@ out:
        return segs;
 }
 
+struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
+                                      netdev_features_t features,
+                                      bool is_ipv6)
+{
+       __be16 protocol = skb->protocol;
+       const struct net_offload **offloads;
+       const struct net_offload *ops;
+       struct sk_buff *segs = ERR_PTR(-EINVAL);
+       struct sk_buff *(*gso_inner_segment)(struct sk_buff *skb,
+                                            netdev_features_t features);
+
+       rcu_read_lock();
+
+       switch (skb->inner_protocol_type) {
+       case ENCAP_TYPE_ETHER:
+               protocol = skb->inner_protocol;
+               gso_inner_segment = skb_mac_gso_segment;
+               break;
+       case ENCAP_TYPE_IPPROTO:
+               offloads = is_ipv6 ? inet6_offloads : inet_offloads;
+               ops = rcu_dereference(offloads[skb->inner_ipproto]);
+               if (!ops || !ops->callbacks.gso_segment)
+                       goto out_unlock;
+               gso_inner_segment = ops->callbacks.gso_segment;
+               break;
+       default:
+               goto out_unlock;
+       }
+
+       segs = __skb_udp_tunnel_segment(skb, features, gso_inner_segment,
+                                       protocol);
+
+out_unlock:
+       rcu_read_unlock();
+
+       return segs;
+}
+
 static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
                                         netdev_features_t features)
 {
@@ -113,7 +154,7 @@ static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
        if (skb->encapsulation &&
            (skb_shinfo(skb)->gso_type &
             (SKB_GSO_UDP_TUNNEL|SKB_GSO_UDP_TUNNEL_CSUM))) {
-               segs = skb_udp_tunnel_segment(skb, features);
+               segs = skb_udp_tunnel_segment(skb, features, false);
                goto out;
        }
 
index 212ebfc7973f2c4c2fa4f79a22bc9b6c0649f79a..8f96988c1db2ee9fb5fb0c012d4dfd3a0de1163c 100644 (file)
@@ -58,7 +58,7 @@ static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,
 
        if (skb->encapsulation && skb_shinfo(skb)->gso_type &
            (SKB_GSO_UDP_TUNNEL|SKB_GSO_UDP_TUNNEL_CSUM))
-               segs = skb_udp_tunnel_segment(skb, features);
+               segs = skb_udp_tunnel_segment(skb, features, true);
        else {
                const struct ipv6hdr *ipv6h;
                struct udphdr *uh;