learning-switch: Add support for OpenFlow queues.
[openvswitch] / datapath / vport-gre.c
index 40dd330ee257e778240f7873c01e5c3332c167f3..2bddc871093205c6442a24e4954c5bd538b47693 100644 (file)
@@ -22,7 +22,9 @@
 #include <net/icmp.h>
 #include <net/inet_ecn.h>
 #include <net/ip.h>
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
 #include <net/ipv6.h>
+#endif
 #include <net/protocol.h>
 #include <net/route.h>
 #include <net/xfrm.h>
@@ -32,6 +34,7 @@
 #include "openvswitch/gre.h"
 #include "table.h"
 #include "vport.h"
+#include "vport-generic.h"
 
 /* The absolute minimum fragment size.  Note that there are many other
  * definitions of the minimum MTU. */
  * number of options. */
 #define GRE_HEADER_SECTION 4
 
+struct gre_base_hdr {
+       __be16 flags;
+       __be16 protocol;
+};
+
 struct mutable_config {
        struct rcu_head rcu;
 
@@ -52,6 +60,7 @@ struct mutable_config {
 };
 
 struct gre_vport {
+       struct rcu_head rcu;
        struct tbl_node tbl_node;
 
        char name[IFNAMSIZ];
@@ -60,8 +69,6 @@ struct gre_vport {
        struct mutable_config *mutable;
 };
 
-struct vport_ops gre_vport_ops;
-
 /* Protected by RCU. */
 static struct tbl *port_table;
 
@@ -73,34 +80,30 @@ static unsigned int key_remote_ports;
 static unsigned int local_remote_ports;
 static unsigned int remote_ports;
 
-static inline struct gre_vport *
-gre_vport_priv(const struct vport *vport)
+static inline struct gre_vport *gre_vport_priv(const struct vport *vport)
 {
        return vport_priv(vport);
 }
 
-static inline struct vport *
-gre_vport_to_vport(const struct gre_vport *gre_vport)
+static inline struct vport *gre_vport_to_vport(const struct gre_vport *gre_vport)
 {
        return vport_from_priv(gre_vport);
 }
 
-static inline struct gre_vport *
-gre_vport_table_cast(const struct tbl_node *node)
+static inline struct gre_vport *gre_vport_table_cast(const struct tbl_node *node)
 {
        return container_of(node, struct gre_vport, tbl_node);
 }
 
 /* RCU callback. */
-static void
-free_config(struct rcu_head *rcu)
+static void free_config(struct rcu_head *rcu)
 {
        struct mutable_config *c = container_of(rcu, struct mutable_config, rcu);
        kfree(c);
 }
 
-static void
-assign_config_rcu(struct vport *vport, struct mutable_config *new_config)
+static void assign_config_rcu(struct vport *vport,
+                             struct mutable_config *new_config)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        struct mutable_config *old_config;
@@ -110,8 +113,7 @@ assign_config_rcu(struct vport *vport, struct mutable_config *new_config)
        call_rcu(&old_config->rcu, free_config);
 }
 
-static unsigned int *
-find_port_pool(const struct mutable_config *mutable)
+static unsigned int *find_port_pool(const struct mutable_config *mutable)
 {
        if (mutable->port_config.flags & GRE_F_IN_KEY_MATCH) {
                if (mutable->port_config.saddr)
@@ -140,8 +142,7 @@ struct port_lookup_key {
 
 /* Modifies 'target' to store the rcu_dereferenced pointer that was used to do
  * the comparision. */
-static int
-port_cmp(const struct tbl_node *node, void *target)
+static int port_cmp(const struct tbl_node *node, void *target)
 {
        const struct gre_vport *gre_vport = gre_vport_table_cast(node);
        struct port_lookup_key *lookup = target;
@@ -155,14 +156,12 @@ port_cmp(const struct tbl_node *node, void *target)
               lookup->mutable->port_config.saddr == lookup->vals[LOOKUP_SADDR];
 }
 
-static u32
-port_hash(struct port_lookup_key *lookup)
+static u32 port_hash(struct port_lookup_key *lookup)
 {
        return jhash2(lookup->vals, ARRAY_SIZE(lookup->vals), 0);
 }
 
-static int
-add_port(struct vport *vport)
+static int add_port(struct vport *vport)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        struct port_lookup_key lookup;
@@ -203,8 +202,7 @@ add_port(struct vport *vport)
        return 0;
 }
 
-static int
-del_port(struct vport *vport)
+static int del_port(struct vport *vport)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        int err;
@@ -222,9 +220,9 @@ del_port(struct vport *vport)
 #define FIND_PORT_MATCH                (1 << 1)
 #define FIND_PORT_ANY          (FIND_PORT_KEY | FIND_PORT_MATCH)
 
-static struct vport *
-find_port(__be32 saddr, __be32 daddr, __be32 key, int port_type,
-         const struct mutable_config **mutable)
+static struct vport *find_port(__be32 saddr, __be32 daddr, __be32 key,
+                              int port_type,
+                              const struct mutable_config **mutable)
 {
        struct port_lookup_key lookup;
        struct tbl *table = rcu_dereference(port_table);
@@ -283,8 +281,7 @@ found:
        return gre_vport_to_vport(gre_vport_table_cast(tbl_node));
 }
 
-static bool
-check_ipv4_address(__be32 addr)
+static bool check_ipv4_address(__be32 addr)
 {
        if (ipv4_is_multicast(addr) || ipv4_is_lbcast(addr)
            || ipv4_is_loopback(addr) || ipv4_is_zeronet(addr))
@@ -293,8 +290,7 @@ check_ipv4_address(__be32 addr)
        return true;
 }
 
-static bool
-ipv4_should_icmp(struct sk_buff *skb)
+static bool ipv4_should_icmp(struct sk_buff *skb)
 {
        struct iphdr *old_iph = ip_hdr(skb);
 
@@ -334,9 +330,8 @@ ipv4_should_icmp(struct sk_buff *skb)
        return true;
 }
 
-static void
-ipv4_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
-               unsigned int mtu, unsigned int payload_length)
+static void ipv4_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
+                           unsigned int mtu, unsigned int payload_length)
 {
        struct iphdr *iph, *old_iph = ip_hdr(skb);
        struct icmphdr *icmph;
@@ -376,8 +371,8 @@ ipv4_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
        icmph->checksum = csum_fold(nskb->csum);
 }
 
-static bool
-ipv6_should_icmp(struct sk_buff *skb)
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+static bool ipv6_should_icmp(struct sk_buff *skb)
 {
        struct ipv6hdr *old_ipv6h = ipv6_hdr(skb);
        int addr_type;
@@ -413,9 +408,8 @@ ipv6_should_icmp(struct sk_buff *skb)
        return true;
 }
 
-static void
-ipv6_build_icmp(struct sk_buff *skb, struct sk_buff *nskb, unsigned int mtu,
-               unsigned int payload_length)
+static void ipv6_build_icmp(struct sk_buff *skb, struct sk_buff *nskb,
+                           unsigned int mtu, unsigned int payload_length)
 {
        struct ipv6hdr *ipv6h, *old_ipv6h = ipv6_hdr(skb);
        struct icmp6hdr *icmp6h;
@@ -451,13 +445,15 @@ ipv6_build_icmp(struct sk_buff *skb, struct sk_buff *nskb, unsigned int mtu,
                                                + payload_length,
                                                ipv6h->nexthdr, nskb->csum);
 }
+#endif /* IPv6 */
 
-static bool
-send_frag_needed(struct vport *vport, const struct mutable_config *mutable,
-                struct sk_buff *skb, unsigned int mtu)
+static bool send_frag_needed(struct vport *vport,
+                            const struct mutable_config *mutable,
+                            struct sk_buff *skb, unsigned int mtu,
+                            __be32 flow_key)
 {
        unsigned int eth_hdr_len = ETH_HLEN;
-       unsigned int total_length, header_length, payload_length;
+       unsigned int total_length = 0, header_length = 0, payload_length;
        struct ethhdr *eh, *old_eh = eth_hdr(skb);
        struct sk_buff *nskb;
 
@@ -468,7 +464,9 @@ send_frag_needed(struct vport *vport, const struct mutable_config *mutable,
 
                if (!ipv4_should_icmp(skb))
                        return true;
-       } else {
+       }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       else if (skb->protocol == htons(ETH_P_IPV6)) {
                if (mtu < IPV6_MIN_MTU)
                        return false;
 
@@ -480,6 +478,9 @@ send_frag_needed(struct vport *vport, const struct mutable_config *mutable,
                if (!ipv6_should_icmp(skb))
                        return true;
        }
+#endif
+       else
+               return false;
 
        /* Allocate */
        if (old_eh->h_proto == htons(ETH_P_8021Q))
@@ -490,12 +491,16 @@ send_frag_needed(struct vport *vport, const struct mutable_config *mutable,
                header_length = sizeof(struct iphdr) + sizeof(struct icmphdr);
                total_length = min_t(unsigned int, header_length +
                                                   payload_length, 576);
-       } else {
+       }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       else {
                header_length = sizeof(struct ipv6hdr) +
                                sizeof(struct icmp6hdr);
                total_length = min_t(unsigned int, header_length +
                                                  payload_length, IPV6_MIN_MTU);
        }
+#endif
+
        total_length = min(total_length, mutable->mtu);
        payload_length = total_length - header_length;
 
@@ -522,32 +527,30 @@ send_frag_needed(struct vport *vport, const struct mutable_config *mutable,
        /* Protocol */
        if (skb->protocol == htons(ETH_P_IP))
                ipv4_build_icmp(skb, nskb, mtu, payload_length);
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
        else
                ipv6_build_icmp(skb, nskb, mtu, payload_length);
+#endif
 
        /* Assume that flow based keys are symmetric with respect to input
         * and output and use the key that we were going to put on the
         * outgoing packet for the fake received packet.  If the keys are
         * not symmetric then PMTUD needs to be disabled since we won't have
         * any way of synthesizing packets. */
-       if (mutable->port_config.flags & GRE_F_IN_KEY_MATCH) {
-               if (mutable->port_config.flags & GRE_F_OUT_KEY_ACTION)
-                       OVS_CB(nskb)->tun_id = OVS_CB(skb)->tun_id;
-               else
-                       OVS_CB(nskb)->tun_id = mutable->port_config.out_key;
-       }
+       if (mutable->port_config.flags & GRE_F_IN_KEY_MATCH &&
+           mutable->port_config.flags & GRE_F_OUT_KEY_ACTION)
+               OVS_CB(nskb)->tun_id = flow_key;
 
+       compute_ip_summed(nskb, false);
        vport_receive(vport, nskb);
 
        return true;
 }
 
-static struct sk_buff *
-check_headroom(struct sk_buff *skb, int headroom)
+static struct sk_buff *check_headroom(struct sk_buff *skb, int headroom)
 {
-       if (skb_headroom(skb) < headroom ||
-           (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {
-               struct sk_buff *nskb = skb_realloc_headroom(skb, headroom);
+       if (skb_headroom(skb) < headroom || skb_header_cloned(skb)) {
+               struct sk_buff *nskb = skb_realloc_headroom(skb, headroom + 16);
                if (!nskb) {
                        kfree_skb(skb);
                        return ERR_PTR(-ENOMEM);
@@ -565,22 +568,21 @@ check_headroom(struct sk_buff *skb, int headroom)
        return skb;
 }
 
-static void
-create_gre_header(struct sk_buff *skb, const struct mutable_config *mutable)
+static void create_gre_header(struct sk_buff *skb,
+                             const struct mutable_config *mutable)
 {
        struct iphdr *iph = ip_hdr(skb);
-       __be16 *flags = (__be16 *)(iph + 1);
-       __be16 *protocol = flags + 1;
+       struct gre_base_hdr *greh = (struct gre_base_hdr *)(iph + 1);
        __be32 *options = (__be32 *)((u8 *)iph + mutable->tunnel_hlen
                                               - GRE_HEADER_SECTION);
 
-       *protocol = htons(ETH_P_TEB);
-       *flags = 0;
+       greh->protocol = htons(ETH_P_TEB);
+       greh->flags = 0;
 
        /* Work backwards over the options so the checksum is last. */
        if (mutable->port_config.out_key ||
            mutable->port_config.flags & GRE_F_OUT_KEY_ACTION) {
-               *flags |= GRE_KEY;
+               greh->flags |= GRE_KEY;
 
                if (mutable->port_config.flags & GRE_F_OUT_KEY_ACTION)
                        *options = OVS_CB(skb)->tun_id;
@@ -591,7 +593,7 @@ create_gre_header(struct sk_buff *skb, const struct mutable_config *mutable)
        }
 
        if (mutable->port_config.flags & GRE_F_OUT_CSUM) {
-               *flags |= GRE_CSUM;
+               greh->flags |= GRE_CSUM;
 
                *options = 0;
                *(__sum16 *)options = csum_fold(skb_checksum(skb,
@@ -601,8 +603,7 @@ create_gre_header(struct sk_buff *skb, const struct mutable_config *mutable)
        }
 }
 
-static int
-check_checksum(struct sk_buff *skb)
+static int check_checksum(struct sk_buff *skb)
 {
        struct iphdr *iph = ip_hdr(skb);
        __be16 flags = *(__be16 *)(iph + 1);
@@ -628,30 +629,29 @@ check_checksum(struct sk_buff *skb)
        return (csum == 0);
 }
 
-static int
-parse_gre_header(struct iphdr *iph, __be16 *flags, __be32 *key)
+static int parse_gre_header(struct iphdr *iph, __be16 *flags, __be32 *key)
 {
-       __be16 *flagsp = (__be16 *)(iph + 1);
-       __be16 *protocol = flagsp + 1;
-       __be32 *options = (__be32 *)(protocol + 1);
+       /* IP and ICMP protocol handlers check that the IHL is valid. */
+       struct gre_base_hdr *greh = (struct gre_base_hdr *)((u8 *)iph + (iph->ihl << 2));
+       __be32 *options = (__be32 *)(greh + 1);
        int hdr_len;
 
-       *flags = *flagsp;
+       *flags = greh->flags;
 
-       if (*flags & (GRE_VERSION | GRE_ROUTING))
+       if (greh->flags & (GRE_VERSION | GRE_ROUTING))
                return -EINVAL;
 
-       if (*protocol != htons(ETH_P_TEB))
+       if (greh->protocol != htons(ETH_P_TEB))
                return -EINVAL;
 
        hdr_len = GRE_HEADER_SECTION;
 
-       if (*flags & GRE_CSUM) {
+       if (greh->flags & GRE_CSUM) {
                hdr_len += GRE_HEADER_SECTION;
                options++;
        }
 
-       if (*flags & GRE_KEY) {
+       if (greh->flags & GRE_KEY) {
                hdr_len += GRE_HEADER_SECTION;
 
                *key = *options;
@@ -659,29 +659,29 @@ parse_gre_header(struct iphdr *iph, __be16 *flags, __be32 *key)
        } else
                *key = 0;
 
-       if (*flags & GRE_SEQ)
+       if (greh->flags & GRE_SEQ)
                hdr_len += GRE_HEADER_SECTION;
 
        return hdr_len;
 }
 
-static inline u8
-ecn_encapsulate(u8 tos, struct sk_buff *skb)
+static inline u8 ecn_encapsulate(u8 tos, struct sk_buff *skb)
 {
        u8 inner;
 
        if (skb->protocol == htons(ETH_P_IP))
                inner = ((struct iphdr *)skb_network_header(skb))->tos;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
        else if (skb->protocol == htons(ETH_P_IPV6))
                inner = ipv6_get_dsfield((struct ipv6hdr *)skb_network_header(skb));
+#endif
        else
                inner = 0;
 
        return INET_ECN_encapsulate(tos, inner);
 }
 
-static inline void
-ecn_decapsulate(u8 tos, struct sk_buff *skb)
+static inline void ecn_decapsulate(u8 tos, struct sk_buff *skb)
 {
        if (INET_ECN_is_ce(tos)) {
                __be16 protocol = skb->protocol;
@@ -701,7 +701,9 @@ ecn_decapsulate(u8 tos, struct sk_buff *skb)
                                return;
 
                        IP_ECN_set_ce((struct iphdr *)(nw_header + skb->data));
-               } else if (protocol == htons(ETH_P_IPV6)) {
+               }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+               else if (protocol == htons(ETH_P_IPV6)) {
                        if (unlikely(!pskb_may_pull(skb, nw_header
                            + sizeof(struct ipv6hdr))))
                                return;
@@ -709,14 +711,14 @@ ecn_decapsulate(u8 tos, struct sk_buff *skb)
                        IP6_ECN_set_ce((struct ipv6hdr *)(nw_header
                                                          + skb->data));
                }
+#endif
        }
 }
 
-static struct sk_buff *
-handle_gso(struct sk_buff *skb)
+static struct sk_buff *handle_gso(struct sk_buff *skb)
 {
        if (skb_is_gso(skb)) {
-               struct sk_buff *nskb = skb_gso_segment(skb, NETIF_F_SG);
+               struct sk_buff *nskb = skb_gso_segment(skb, 0);
 
                dev_kfree_skb(skb);
                return nskb;
@@ -725,18 +727,18 @@ handle_gso(struct sk_buff *skb)
        return skb;
 }
 
-static int
-handle_csum_offload(struct sk_buff *skb)
+static int handle_csum_offload(struct sk_buff *skb)
 {
        if (skb->ip_summed == CHECKSUM_PARTIAL)
                return skb_checksum_help(skb);
-       else
+       else {
+               skb->ip_summed = CHECKSUM_NONE;
                return 0;
+       }
 }
 
-/* Called with rcu_read_lock and bottom-halves disabled. */
-static void
-gre_err(struct sk_buff *skb, u32 info)
+/* Called with rcu_read_lock. */
+static void gre_err(struct sk_buff *skb, u32 info)
 {
        struct vport *vport;
        const struct mutable_config *mutable;
@@ -771,16 +773,30 @@ gre_err(struct sk_buff *skb, u32 info)
        if (!vport)
                return;
 
-       if ((mutable->port_config.flags & GRE_F_IN_CSUM) && !(flags & GRE_CSUM))
+       /* Packets received by this function were previously sent by us, so
+        * any comparisons should be to the output values, not the input.
+        * However, it's not really worth it to have a hash table based on
+        * output keys (especially since ICMP error handling of tunneled packets
+        * isn't that reliable anyways).  Therefore, we do a lookup based on the
+        * out key as if it were the in key and then check to see if the input
+        * and output keys are the same. */
+       if (mutable->port_config.in_key != mutable->port_config.out_key)
                return;
 
-       tot_hdr_len = sizeof(struct iphdr) + tunnel_hdr_len;
+       if (!!(mutable->port_config.flags & GRE_F_IN_KEY_MATCH) !=
+           !!(mutable->port_config.flags & GRE_F_OUT_KEY_ACTION))
+               return;
+
+       if ((mutable->port_config.flags & GRE_F_OUT_CSUM) && !(flags & GRE_CSUM))
+               return;
+
+       tunnel_hdr_len += iph->ihl << 2;
 
        orig_mac_header = skb_mac_header(skb) - skb->data;
        orig_nw_header = skb_network_header(skb) - skb->data;
-       skb_set_mac_header(skb, tot_hdr_len);
+       skb_set_mac_header(skb, tunnel_hdr_len);
 
-       tot_hdr_len += ETH_HLEN;
+       tot_hdr_len = tunnel_hdr_len + ETH_HLEN;
 
        skb->protocol = eth_hdr(skb)->h_proto;
        if (skb->protocol == htons(ETH_P_8021Q)) {
@@ -788,19 +804,21 @@ gre_err(struct sk_buff *skb, u32 info)
                skb->protocol = vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
        }
 
+       skb_set_network_header(skb, tot_hdr_len);
+       mtu -= tot_hdr_len;
+
        if (skb->protocol == htons(ETH_P_IP))
                tot_hdr_len += sizeof(struct iphdr);
-       else if (skb->protocol == htons(ETH_P_IP))
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       else if (skb->protocol == htons(ETH_P_IPV6))
                tot_hdr_len += sizeof(struct ipv6hdr);
+#endif
        else
                goto out;
 
        if (!pskb_may_pull(skb, tot_hdr_len))
                goto out;
 
-       skb_set_network_header(skb, tot_hdr_len);
-       mtu -= tot_hdr_len;
-
        if (skb->protocol == htons(ETH_P_IP)) {
                if (mtu < IP_MIN_MTU) {
                        if (ntohs(ip_hdr(skb)->tot_len) >= IP_MIN_MTU)
@@ -809,7 +827,9 @@ gre_err(struct sk_buff *skb, u32 info)
                                goto out;
                }
 
-       } else if (skb->protocol == htons(ETH_P_IPV6)) {
+       }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       else if (skb->protocol == htons(ETH_P_IPV6)) {
                if (mtu < IPV6_MIN_MTU) {
                        unsigned int packet_length = sizeof(struct ipv6hdr) +
                                              ntohs(ipv6_hdr(skb)->payload_len);
@@ -821,9 +841,10 @@ gre_err(struct sk_buff *skb, u32 info)
                                goto out;
                }
        }
+#endif
 
        __pskb_pull(skb, tunnel_hdr_len);
-       send_frag_needed(vport, mutable, skb, mtu);
+       send_frag_needed(vport, mutable, skb, mtu, key);
        skb_push(skb, tunnel_hdr_len);
 
 out:
@@ -832,9 +853,8 @@ out:
        skb->protocol = htons(ETH_P_IP);
 }
 
-/* Called with rcu_read_lock and bottom-halves disabled. */
-static int
-gre_rcv(struct sk_buff *skb)
+/* Called with rcu_read_lock. */
+static int gre_rcv(struct sk_buff *skb)
 {
        struct vport *vport;
        const struct mutable_config *mutable;
@@ -888,6 +908,8 @@ gre_rcv(struct sk_buff *skb)
                OVS_CB(skb)->tun_id = 0;
 
        skb_push(skb, ETH_HLEN);
+       compute_ip_summed(skb, false);
+
        vport_receive(vport, skb);
 
        return 0;
@@ -897,10 +919,9 @@ error:
        return 0;
 }
 
-static int
-build_packet(struct vport *vport, const struct mutable_config *mutable,
-            struct iphdr *iph, struct rtable *rt, int max_headroom, int mtu,
-            struct sk_buff *skb)
+static int build_packet(struct vport *vport, const struct mutable_config *mutable,
+                       struct iphdr *iph, struct rtable *rt, int max_headroom,
+                       int mtu, struct sk_buff *skb)
 {
        int err;
        struct iphdr *new_iph;
@@ -920,11 +941,13 @@ build_packet(struct vport *vport, const struct mutable_config *mutable,
 
                if ((old_iph->frag_off & htons(IP_DF)) &&
                    mtu < ntohs(old_iph->tot_len)) {
-                       if (send_frag_needed(vport, mutable, skb, mtu))
+                       if (send_frag_needed(vport, mutable, skb, mtu, OVS_CB(skb)->tun_id))
                                goto error_free;
                }
 
-       } else if (skb->protocol == htons(ETH_P_IPV6)) {
+       }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       else if (skb->protocol == htons(ETH_P_IPV6)) {
                unsigned int packet_length = skb->len - ETH_HLEN
                        - (eth_hdr(skb)->h_proto == htons(ETH_P_8021Q) ? VLAN_HLEN : 0);
 
@@ -933,10 +956,11 @@ build_packet(struct vport *vport, const struct mutable_config *mutable,
                        frag_off = htons(IP_DF);
 
                if (mtu < packet_length) {
-                       if (send_frag_needed(vport, mutable, skb, mtu))
+                       if (send_frag_needed(vport, mutable, skb, mtu, OVS_CB(skb)->tun_id))
                                goto error_free;
                }
        }
+#endif
 
        skb_reset_transport_header(skb);
        new_iph = (struct iphdr *)skb_push(skb, mutable->tunnel_hlen);
@@ -948,6 +972,10 @@ build_packet(struct vport *vport, const struct mutable_config *mutable,
 
        create_gre_header(skb, mutable);
 
+       /* 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;
+
        memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
        IPCB(skb)->flags = 0;
 
@@ -967,14 +995,12 @@ error:
        return 0;
 }
 
-static int
-gre_send(struct vport *vport, struct sk_buff *skb)
+static int gre_send(struct vport *vport, struct sk_buff *skb)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        const struct mutable_config *mutable = rcu_dereference(gre_vport->mutable);
 
        struct iphdr *old_iph;
-       struct ipv6hdr *old_ipv6h;
        int orig_len;
        struct iphdr iph;
        struct rtable *rt;
@@ -994,21 +1020,24 @@ gre_send(struct vport *vport, struct sk_buff *skb)
                if (unlikely(!pskb_may_pull(skb, skb_network_header(skb)
                    + sizeof(struct iphdr) - skb->data)))
                        skb->protocol = 0;
-       } else if (skb->protocol == htons(ETH_P_IPV6)) {
+       }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       else if (skb->protocol == htons(ETH_P_IPV6)) {
                if (unlikely(!pskb_may_pull(skb, skb_network_header(skb)
                    + sizeof(struct ipv6hdr) - skb->data)))
                        skb->protocol = 0;
        }
-
+#endif
        old_iph = ip_hdr(skb);
-       old_ipv6h = ipv6_hdr(skb);
 
        iph.tos = mutable->port_config.tos;
        if (mutable->port_config.flags & GRE_F_TOS_INHERIT) {
                if (skb->protocol == htons(ETH_P_IP))
                        iph.tos = old_iph->tos;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
                else if (skb->protocol == htons(ETH_P_IPV6))
                        iph.tos = ipv6_get_dsfield(ipv6_hdr(skb));
+#endif
        }
        iph.tos = ecn_encapsulate(iph.tos, skb);
 
@@ -1027,8 +1056,10 @@ gre_send(struct vport *vport, struct sk_buff *skb)
        if (mutable->port_config.flags & GRE_F_TTL_INHERIT) {
                if (skb->protocol == htons(ETH_P_IP))
                        iph.ttl = old_iph->ttl;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
                else if (skb->protocol == htons(ETH_P_IPV6))
-                       iph.ttl = old_ipv6h->hop_limit;
+                       iph.ttl = ipv6_hdr(skb)->hop_limit;
+#endif
        }
        if (!iph.ttl)
                iph.ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
@@ -1045,9 +1076,11 @@ gre_send(struct vport *vport, struct sk_buff *skb)
        if (skb->protocol == htons(ETH_P_IP)) {
                iph.frag_off |= old_iph->frag_off & htons(IP_DF);
                mtu = max(mtu, IP_MIN_MTU);
-
-       } else if (skb->protocol == htons(ETH_P_IPV6))
+       }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+       else if (skb->protocol == htons(ETH_P_IPV6))
                mtu = max(mtu, IPV6_MIN_MTU);
+#endif
 
        iph.version = 4;
        iph.ihl = sizeof(struct iphdr) >> 2;
@@ -1055,15 +1088,10 @@ gre_send(struct vport *vport, struct sk_buff *skb)
        iph.daddr = rt->rt_dst;
        iph.saddr = rt->rt_src;
 
-       /* 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;
-
        nf_reset(skb);
        secpath_reset(skb);
        skb_dst_drop(skb);
        skb_dst_set(skb, &rt->u.dst);
-       skb->ip_summed = CHECKSUM_NONE;
 
        /* If we are doing GSO on a pskb it is better to make sure that the
         * headroom is correct now.  We will only have to copy the portion in
@@ -1071,7 +1099,9 @@ gre_send(struct vport *vport, struct sk_buff *skb)
         * the segments.  This is particularly beneficial on Xen where we get
         * lots of GSO pskbs.  Conversely, we delay copying if it is just to
         * get our own writable clone because GSO may do the copy for us. */
-       max_headroom = LL_RESERVED_SPACE(rt->u.dst.dev) + mutable->tunnel_hlen;
+       max_headroom = LL_RESERVED_SPACE(rt->u.dst.dev) + rt->u.dst.header_len
+                       + mutable->tunnel_hlen;
+
        if (skb_headroom(skb) < max_headroom) {
                skb = check_headroom(skb, max_headroom);
                if (unlikely(IS_ERR(skb))) {
@@ -1080,14 +1110,18 @@ gre_send(struct vport *vport, struct sk_buff *skb)
                }
        }
 
-       vswitch_skb_checksum_setup(skb);
+       forward_ip_summed(skb);
+
+       if (unlikely(vswitch_skb_checksum_setup(skb)))
+               goto error_free;
+
        skb = handle_gso(skb);
        if (unlikely(IS_ERR(skb))) {
                vport_record_error(vport, VPORT_E_TX_DROPPED);
                goto error;
        }
 
-       /* Process GSO segments.  Try to do any work on the entire packet that
+       /* Process GSO segments.  Try to do any work for the entire packet that
         * doesn't involve actually writing to it before this point. */
        orig_len = 0;
        do {
@@ -1113,8 +1147,7 @@ static struct net_protocol gre_protocol_handlers = {
        .err_handler    =       gre_err,
 };
 
-static int
-gre_init(void)
+static int gre_init(void)
 {
        int err;
 
@@ -1125,16 +1158,14 @@ gre_init(void)
        return err;
 }
 
-static void
-gre_exit(void)
+static void gre_exit(void)
 {
        tbl_destroy(port_table, NULL);
        inet_del_protocol(&gre_protocol_handlers, IPPROTO_GRE);
 }
 
-static int
-set_config(const struct vport *cur_vport, struct mutable_config *mutable,
-          const void __user *uconfig)
+static int set_config(const struct vport *cur_vport,
+                     struct mutable_config *mutable, const void __user *uconfig)
 {
        const struct vport *old_vport;
        const struct mutable_config *old_mutable;
@@ -1160,6 +1191,9 @@ set_config(const struct vport *cur_vport, struct mutable_config *mutable,
        if (old_vport && old_vport != cur_vport)
                return -EEXIST;
 
+       if (mutable->port_config.flags & GRE_F_OUT_KEY_ACTION)
+               mutable->port_config.out_key = 0;
+
        mutable->tunnel_hlen = sizeof(struct iphdr) + GRE_HEADER_SECTION;
 
        if (mutable->port_config.flags & GRE_F_OUT_CSUM)
@@ -1172,8 +1206,7 @@ set_config(const struct vport *cur_vport, struct mutable_config *mutable,
        return 0;
 }
 
-static struct vport *
-gre_create(const char *name, const void __user *config)
+static struct vport *gre_create(const char *name, const void __user *config)
 {
        struct vport *vport;
        struct gre_vport *gre_vport;
@@ -1195,7 +1228,7 @@ gre_create(const char *name, const void __user *config)
                goto error_free_vport;
        }
 
-       vport_gen_ether_addr(gre_vport->mutable->eth_addr);
+       vport_gen_rand_ether_addr(gre_vport->mutable->eth_addr);
        gre_vport->mutable->mtu = ETH_DATA_LEN;
 
        err = set_config(NULL, gre_vport->mutable, config);
@@ -1216,8 +1249,7 @@ error:
        return ERR_PTR(err);
 }
 
-static int
-gre_modify(struct vport *vport, const void __user *config)
+static int gre_modify(struct vport *vport, const void __user *config)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        struct mutable_config *mutable;
@@ -1268,8 +1300,15 @@ error:
        return err;
 }
 
-static int
-gre_destroy(struct vport *vport)
+static void free_port(struct rcu_head *rcu)
+{
+       struct gre_vport *gre_vport = container_of(rcu, struct gre_vport, rcu);
+
+       kfree(gre_vport->mutable);
+       vport_free(gre_vport_to_vport(gre_vport));
+}
+
+static int gre_destroy(struct vport *vport)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        int port_type;
@@ -1287,18 +1326,15 @@ gre_destroy(struct vport *vport)
            gre_vport->mutable->port_config.in_key, port_type, &old_mutable))
                del_port(vport);
 
-       kfree(gre_vport->mutable);
-       vport_free(vport);
+       call_rcu(&gre_vport->rcu, free_port);
 
        return 0;
 }
 
-static int
-gre_set_mtu(struct vport *vport, int mtu)
+static int gre_set_mtu(struct vport *vport, int mtu)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        struct mutable_config *mutable;
-       struct dp_port *dp_port;
 
        mutable = kmemdup(gre_vport->mutable, sizeof(struct mutable_config), GFP_KERNEL);
        if (!mutable)
@@ -1307,15 +1343,10 @@ gre_set_mtu(struct vport *vport, int mtu)
        mutable->mtu = mtu;
        assign_config_rcu(vport, mutable);
 
-       dp_port = vport_get_dp_port(vport);
-       if (dp_port)
-               set_internal_devs_mtu(dp_port->dp);
-
        return 0;
 }
 
-static int
-gre_set_addr(struct vport *vport, const unsigned char *addr)
+static int gre_set_addr(struct vport *vport, const unsigned char *addr)
 {
        struct gre_vport *gre_vport = gre_vport_priv(vport);
        struct mutable_config *mutable;
@@ -1331,40 +1362,19 @@ gre_set_addr(struct vport *vport, const unsigned char *addr)
 }
 
 
-static const char *
-gre_get_name(const struct vport *vport)
+static const char *gre_get_name(const struct vport *vport)
 {
        const struct gre_vport *gre_vport = gre_vport_priv(vport);
        return gre_vport->name;
 }
 
-static const unsigned char *
-gre_get_addr(const struct vport *vport)
+static const unsigned char *gre_get_addr(const struct vport *vport)
 {
        const struct gre_vport *gre_vport = gre_vport_priv(vport);
        return rcu_dereference(gre_vport->mutable)->eth_addr;
 }
 
-static unsigned
-gre_get_dev_flags(const struct vport *vport)
-{
-       return IFF_UP | IFF_RUNNING | IFF_LOWER_UP;
-}
-
-static int
-gre_is_running(const struct vport *vport)
-{
-       return 1;
-}
-
-static unsigned char
-gre_get_operstate(const struct vport *vport)
-{
-       return IF_OPER_UP;
-}
-
-static int
-gre_get_mtu(const struct vport *vport)
+static int gre_get_mtu(const struct vport *vport)
 {
        const struct gre_vport *gre_vport = gre_vport_priv(vport);
        return rcu_dereference(gre_vport->mutable)->mtu;
@@ -1382,9 +1392,9 @@ struct vport_ops gre_vport_ops = {
        .set_addr       = gre_set_addr,
        .get_name       = gre_get_name,
        .get_addr       = gre_get_addr,
-       .get_dev_flags  = gre_get_dev_flags,
-       .is_running     = gre_is_running,
-       .get_operstate  = gre_get_operstate,
+       .get_dev_flags  = vport_gen_get_dev_flags,
+       .is_running     = vport_gen_is_running,
+       .get_operstate  = vport_gen_get_operstate,
        .get_mtu        = gre_get_mtu,
        .send           = gre_send,
 };