Implement OpenFlow hard timeouts.
[sliver-openvswitch.git] / datapath / flow.c
index cdda24b..9b5a87a 100644 (file)
 #include <linux/if_vlan.h>
 #include <net/llc_pdu.h>
 #include <linux/ip.h>
+#include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/in.h>
 #include <linux/rcupdate.h>
+#include <net/ip.h>
 
 #include "openflow.h"
 #include "compat.h"
@@ -73,26 +75,40 @@ void flow_extract_match(struct sw_flow_key* to, const struct ofp_match* from)
        memcpy(to->dl_dst, from->dl_dst, ETH_ALEN);
        to->dl_type = from->dl_type;
 
-       if (likely(from->dl_type == htons(ETH_P_IP))) {
+       to->nw_src = to->nw_dst = to->nw_proto = 0;
+       to->tp_src = to->tp_dst = 0;
+
+#define OFPFW_TP (OFPFW_TP_SRC | OFPFW_TP_DST)
+#define OFPFW_NW (OFPFW_NW_SRC | OFPFW_NW_DST | OFPFW_NW_PROTO)
+       if (to->wildcards & OFPFW_DL_TYPE) {
+               /* Can't sensibly match on network or transport headers if the
+                * data link type is unknown. */
+               to->wildcards |= OFPFW_NW | OFPFW_TP;
+       } else if (from->dl_type == htons(ETH_P_IP)) {
                to->nw_src   = from->nw_src;
                to->nw_dst   = from->nw_dst;
                to->nw_proto = from->nw_proto;
 
-               if ((from->nw_proto != IPPROTO_TCP && from->nw_proto != IPPROTO_UDP)) {
-                       goto no_th;
+               if (to->wildcards & OFPFW_NW_PROTO) {
+                       /* Can't sensibly match on transport headers if the
+                        * network protocol is unknown. */
+                       to->wildcards |= OFPFW_TP;
+               } else if (from->nw_proto == IPPROTO_TCP
+                          || from->nw_proto == IPPROTO_UDP) {
+                       to->tp_src = from->tp_src;
+                       to->tp_dst = from->tp_dst;
+               } else {
+                       /* Transport layer fields are undefined.  Mark them as
+                        * exact-match to allow such flows to reside in
+                        * table-hash, instead of falling into table-linear. */
+                       to->wildcards &= ~OFPFW_TP;
                }
-               to->tp_src = from->tp_src;
-               to->tp_dst = from->tp_dst;
-               return;
+       } else {
+               /* Network and transport layer fields are undefined.  Mark them
+                * as exact-match to allow such flows to reside in table-hash,
+                * instead of falling into table-linear. */
+               to->wildcards &= ~(OFPFW_NW | OFPFW_TP);
        }
-
-       to->nw_src = 0;
-       to->nw_dst = 0;
-       to->nw_proto = 0;
-
-no_th:
-       to->tp_src = 0;
-       to->tp_dst = 0;
 }
 
 void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from)
@@ -111,15 +127,19 @@ void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from)
        memset(to->pad, '\0', sizeof(to->pad));
 }
 
-/* Returns true if 'flow' can be deleted and set up for a deferred free, false
- * if deletion has already been scheduled (by another thread).
- *
- * Caller must hold rcu_read_lock. */
-int flow_del(struct sw_flow *flow)
+int flow_timeout(struct sw_flow *flow)
 {
-       return !atomic_cmpxchg(&flow->deleted, 0, 1);
+       if (flow->idle_timeout != OFP_FLOW_PERMANENT
+           && time_after(jiffies, flow->used + flow->idle_timeout * HZ))
+               return OFPER_IDLE_TIMEOUT;
+       else if (flow->hard_timeout != OFP_FLOW_PERMANENT
+                && time_after(jiffies,
+                              flow->init_time + flow->hard_timeout * HZ))
+               return OFPER_HARD_TIMEOUT;
+       else
+               return -1;
 }
-EXPORT_SYMBOL(flow_del);
+EXPORT_SYMBOL(flow_timeout);
 
 /* Allocates and returns a new flow with 'n_actions' action, using allocation
  * flags 'flags'.  Returns the new flow or a null pointer on failure. */
@@ -188,14 +208,32 @@ void print_flow(const struct sw_flow_key *key)
 }
 EXPORT_SYMBOL(print_flow);
 
+static int tcphdr_ok(struct sk_buff *skb)
+{
+       int th_ofs = skb_transport_offset(skb);
+       if (skb->len >= th_ofs + sizeof(struct tcphdr)) {
+               int tcp_len = tcp_hdrlen(skb);
+               return (tcp_len >= sizeof(struct tcphdr)
+                       && skb->len >= th_ofs + tcp_len);
+       }
+       return 0;
+}
+
+static int udphdr_ok(struct sk_buff *skb)
+{
+       int th_ofs = skb_transport_offset(skb);
+       return skb->len >= th_ofs + sizeof(struct udphdr);
+}
+
 /* Parses the Ethernet frame in 'skb', which was received on 'in_port',
- * and initializes 'key' to match. */
-void flow_extract(struct sk_buff *skb, uint16_t in_port,
-                 struct sw_flow_key *key)
+ * and initializes 'key' to match.  Returns 1 if 'skb' contains an IP
+ * fragment, 0 otherwise. */
+int flow_extract(struct sk_buff *skb, uint16_t in_port,
+                struct sw_flow_key *key)
 {
        struct ethhdr *mac;
-       struct udphdr *th;
        int nh_ofs, th_ofs;
+       int retval = 0;
 
        key->in_port = htons(in_port);
        key->wildcards = 0;
@@ -246,24 +284,48 @@ void flow_extract(struct sk_buff *skb, uint16_t in_port,
                skb_set_transport_header(skb, th_ofs);
 
                /* Transport layer. */
-               if ((key->nw_proto != IPPROTO_TCP && key->nw_proto != IPPROTO_UDP)
-                               || skb->len < th_ofs + sizeof(struct udphdr)) {
+               if (!(nh->frag_off & htons(IP_MF | IP_OFFSET))) {
+                       if (key->nw_proto == IPPROTO_TCP) {
+                               if (tcphdr_ok(skb)) {
+                                       struct tcphdr *tcp = tcp_hdr(skb);
+                                       key->tp_src = tcp->source;
+                                       key->tp_dst = tcp->dest;
+                               } else {
+                                       /* Avoid tricking other code into
+                                        * thinking that this packet has an L4
+                                        * header. */
+                                       goto no_proto;
+                               }
+                       } else if (key->nw_proto == IPPROTO_UDP) {
+                               if (udphdr_ok(skb)) {
+                                       struct udphdr *udp = udp_hdr(skb);
+                                       key->tp_src = udp->source;
+                                       key->tp_dst = udp->dest;
+                               } else {
+                                       /* Avoid tricking other code into
+                                        * thinking that this packet has an L4
+                                        * header. */
+                                       goto no_proto;
+                               }
+                       }
+               } else {
+                       retval = 1;
                        goto no_th;
                }
-               th = udp_hdr(skb);
-               key->tp_src = th->source;
-               key->tp_dst = th->dest;
 
-               return;
+               return 0;
        }
 
        key->nw_src = 0;
        key->nw_dst = 0;
+
+no_proto:
        key->nw_proto = 0;
 
 no_th:
        key->tp_src = 0;
        key->tp_dst = 0;
+       return retval;
 }
 
 /* Initializes the flow module.