datapath: Add support for tunnel fragmentation.
authorJesse Gross <jesse@nicira.com>
Mon, 16 Aug 2010 14:32:41 +0000 (10:32 -0400)
committerJesse Gross <jesse@nicira.com>
Tue, 24 Aug 2010 19:17:29 +0000 (15:17 -0400)
Up until now it was assumed that encapsulated packets larger than
the MTU would be fragmented by the IP stack.  However, some
tunneling protocols provide their own fragmentation mechanism.  This
adds the necessary support to the generic tunnel code to support
fragmentation.

Signed-off-by: Jesse Gross <jesse@nicira.com>
datapath/tunnel.c
datapath/tunnel.h
datapath/vport-gre.c

index 3f25c9b4ad3fcc28efa4dd5dd02e49a3b19d0741..6fa369be0c49628d4cfd43c12a4e68b8eddb07f0 100644 (file)
@@ -680,29 +680,48 @@ static int build_packet(struct vport *vport, const struct tnl_mutable_config *mu
        new_iph->frag_off = frag_off;
        ip_select_ident(new_iph, &rt_dst(rt), NULL);
 
-       tnl_vport->tnl_ops->build_header(skb, vport, mutable);
+       memset(&IPCB(skb)->opt, 0, sizeof(IPCB(skb)->opt));
+       IPCB(skb)->flags = 0;
 
-       /* Allow our local IP stack to fragment the outer packet even if the
-        * DF bit is set as a last resort. */
-       skb->local_df = 1;
+       skb = tnl_vport->tnl_ops->build_header(skb, vport, mutable, &rt_dst(rt));
+       if (unlikely(!skb))
+               goto error;
 
-       memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
-       IPCB(skb)->flags = 0;
+       while (skb) {
+               struct sk_buff *next = skb->next;
+               int frag_len = skb->len - mutable->tunnel_hlen;
 
-       err = ip_local_out(skb);
-       if (likely(net_xmit_eval(err) == 0))
-               return orig_len;
-       else {
-               vport_record_error(vport, VPORT_E_TX_ERROR);
-               return 0;
-       }
+               skb->next = NULL;
+
+               err = ip_local_out(skb);
+               if (unlikely(net_xmit_eval(err) != 0)) {
+                       orig_len -= frag_len;
+                       skb = next;
+                       goto free_frags;
+               }
+
+               skb = next;
+       };
+
+       return orig_len;
 
 error_free:
        kfree_skb(skb);
 error:
-       vport_record_error(vport, VPORT_E_TX_DROPPED);
-
        return 0;
+free_frags:
+       /*
+        * There's no point in continuing to send fragments once one has been
+        * dropped so just free the rest.  This may help improve the congestion
+        * that caused the first packet to be dropped.
+        */
+       while (skb) {
+               struct sk_buff *next = skb->next;
+               orig_len -= skb->len - mutable->tunnel_hlen;
+               kfree_skb(skb);
+               skb = next;
+       };
+       return orig_len;
 }
 
 int tnl_send(struct vport *vport, struct sk_buff *skb)
@@ -847,6 +866,9 @@ int tnl_send(struct vport *vport, struct sk_buff *skb)
                skb = next_skb;
        } while (skb);
 
+       if (unlikely(orig_len == 0))
+               vport_record_error(vport, VPORT_E_TX_DROPPED);
+
        return orig_len;
 
 error_free:
@@ -914,6 +936,7 @@ struct vport *tnl_create(const char *name, const void __user *config,
 {
        struct vport *vport;
        struct tnl_vport *tnl_vport;
+       int initial_frag_id;
        int err;
 
        vport = vport_alloc(sizeof(struct tnl_vport), vport_ops);
@@ -936,6 +959,9 @@ struct vport *tnl_create(const char *name, const void __user *config,
        vport_gen_rand_ether_addr(tnl_vport->mutable->eth_addr);
        tnl_vport->mutable->mtu = ETH_DATA_LEN;
 
+       get_random_bytes(&initial_frag_id, sizeof(int));
+       atomic_set(&tnl_vport->frag_id, initial_frag_id);
+
        err = set_config(config, tnl_ops, NULL, tnl_vport->mutable);
        if (err)
                goto error_free_mutable;
index 89e73bac67677ea46df8289cedb3b8e258767df5..92963d766b7d9917042507c1a4f3fe4b2b3cc843 100644 (file)
 #include "table.h"
 #include "vport.h"
 
-/* The absolute minimum fragment size.  Note that there are many other
- * definitions of the minimum MTU. */
+/*
+ * The absolute minimum fragment size.  Note that there are many other
+ * definitions of the minimum MTU.
+ */
 #define IP_MIN_MTU 68
 
 /*
@@ -47,9 +49,24 @@ struct tnl_ops {
        u32 tunnel_type;
        u8 ipproto;
 
+       /*
+        * Returns the length of the tunnel header you will add in
+        * build_header() (i.e. excludes the IP header).  Returns a negative
+        * error code if the configuration is invalid.
+        */
        int (*hdr_len)(const struct tnl_port_config *);
-       void (*build_header)(struct sk_buff *, const struct vport *,
-                            const struct tnl_mutable_config *);
+
+       /*
+        * Returns a linked list of SKBs with tunnel headers (multiple
+        * packets may be generated in the event of fragmentation).  Space
+        * will have already been allocated at the start of the packet equal
+        * to sizeof(struct iphdr) + value returned by hdr_len().  The IP
+        * header will have already been constructed.
+        */
+       struct sk_buff *(*build_header)(struct sk_buff *,
+                                       const struct vport *,
+                                       const struct tnl_mutable_config *,
+                                       struct dst_entry *);
 };
 
 struct tnl_vport {
@@ -61,6 +78,8 @@ struct tnl_vport {
 
        /* Protected by RCU. */
        struct tnl_mutable_config *mutable;
+
+       atomic_t frag_id;
 };
 
 int tnl_init(void);
index 31d2d4f621efb317131dd5c355d8d79ac71dd0f8..223644e27194fecbe8e918300554102d765816c0 100644 (file)
@@ -48,9 +48,10 @@ static int gre_hdr_len(const struct tnl_port_config *port_config)
        return len;
 }
 
-static void gre_build_header(struct sk_buff *skb,
-                            const struct vport *vport,
-                            const struct tnl_mutable_config *mutable)
+static struct sk_buff *gre_build_header(struct sk_buff *skb,
+                                       const struct vport *vport,
+                                       const struct tnl_mutable_config *mutable,
+                                       struct dst_entry *dst)
 {
        struct gre_base_hdr *greh = (struct gre_base_hdr *)skb_transport_header(skb);
        __be32 *options = (__be32 *)(skb_network_header(skb) + mutable->tunnel_hlen
@@ -81,6 +82,14 @@ static void gre_build_header(struct sk_buff *skb,
                                                skb->len - sizeof(struct iphdr),
                                                0));
        }
+
+       /*
+        * Allow our local IP stack to fragment the outer packet even if the
+        * DF bit is set as a last resort.
+        */
+       skb->local_df = 1;
+
+       return skb;
 }
 
 static int parse_header(struct iphdr *iph, __be16 *flags, __be32 *key)