Fix fragment issue for large IP packets when SNAT action enabled.
authorJustin Pettit <jpettit@nicira.com>
Wed, 21 Jan 2009 02:22:49 +0000 (18:22 -0800)
committerJustin Pettit <jpettit@nicira.com>
Wed, 21 Jan 2009 02:25:12 +0000 (18:25 -0800)
The Netfilter code automatically reassembles IP fragments.  We need to
explicitly have them refragmented before transmitting. (Bug #823)
(cherry picked from commit dfc7aa676ab44db7a49284a80798c7be5369db85)

datapath/datapath.c
datapath/nx_act_snat.c
datapath/nx_act_snat.h

index f7e3ad8539cc869ce608ae6e253d32277f182e5a..2c7c3b3b0862401f5573a60f54dc70c835df3420 100644 (file)
@@ -554,9 +554,8 @@ do_port_input(struct net_bridge_port *p, struct sk_buff *skb)
 
 #ifdef SUPPORT_SNAT
        /* Check if this packet needs early SNAT processing. */
-       if (snat_pre_route(skb)) {
+       if (snat_pre_route(skb)) 
                return;
-       }
 #endif
 
        /* Push the Ethernet header back on. */
@@ -654,6 +653,53 @@ void dp_set_origin(struct datapath *dp, uint16_t in_port,
                skb->dev = NULL;
 }
 
+#ifdef SUPPORT_SNAT
+static int 
+dp_xmit_skb_finish(struct sk_buff *skb)
+{
+       /* The ip_fragment function does not copy the Ethernet header into
+        * the newly generated frames, so put back the values stowed
+        * earlier. */
+       if (snat_copy_header(skb)) {
+               kfree_skb(skb);
+               return -EINVAL;
+       }
+       skb_reset_mac_header(skb);
+
+       if (packet_length(skb) > skb->dev->mtu && !skb_is_gso(skb)) {
+               printk("dropped over-mtu packet: %d > %d\n",
+                          packet_length(skb), skb->dev->mtu);
+               kfree_skb(skb);
+               return -E2BIG;
+       }
+
+       skb_push(skb, ETH_HLEN);
+       dev_queue_xmit(skb);
+
+       return 0;
+}
+
+int
+dp_xmit_skb(struct sk_buff *skb)
+{
+       int len = skb->len;
+       int err;
+
+       skb_pull(skb, ETH_HLEN);
+
+       if (skb->protocol == htons(ETH_P_IP) &&
+                       skb->len > skb->dev->mtu &&
+                       !skb_is_gso(skb)) {
+               err = ip_fragment(skb, dp_xmit_skb_finish);
+       } else {
+               err = dp_xmit_skb_finish(skb);
+       }
+       if (err)
+               return err;
+
+       return len;
+}
+#else
 int 
 dp_xmit_skb(struct sk_buff *skb)
 {
@@ -671,6 +717,7 @@ dp_xmit_skb(struct sk_buff *skb)
 
        return len;
 }
+#endif
 
 /* Takes ownership of 'skb' and transmits it to 'out_port' on 'dp'.
  */
index 5301fb68b6e8002a399c978163516123c2abe3db..5b4a31999c341c7346e674068554c021359d5994 100644 (file)
@@ -7,6 +7,7 @@
 #include <linux/etherdevice.h>
 #include <linux/netdevice.h>
 #include <linux/netfilter.h>
+#include <linux/netfilter_bridge.h>
 #include <linux/netfilter_ipv4.h>
 #include <linux/in.h>
 #include <net/ip.h>
@@ -59,6 +60,42 @@ struct ip_arphdr {
 } __attribute__((packed));
 OFP_ASSERT(sizeof(struct ip_arphdr) == 28);
 
+static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb)
+{
+       skb->nf_bridge = kzalloc(sizeof(struct nf_bridge_info), GFP_ATOMIC);
+       if (likely(skb->nf_bridge))
+               atomic_set(&(skb->nf_bridge->use), 1);
+
+       return skb->nf_bridge;
+}
+
+/* Save a copy of the original Ethernet header. */
+static inline void snat_save_header(struct sk_buff *skb)
+{
+       int header_size = ETH_HLEN + nf_bridge_encap_header_len(skb);
+
+       skb_copy_from_linear_data_offset(skb, -header_size, 
+                       skb->nf_bridge->data, header_size);
+}
+
+/* Restore a saved Ethernet header. */
+int snat_copy_header(struct sk_buff *skb)
+{
+       int err;
+       int header_size = ETH_HLEN + nf_bridge_encap_header_len(skb);
+
+       if (!skb->nf_bridge)
+               return 0;
+
+       err = skb_cow_head(skb, header_size);
+       if (err)
+               return err;
+
+       skb_copy_to_linear_data_offset(skb, -header_size, 
+                       skb->nf_bridge->data, header_size);
+       __skb_push(skb, nf_bridge_encap_header_len(skb));
+       return 0;
+}
 
 /* Push the Ethernet header back on and tranmit the packet. */
 static int
@@ -202,6 +239,7 @@ snat_pre_route_finish(struct sk_buff *skb)
 
        /* Pass the translated packet as input to the OpenFlow stack, which
         * consumes it. */
+       snat_save_header(skb);
        skb_push(skb, ETH_HLEN);
        skb_reset_mac_header(skb);
        fwd_port_input(p->dp->chain, skb, p);
@@ -375,6 +413,10 @@ snat_pre_route(struct sk_buff *skb)
        if (pskb_trim_rcsum(skb, len))
                goto consume;
 
+       nf_bridge_put(skb->nf_bridge);
+       if (!nf_bridge_alloc(skb))
+               return 0;
+
        NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, skb->dev, NULL,
                snat_pre_route_finish);
        return -1;
index 28a0e595928d12af147a3b5ba0d214c30d0007ac..7570aa0f0c9f50157bce83719c1cbe6eab3eac10 100644 (file)
@@ -34,6 +34,7 @@ struct snat_conf {
 void snat_local_in(struct sk_buff *skb);
 int snat_pre_route(struct sk_buff *skb);
 void snat_skb(struct datapath *dp, const struct sk_buff *skb, int out_port);
+int snat_copy_header(struct sk_buff *skb);
 void snat_maint(struct net_bridge_port *p);
 int snat_mod_config(struct datapath *, const struct nx_act_config *);
 int snat_free_conf(struct net_bridge_port *p);