Hi Justin,

I got a chance to try out your code with the example flows and measure raw
packet throughput with and without connection tracking. The code works and
the performance numbers look pretty good.

I ran two sets of tests both with 4 iptable rules in chain input and 8 in
chain forward, all default rules coming from a docker installation. The
setup is a ovs connected to two packet generators via two ports. I am using
dpdk-pktgen.

In the first test I am sending random tcp 64 byte packets and the
throughput difference is about 20% (5484/6819 Mbps)

In the second test I let a connection get established and then hijack the
connection via pktgen and send a spoofed packet from one end to the other.
Here I see a throughput difference of 15%.

I am planning on adding iptables rules and running some more tests. If you
have any specific test cases in mind I would be happy to run them.

Thanks.


On Mon, Aug 25, 2014 at 6:26 PM, Justin Pettit <jpet...@nicira.com> wrote:

> An RFC to get back early feedback on exposing Linux's kernel connection
> tracker to OVS.  The code has a few rough spots that will be addressed
> in the next version:
>
>     - Need better interface than setting individual flags for the state.
>     - Need support for IP frags.
>     - Need support for zones.
>     - Should have ability to match on "invalid" connection states.
>     - Should only allow conntrack() if conn_state is 0x00 to prevent loops.
>
> I'd be interested in hearing back suggestion on improvements other than
> those mentioned above.
>
> Here's a simple example flow table to allow outbound TCP traffic from
> port 1; drop traffic from port 2 that was not initiated by port 1:
>
> ovs-ofctl add-flow br0 \
>     "in_port=1,conn_state=0x00/0x80,tcp,action=conntrack(zone=0),normal"
> ovs-ofctl add-flow br0 \
>     "in_port=2,conn_state=0x00/0x80,tcp,action=conntrack(flags=1,zone=0)"
> ovs-ofctl add-flow br0 in_port=2,conn_state=0x82/0x83,tcp,action=1
> ovs-ofctl add-flow br0 in_port=2,conn_state=0x81/0x83,tcp,action=drop
> ovs-ofctl add-flow br0 priority=10,action=normal
> ---
>  datapath/actions.c                                |   49 +++++++++++
>  datapath/datapath.c                               |    9 ++
>  datapath/flow.c                                   |   25 ++++++
>  datapath/flow.h                                   |    3 +
>  datapath/flow_netlink.c                           |   21 +++++-
>  datapath/linux/compat/include/linux/openvswitch.h |    3 +
>  include/openflow/nicira-ext.h                     |   22 +++++
>  lib/dpif-netdev.c                                 |    1 +
>  lib/dpif.c                                        |    1 +
>  lib/flow.c                                        |   61 ++++++++++++--
>  lib/flow.h                                        |    9 ++-
>  lib/match.c                                       |   40 +++++++++-
>  lib/match.h                                       |    3 +
>  lib/meta-flow.c                                   |   36 ++++++++
>  lib/meta-flow.h                                   |    1 +
>  lib/nx-match.c                                    |    6 +-
>  lib/odp-execute.c                                 |    8 ++
>  lib/odp-util.c                                    |   59 ++++++++++++++
>  lib/odp-util.h                                    |    5 +-
>  lib/ofp-actions.c                                 |   90
> +++++++++++++++++++++
>  lib/ofp-actions.h                                 |   18 ++++
>  lib/ofp-print.c                                   |    4 +
>  lib/ofp-util.c                                    |    7 ++-
>  lib/packets.h                                     |    1 +
>  ofproto/ofproto-dpif-xlate.c                      |   19 ++++-
>  ofproto/ofproto-unixctl.man                       |    2 +
>  tests/dpif-netdev.at                              |   16 ++--
>  tests/odp.at                                      |   20 +++---
>  tests/ofproto-dpif.at                             |   18 ++--
>  tests/ofproto.at                                  |    5 +-
>  utilities/ovs-ofctl.8.in                          |   20 +++++
>  31 files changed, 535 insertions(+), 47 deletions(-)
>
> diff --git a/datapath/actions.c b/datapath/actions.c
> index d70348e..908ed02 100644
> --- a/datapath/actions.c
> +++ b/datapath/actions.c
> @@ -32,6 +32,7 @@
>  #include <net/ipv6.h>
>  #include <net/checksum.h>
>  #include <net/dsfield.h>
> +#include <net/netfilter/nf_conntrack_core.h>
>  #include <net/sctp/checksum.h>
>
>  #include "datapath.h"
> @@ -752,6 +753,46 @@ static void execute_hash(struct sk_buff *skb, const
> struct nlattr *attr)
>         key->ovs_flow_hash = hash;
>  }
>
> +static int conntrack(struct sk_buff *skb, uint16_t zone)
> +{
> +       struct sw_flow_key *key = OVS_CB(skb)->pkt_key;
> +       int nh_ofs = skb_network_offset(skb);
> +       struct vport *vport;
> +       struct net *net;
> +
> +       if (skb->nfct) {
> +               pr_warn_once("Attempt to run through conntrack again\n");
> +               return 0;
> +       }
> +
> +#ifdef CONFIG_NET_NS
> +       vport = OVS_CB(skb)->input_vport;
> +       if (!vport)
> +               return EINVAL;
> +
> +       net = vport->dp->net;
> +#else
> +       net = &init_net;
> +#endif
> +
> +       /* The conntrack module expects to be working at L3. */
> +       skb_pull(skb, nh_ofs);
> +
> +       /* xxx What's the best return val? */
> +       if (nf_conntrack_in(net, PF_INET, NF_INET_PRE_ROUTING, skb) !=
> NF_ACCEPT)
> +               return EINVAL;
> +
> +       if (nf_conntrack_confirm(skb) != NF_ACCEPT)
> +               return EINVAL;
> +
> +       /* Point back to L2, which OVS expects. */
> +    skb_push(skb, nh_ofs);
> +
> +       key->phy.conn_state = ovs_map_nfctinfo(skb);
> +
> +       return 0;
> +}
> +
>  static int execute_set_action(struct sk_buff *skb,
>                                  const struct nlattr *nested_attr)
>  {
> @@ -897,6 +938,10 @@ static int do_execute_actions(struct datapath *dp,
> struct sk_buff *skb,
>
>                 case OVS_ACTION_ATTR_RECIRC:
>                         err = execute_recirc(dp, skb, a, rem);
> +                       /* xxx Work around crash when recirculating
> without forwarding. */
> +                       /* xxx It's being fixed upstream. */
> +               if (last_action(a, rem))
> +                               return err;
>                         break;
>
>                 case OVS_ACTION_ATTR_SET:
> @@ -906,6 +951,10 @@ static int do_execute_actions(struct datapath *dp,
> struct sk_buff *skb,
>                 case OVS_ACTION_ATTR_SAMPLE:
>                         err = sample(dp, skb, a);
>                         break;
> +
> +               case OVS_ACTION_ATTR_CONNTRACK:
> +                       err = conntrack(skb, nla_get_u16(a));
> +                       break;
>                 }
>
>                 if (unlikely(err)) {
> diff --git a/datapath/datapath.c b/datapath/datapath.c
> index 3ca9716..75a53fb 100644
> --- a/datapath/datapath.c
> +++ b/datapath/datapath.c
> @@ -264,8 +264,17 @@ void ovs_dp_process_packet(struct sk_buff *skb, bool
> recirc)
>         stats = this_cpu_ptr(dp->stats_percpu);
>
>         /* Look up flow. */
> +       /* xxx Are we better off resetting the SKB hash, since we've
> changed
> +        * xxx the value of a field?  Will we always have collision for
> packets
> +        * xxx that only vary based on the conn_state? */
> +#if 0
>         flow = ovs_flow_tbl_lookup_stats(&dp->table, pkt_key,
> skb_get_hash(skb),
>                                          &n_mask_hit);
> +#else
> +       /* xxx Gross, clearing hash. */
> +       flow = ovs_flow_tbl_lookup_stats(&dp->table, pkt_key, 0,
> +                                        &n_mask_hit);
> +#endif
>         if (unlikely(!flow)) {
>                 struct dp_upcall_info upcall;
>
> diff --git a/datapath/flow.c b/datapath/flow.c
> index 12ebc5b..393c490 100644
> --- a/datapath/flow.c
> +++ b/datapath/flow.c
> @@ -42,6 +42,7 @@
>  #include <net/ip.h>
>  #include <net/ipv6.h>
>  #include <net/ndisc.h>
> +#include <linux/netfilter/nf_conntrack_common.h>
>
>  #include "datapath.h"
>  #include "flow.h"
> @@ -678,6 +679,29 @@ int ovs_flow_key_update(struct sk_buff *skb, struct
> sw_flow_key *key)
>         return key_extract(skb, key);
>  }
>
> +/* Map SKB connection state into the values used by flow definition. */
> +u8 ovs_map_nfctinfo(struct sk_buff *skb)
> +{
> +       if (!skb->nfct)
> +               return 0;
> +
> +       /* xxx This should use #defines instead of numbers. */
> +       if (skb->nfctinfo == IP_CT_ESTABLISHED)
> +               return 0x82;
> +       else if (skb->nfctinfo == IP_CT_RELATED)
> +               return 0x84;
> +       else if (skb->nfctinfo == IP_CT_NEW)
> +               return 0x81;
> +       else if (skb->nfctinfo == IP_CT_ESTABLISHED_REPLY)
> +               return 0xc2;
> +       else if (skb->nfctinfo == IP_CT_RELATED_REPLY)
> +               return 0xc4;
> +       else if (skb->nfctinfo == IP_CT_NEW_REPLY)
> +               return 0xc1;
> +       else
> +               return 0x80;
> +}
> +
>  int ovs_flow_key_extract(const struct ovs_tunnel_info *tun_info,
>                          struct sk_buff *skb,
>                          struct sw_flow_key *key)
> @@ -704,6 +728,7 @@ int ovs_flow_key_extract(const struct ovs_tunnel_info
> *tun_info,
>         key->phy.priority = skb->priority;
>         key->phy.in_port = OVS_CB(skb)->input_vport->port_no;
>         key->phy.skb_mark = skb->mark;
> +       key->phy.conn_state = ovs_map_nfctinfo(skb);
>         key->ovs_flow_hash = 0;
>         key->recirc_id = 0;
>
> diff --git a/datapath/flow.h b/datapath/flow.h
> index 44ed10d..22f2c83 100644
> --- a/datapath/flow.h
> +++ b/datapath/flow.h
> @@ -130,6 +130,7 @@ struct sw_flow_key {
>                 u32     priority;       /* Packet QoS priority. */
>                 u32     skb_mark;       /* SKB mark. */
>                 u16     in_port;        /* Input switch port (or
> DP_MAX_PORTS). */
> +               u8  conn_state; /* Connection state. */
>         } __packed phy; /* Safe when right after 'tun_key'. */
>         u32 ovs_flow_hash;              /* Datapath computed hash value.
> */
>         u32 recirc_id;                  /* Recirculation ID.  */
> @@ -252,6 +253,8 @@ void ovs_flow_stats_get(const struct sw_flow *, struct
> ovs_flow_stats *,
>  void ovs_flow_stats_clear(struct sw_flow *);
>  u64 ovs_flow_used_time(unsigned long flow_jiffies);
>
> +u8 ovs_map_nfctinfo(struct sk_buff *skb);
> +
>  int ovs_flow_key_extract(const struct ovs_tunnel_info *tun_info,
>                          struct sk_buff *skb, struct sw_flow_key *key);
>  /* Extract key from packet coming from userspace. */
> diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
> index 69d1919..caaaaaa 100644
> --- a/datapath/flow_netlink.c
> +++ b/datapath/flow_netlink.c
> @@ -267,7 +267,7 @@ size_t ovs_key_attr_size(void)
>  {
>         /* Whenever adding new OVS_KEY_ FIELDS, we should consider
>          * updating this function.  */
> -       BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 22);
> +       BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 23);
>
>         return    nla_total_size(4)   /* OVS_KEY_ATTR_PRIORITY */
>                 + nla_total_size(0)   /* OVS_KEY_ATTR_TUNNEL */
> @@ -276,6 +276,7 @@ size_t ovs_key_attr_size(void)
>                 + nla_total_size(4)   /* OVS_KEY_ATTR_SKB_MARK */
>                 + nla_total_size(4)   /* OVS_KEY_ATTR_DP_HASH */
>                 + nla_total_size(4)   /* OVS_KEY_ATTR_RECIRC_ID */
> +               + nla_total_size(1)   /* OVS_KEY_ATTR_CONN_STATE */
>                 + nla_total_size(12)  /* OVS_KEY_ATTR_ETHERNET */
>                 + nla_total_size(2)   /* OVS_KEY_ATTR_ETHERTYPE */
>                 + nla_total_size(4)   /* OVS_KEY_ATTR_VLAN */
> @@ -292,6 +293,7 @@ static const int ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = {
>         [OVS_KEY_ATTR_PRIORITY] = sizeof(u32),
>         [OVS_KEY_ATTR_IN_PORT] = sizeof(u32),
>         [OVS_KEY_ATTR_SKB_MARK] = sizeof(u32),
> +       [OVS_KEY_ATTR_CONN_STATE] = sizeof(u8),
>         [OVS_KEY_ATTR_ETHERNET] = sizeof(struct ovs_key_ethernet),
>         [OVS_KEY_ATTR_VLAN] = sizeof(__be16),
>         [OVS_KEY_ATTR_ETHERTYPE] = sizeof(__be16),
> @@ -663,6 +665,13 @@ static int metadata_from_nlattrs(struct sw_flow_match
> *match,  u64 *attrs,
>                         return -EINVAL;
>                 *attrs &= ~(1ULL << OVS_KEY_ATTR_TUNNEL);
>         }
> +
> +       if (*attrs & (1ULL << OVS_KEY_ATTR_CONN_STATE)) {
> +               uint8_t conn_state =
> nla_get_u8(a[OVS_KEY_ATTR_CONN_STATE]);
> +
> +               SW_FLOW_KEY_PUT(match, phy.conn_state, conn_state,
> is_mask);
> +               *attrs &= ~(1ULL << OVS_KEY_ATTR_CONN_STATE);
> +       }
>         return 0;
>  }
>
> @@ -1146,6 +1155,9 @@ int ovs_nla_put_flow(struct datapath *dp, const
> struct sw_flow_key *swkey,
>         if (nla_put_u32(skb, OVS_KEY_ATTR_SKB_MARK, output->phy.skb_mark))
>                 goto nla_put_failure;
>
> +       if (nla_put_u8(skb, OVS_KEY_ATTR_CONN_STATE,
> output->phy.conn_state))
> +               goto nla_put_failure;
> +
>         nla = nla_reserve(skb, OVS_KEY_ATTR_ETHERNET, sizeof(*eth_key));
>         if (!nla)
>                 goto nla_put_failure;
> @@ -1616,6 +1628,7 @@ static int validate_set(const struct nlattr *a,
>
>         case OVS_KEY_ATTR_PRIORITY:
>         case OVS_KEY_ATTR_SKB_MARK:
> +       case OVS_KEY_ATTR_CONN_STATE:
>         case OVS_KEY_ATTR_ETHERNET:
>                 break;
>
> @@ -1750,7 +1763,8 @@ static int __ovs_nla_copy_actions(const struct
> nlattr *attr,
>                         [OVS_ACTION_ATTR_POP_VLAN] = 0,
>                         [OVS_ACTION_ATTR_SET] = (u32)-1,
>                         [OVS_ACTION_ATTR_SAMPLE] = (u32)-1,
> -                       [OVS_ACTION_ATTR_HASH] = sizeof(struct
> ovs_action_hash)
> +                       [OVS_ACTION_ATTR_HASH] = sizeof(struct
> ovs_action_hash),
> +                       [OVS_ACTION_ATTR_CONNTRACK] = sizeof(u16)
>                 };
>                 const struct ovs_action_push_vlan *vlan;
>                 int type = nla_type(a);
> @@ -1856,6 +1870,9 @@ static int __ovs_nla_copy_actions(const struct
> nlattr *attr,
>                         skip_copy = true;
>                         break;
>
> +               case OVS_ACTION_ATTR_CONNTRACK:
> +                       break;
> +
>                 default:
>                         return -EINVAL;
>                 }
> diff --git a/datapath/linux/compat/include/linux/openvswitch.h
> b/datapath/linux/compat/include/linux/openvswitch.h
> index 9c18b3b..50dfcac 100644
> --- a/datapath/linux/compat/include/linux/openvswitch.h
> +++ b/datapath/linux/compat/include/linux/openvswitch.h
> @@ -329,6 +329,7 @@ enum ovs_key_attr {
>         OVS_KEY_ATTR_MPLS,      /* array of struct ovs_key_mpls.
>                                  * The implementation may restrict
>                                  * the accepted length of the array. */
> +       OVS_KEY_ATTR_CONN_STATE,/* u8 conn state */
>
>  #ifdef __KERNEL__
>         /* Only used within kernel data path. */
> @@ -603,6 +604,7 @@ struct ovs_action_hash {
>   * indicate the new packet contents. This could potentially still be
>   * %ETH_P_MPLS if the resulting MPLS label stack is not empty.  If there
>   * is no MPLS label stack, as determined by ethertype, no action is taken.
> + * @OVS_ACTION_ATTR_CONNTRACK: Track the connection.
>   *
>   * Only a single header can be set with a single %OVS_ACTION_ATTR_SET.
> Not all
>   * fields within a header are modifiable, e.g. the IPv4 protocol and
> fragment
> @@ -621,6 +623,7 @@ enum ovs_action_attr {
>         OVS_ACTION_ATTR_HASH,         /* struct ovs_action_hash. */
>         OVS_ACTION_ATTR_PUSH_MPLS,    /* struct ovs_action_push_mpls. */
>         OVS_ACTION_ATTR_POP_MPLS,     /* __be16 ethertype. */
> +       OVS_ACTION_ATTR_CONNTRACK,    /* u16 zone. */
>         __OVS_ACTION_ATTR_MAX
>  };
>
> diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
> index bbf3388..57f0133 100644
> --- a/include/openflow/nicira-ext.h
> +++ b/include/openflow/nicira-ext.h
> @@ -968,6 +968,28 @@ OFP_ASSERT(sizeof(struct nx_async_config) == 24);
>   * Masking: not maskable. */
>  #define NXM_NX_RECIRC_ID   NXM_HEADER  (0x0001, 36, 4)
>
> +/* Connection tracking state.
> + *
> + * The connection tracking state is populated by the NXAST_CONNTRACK
> + * action.  The following flags are defined:
> + *
> + *   - CONN_STATE_TRACKED (0x80): Connection tracking has occurred.
> + *   - CONN_STATE_REPLY (0x40): This flow did not initiate the connection.
> + *
> + * The following values describe the state of the connection:
> + *
> + *   - New (0x01): This is the beginning of a new connection.
> + *   - Established (0x02): This is part of an already existing connection.
> + *   - Related (0x04): This is a new connection that is "expected".
> + *
> + * Prereqs: None.
> + *
> + * Format: 8-bit fully maskable
> + *
> + * Masking: Fully maskable. */
> +#define NXM_NX_CONN_STATE   NXM_HEADER  (0x0001, 37, 1)
> +#define NXM_NX_CONN_STATE_W NXM_HEADER_W(0x0001, 37, 1)
> +
>  /* ## --------------------- ## */
>  /* ## Requests and replies. ## */
>  /* ## --------------------- ## */
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 7401293..ef6b722 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -2241,6 +2241,7 @@ dp_execute_cb(void *aux_, struct dpif_packet
> **packets, int cnt,
>      case OVS_ACTION_ATTR_SET:
>      case OVS_ACTION_ATTR_SAMPLE:
>      case OVS_ACTION_ATTR_UNSPEC:
> +    case OVS_ACTION_ATTR_CONNTRACK:
>      case __OVS_ACTION_ATTR_MAX:
>          OVS_NOT_REACHED();
>      }
> diff --git a/lib/dpif.c b/lib/dpif.c
> index b2a1972..d4edbdb 100644
> --- a/lib/dpif.c
> +++ b/lib/dpif.c
> @@ -1043,6 +1043,7 @@ dpif_execute_helper_cb(void *aux_, struct
> dpif_packet **packets, int cnt,
>      case OVS_ACTION_ATTR_SET:
>      case OVS_ACTION_ATTR_SAMPLE:
>      case OVS_ACTION_ATTR_UNSPEC:
> +    case OVS_ACTION_ATTR_CONNTRACK:
>      case __OVS_ACTION_ATTR_MAX:
>          OVS_NOT_REACHED();
>      }
> diff --git a/lib/flow.c b/lib/flow.c
> index 29b331e..a27b4f5 100644
> --- a/lib/flow.c
> +++ b/lib/flow.c
> @@ -70,11 +70,12 @@ BUILD_ASSERT_DECL(offsetof(struct flow, nw_frag) + 3
>                    offsetof(struct flow, nw_proto) / 4
>                    == offsetof(struct flow, nw_tos) / 4);
>
> -/* TCP flags in the first half of a BE32, zeroes in the other half. */
> +/* TCP flags in the first half of a BE32, 'conn_state' and pad in the
> + * other half. */
>  BUILD_ASSERT_DECL(offsetof(struct flow, tcp_flags) + 2
> -                  == offsetof(struct flow, pad) &&
> +                  == offsetof(struct flow, conn_state) &&
>                    offsetof(struct flow, tcp_flags) / 4
> -                  == offsetof(struct flow, pad) / 4);
> +                  == offsetof(struct flow, conn_state) / 4);
>  #if WORDS_BIGENDIAN
>  #define TCP_FLAGS_BE32(tcp_ctl) ((OVS_FORCE
> ovs_be32)TCP_FLAGS_BE16(tcp_ctl) \
>                                   << 16)
> @@ -138,6 +139,19 @@ struct mf_ctx {
>  #define miniflow_push_be32_(MF, OFS, VALUE) \
>      miniflow_push_uint32_(MF, OFS, (OVS_FORCE uint32_t)(VALUE))
>
> +/* xxx Possibly clean this up. Assert if another value has been pushed. */
> +/* Caller must have previously called a miniflow_push_* macro for "OFS"
> + * with no other push calls in between. */
> +#define miniflow_update_uint32_(MF, OFS, VALUE, MASK)           \
> +{                                                               \
> +       MINIFLOW_ASSERT(MF.data < MF.end && (OFS) % 4 == 0);        \
> +    *(MF.data-1) |= (VALUE & MASK);                             \
> +}
> +
> +#define miniflow_update_be32_(MF, OFS, VALUE, MASK)               \
> +    miniflow_update_uint32_(MF, OFS, (OVS_FORCE uint32_t)(VALUE), \
> +                                     (OVS_FORCE uint32_t)(MASK))
> +
>  #define miniflow_push_uint16_(MF, OFS, VALUE)                   \
>  {                                                               \
>      MINIFLOW_ASSERT(MF.data < MF.end &&                                 \
> @@ -188,6 +202,12 @@ struct mf_ctx {
>          }                                                               \
>      }
>
> +#define miniflow_update_uint32(MF, FIELD, VALUE, MASK)                  \
> +    miniflow_update_uint32_(MF, offsetof(struct flow, FIELD), VALUE, MASK)
> +
> +#define miniflow_update_be32(MF, FIELD, VALUE, MASK)                    \
> +    miniflow_update_be32_(MF, offsetof(struct flow, FIELD), VALUE, MASK)
> +
>  #define miniflow_push_uint16(MF, FIELD, VALUE)                          \
>      miniflow_push_uint16_(MF, offsetof(struct flow, FIELD), VALUE)
>
> @@ -570,13 +590,28 @@ miniflow_extract(struct ofpbuf *packet, const struct
> pkt_metadata *md,
>      miniflow_push_be32(mf, nw_frag,
>                         BYTES_TO_BE32(nw_frag, nw_tos, nw_ttl, nw_proto));
>
> +    /* xxx This is hacky to get around ICMPv6 issues. */
> +    if ((nw_frag & FLOW_NW_FRAG_LATER) || (nw_proto != IPPROTO_ICMPV6)) {
> +        if (md) {
> +            /* xxx Can't be use _check() version, since state may be 0 */
> +            miniflow_push_be32(mf, tcp_flags,
> +                                     BYTES_TO_BE32(0, 0, md->conn_state,
> 0));
> +        } else {
> +            /* xxx Hack so tcp_flags always has pushed entry */
> +            miniflow_push_be32(mf, tcp_flags,
> +                                     BYTES_TO_BE32(0, 0, 0, 0));
> +        }
> +    }
> +
>      if (OVS_LIKELY(!(nw_frag & FLOW_NW_FRAG_LATER))) {
>          if (OVS_LIKELY(nw_proto == IPPROTO_TCP)) {
>              if (OVS_LIKELY(size >= TCP_HEADER_LEN)) {
>                  const struct tcp_header *tcp = data;
>
> -                miniflow_push_be32(mf, tcp_flags,
> -                                   TCP_FLAGS_BE32(tcp->tcp_ctl));
> +                miniflow_update_be32(mf, tcp_flags,
> +                                     TCP_FLAGS_BE32(tcp->tcp_ctl),
> +                                     htonl(0xffff0000));
> +
>                  miniflow_push_words(mf, tp_src, &tcp->tcp_src, 1);
>              }
>          } else if (OVS_LIKELY(nw_proto == IPPROTO_UDP)) {
> @@ -622,6 +657,17 @@ miniflow_extract(struct ofpbuf *packet, const struct
> pkt_metadata *md,
>                          miniflow_push_words(mf, nd_target, nd_target,
>                                              sizeof *nd_target / 4);
>                      }
> +                    /* xxx This is gross. */
> +                    if (md) {
> +                        /* xxx Can't be use _check() version, since
> +                         * xxx state may be 0 */
> +                        miniflow_push_be32(mf, tcp_flags,
> +                                     BYTES_TO_BE32(0, 0, md->conn_state,
> 0));
> +                    } else {
> +                        /* xxx Hack so tcp_flags always has pushed entry
> */
> +                        miniflow_push_be32(mf, tcp_flags,
> +                                           BYTES_TO_BE32(0, 0, 0, 0));
> +                    }
>                      miniflow_push_be16(mf, tp_src,
> htons(icmp->icmp6_type));
>                      miniflow_push_be16(mf, tp_dst,
> htons(icmp->icmp6_code));
>                  }
> @@ -665,7 +711,7 @@ flow_unwildcard_tp_ports(const struct flow *flow,
> struct flow_wildcards *wc)
>  void
>  flow_get_metadata(const struct flow *flow, struct flow_metadata *fmd)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 27);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 28);
>
>      fmd->dp_hash = flow->dp_hash;
>      fmd->recirc_id = flow->recirc_id;
> @@ -675,6 +721,7 @@ flow_get_metadata(const struct flow *flow, struct
> flow_metadata *fmd)
>      fmd->metadata = flow->metadata;
>      memcpy(fmd->regs, flow->regs, sizeof fmd->regs);
>      fmd->pkt_mark = flow->pkt_mark;
> +    fmd->conn_state = flow->conn_state;
>      fmd->in_port = flow->in_port.ofp_port;
>  }
>
> @@ -1335,7 +1382,7 @@ flow_push_mpls(struct flow *flow, int n, ovs_be16
> mpls_eth_type,
>          flow->mpls_lse[0] = set_mpls_lse_values(ttl, tc, 1, htonl(label));
>
>          /* Clear all L3 and L4 fields. */
> -        BUILD_ASSERT(FLOW_WC_SEQ == 27);
> +        BUILD_ASSERT(FLOW_WC_SEQ == 28);
>          memset((char *) flow + FLOW_SEGMENT_2_ENDS_AT, 0,
>                 sizeof(struct flow) - FLOW_SEGMENT_2_ENDS_AT);
>      }
> diff --git a/lib/flow.h b/lib/flow.h
> index 3b8d24d..7405437 100644
> --- a/lib/flow.h
> +++ b/lib/flow.h
> @@ -38,7 +38,7 @@ struct pkt_metadata;
>  /* This sequence number should be incremented whenever anything involving
> flows
>   * or the wildcarding of flows changes.  This will cause build assertion
>   * failures in places which likely need to be updated. */
> -#define FLOW_WC_SEQ 27
> +#define FLOW_WC_SEQ 28
>
>  /* Number of Open vSwitch extension 32-bit registers. */
>  #define FLOW_N_REGS 8
> @@ -122,7 +122,8 @@ struct flow {
>      uint8_t arp_tha[6];         /* ARP/ND target hardware address. */
>      struct in6_addr nd_target;  /* IPv6 neighbor discovery (ND) target. */
>      ovs_be16 tcp_flags;         /* TCP flags. With L3 to avoid matching
> L4. */
> -    ovs_be16 pad;               /* Padding. */
> +    uint8_t conn_state ;        /* Connection state. */
> +    uint8_t pad;                /* Padding. */
>
>      /* L4 */
>      ovs_be16 tp_src;            /* TCP/UDP/SCTP source port. */
> @@ -139,7 +140,7 @@ BUILD_ASSERT_DECL(sizeof(struct flow) % 4 == 0);
>  /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
>  BUILD_ASSERT_DECL(offsetof(struct flow, dp_hash) + sizeof(uint32_t)
>                    == sizeof(struct flow_tnl) + 176
> -                  && FLOW_WC_SEQ == 27);
> +                  && FLOW_WC_SEQ == 28);
>
>  /* Incremental points at which flow classification may be performed in
>   * segments.
> @@ -172,6 +173,7 @@ struct flow_metadata {
>      ovs_be64 metadata;               /* OpenFlow 1.1+ metadata field. */
>      uint32_t regs[FLOW_N_REGS];      /* Registers. */
>      uint32_t pkt_mark;               /* Packet mark. */
> +    uint8_t conn_state;              /* Connection state. */
>      ofp_port_t in_port;              /* OpenFlow port or zero. */
>  };
>
> @@ -657,6 +659,7 @@ pkt_metadata_from_flow(const struct flow *flow)
>      md.skb_priority = flow->skb_priority;
>      md.pkt_mark = flow->pkt_mark;
>      md.in_port = flow->in_port;
> +    md.conn_state = flow->conn_state;
>
>      return md;
>  }
> diff --git a/lib/match.c b/lib/match.c
> index 8364906..5a63628 100644
> --- a/lib/match.c
> +++ b/lib/match.c
> @@ -60,6 +60,10 @@ match_wc_init(struct match *match, const struct flow
> *flow)
>          memset(&wc->masks.pkt_mark, 0xff, sizeof wc->masks.pkt_mark);
>      }
>
> +    if (flow->conn_state) {
> +        memset(&wc->masks.conn_state, 0xff, sizeof wc->masks.conn_state);
> +    }
> +
>      for (i = 0; i < FLOW_N_REGS; i++) {
>          if (flow->regs[i]) {
>              memset(&wc->masks.regs[i], 0xff, sizeof wc->masks.regs[i]);
> @@ -335,6 +339,20 @@ match_set_pkt_mark_masked(struct match *match,
> uint32_t pkt_mark, uint32_t mask)
>  }
>
>  void
> +match_set_conn_state(struct match *match, uint8_t conn_state)
> +{
> +    match_set_conn_state_masked(match, conn_state, UINT8_MAX);
> +}
> +
> +void
> +match_set_conn_state_masked(struct match *match, uint8_t conn_state,
> +                            uint8_t mask)
> +{
> +    match->flow.conn_state = conn_state & mask;
> +    match->wc.masks.conn_state = mask;
> +}
> +
> +void
>  match_set_dl_type(struct match *match, ovs_be16 dl_type)
>  {
>      match->wc.masks.dl_type = OVS_BE16_MAX;
> @@ -867,6 +885,19 @@ format_ipv6_netmask(struct ds *s, const char *name,
>  }
>
>  static void
> +format_uint8_masked(struct ds *s, const char *name,
> +                   uint8_t value, uint8_t mask)
> +{
> +    if (mask) {
> +        ds_put_format(s, "%s=%#"PRIx8, name, value);
> +        if (mask != UINT8_MAX) {
> +            ds_put_format(s, "/%#"PRIx8, mask);
> +        }
> +        ds_put_char(s, ',');
> +    }
> +}
> +
> +static void
>  format_be16_masked(struct ds *s, const char *name,
>                     ovs_be16 value, ovs_be16 mask)
>  {
> @@ -959,7 +990,7 @@ match_format(const struct match *match, struct ds *s,
> unsigned int priority)
>
>      int i;
>
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 27);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 28);
>
>      if (priority != OFP_DEFAULT_PRIORITY) {
>          ds_put_format(s, "priority=%u,", priority);
> @@ -981,6 +1012,13 @@ match_format(const struct match *match, struct ds
> *s, unsigned int priority)
>          ds_put_format(s, "skb_priority=%#"PRIx32",", f->skb_priority);
>      }
>
> +    if (wc->masks.conn_state) {
> +        /* xxx Spell out the flags? To be prettier? */
> +        /* xxx If pretty print, remove format_uint8_masked(). */
> +        format_uint8_masked(s, "conn_state", f->conn_state,
> +                            wc->masks.conn_state);
> +    }
> +
>      if (wc->masks.dl_type) {
>          skip_type = true;
>          if (f->dl_type == htons(ETH_TYPE_IP)) {
> diff --git a/lib/match.h b/lib/match.h
> index ce9fb28..b8e3745 100644
> --- a/lib/match.h
> +++ b/lib/match.h
> @@ -71,6 +71,9 @@ void match_set_tun_flags_masked(struct match *match,
> uint16_t flags, uint16_t ma
>  void match_set_in_port(struct match *, ofp_port_t ofp_port);
>  void match_set_pkt_mark(struct match *, uint32_t pkt_mark);
>  void match_set_pkt_mark_masked(struct match *, uint32_t pkt_mark,
> uint32_t mask);
> +void match_set_conn_state(struct match *, uint8_t conn_state);
> +void match_set_conn_state_masked(struct match *, uint8_t conn_state,
> +                                 uint8_t mask);
>  void match_set_skb_priority(struct match *, uint32_t skb_priority);
>  void match_set_dl_type(struct match *, ovs_be16);
>  void match_set_dl_src(struct match *, const uint8_t[6]);
> diff --git a/lib/meta-flow.c b/lib/meta-flow.c
> index b76c11d..a7d0b91 100644
> --- a/lib/meta-flow.c
> +++ b/lib/meta-flow.c
> @@ -207,6 +207,18 @@ const struct mf_field mf_fields[MFF_N_IDS] = {
>          OFPUTIL_P_NXM_OXM_ANY,
>          OFPUTIL_P_NXM_OXM_ANY,
>          -1,
> +    }, {
> +        MFF_CONN_STATE, "conn_state", NULL,
> +        MF_FIELD_SIZES(u8),
> +        MFM_FULLY,
> +        MFS_HEXADECIMAL,
> +        MFP_NONE,
> +        true,
> +        NXM_NX_CONN_STATE, "NXM_NX_CONN_STATE",
> +        NXM_NX_CONN_STATE, "NXM_NX_CONN_STATE", 0,
> +        OFPUTIL_P_NXM_OXM_ANY,
> +        OFPUTIL_P_NXM_OXM_ANY,
> +        -1,
>      },
>
>  #define REGISTER(IDX)                           \
> @@ -943,6 +955,8 @@ mf_is_all_wild(const struct mf_field *mf, const struct
> flow_wildcards *wc)
>          return !wc->masks.skb_priority;
>      case MFF_PKT_MARK:
>          return !wc->masks.pkt_mark;
> +    case MFF_CONN_STATE:
> +        return !wc->masks.conn_state;
>      CASE_MFF_REGS:
>          return !wc->masks.regs[mf->id - MFF_REG0];
>      CASE_MFF_XREGS:
> @@ -1184,6 +1198,7 @@ mf_is_value_valid(const struct mf_field *mf, const
> union mf_value *value)
>      case MFF_IN_PORT:
>      case MFF_SKB_PRIORITY:
>      case MFF_PKT_MARK:
> +    case MFF_CONN_STATE:
>      CASE_MFF_REGS:
>      CASE_MFF_XREGS:
>      case MFF_ETH_SRC:
> @@ -1312,6 +1327,10 @@ mf_get_value(const struct mf_field *mf, const
> struct flow *flow,
>          value->be32 = htonl(flow->pkt_mark);
>          break;
>
> +    case MFF_CONN_STATE:
> +        value->u8 = flow->conn_state;
> +        break;
> +
>      CASE_MFF_REGS:
>          value->be32 = htonl(flow->regs[mf->id - MFF_REG0]);
>          break;
> @@ -1518,6 +1537,10 @@ mf_set_value(const struct mf_field *mf,
>          match_set_pkt_mark(match, ntohl(value->be32));
>          break;
>
> +    case MFF_CONN_STATE:
> +        match_set_conn_state(match, value->u8);
> +        break;
> +
>      CASE_MFF_REGS:
>          match_set_reg(match, mf->id - MFF_REG0, ntohl(value->be32));
>          break;
> @@ -1741,6 +1764,10 @@ mf_set_flow_value(const struct mf_field *mf,
>          flow->pkt_mark = ntohl(value->be32);
>          break;
>
> +    case MFF_CONN_STATE:
> +        flow->conn_state = value->u8;
> +        break;
> +
>      CASE_MFF_REGS:
>          flow->regs[mf->id - MFF_REG0] = ntohl(value->be32);
>          break;
> @@ -1962,6 +1989,11 @@ mf_set_wild(const struct mf_field *mf, struct match
> *match)
>          match->wc.masks.pkt_mark = 0;
>          break;
>
> +    case MFF_CONN_STATE:
> +        match->flow.conn_state = 0;
> +        match->wc.masks.conn_state = 0;
> +        break;
> +
>      CASE_MFF_REGS:
>          match_set_reg_masked(match, mf->id - MFF_REG0, 0, 0);
>          break;
> @@ -2203,6 +2235,10 @@ mf_set(const struct mf_field *mf,
>                                    ntohl(mask->be32));
>          break;
>
> +    case MFF_CONN_STATE:
> +        match_set_conn_state_masked(match, value->u8, mask->u8);
> +        break;
> +
>      case MFF_ETH_DST:
>          match_set_dl_dst_masked(match, value->mac, mask->mac);
>          break;
> diff --git a/lib/meta-flow.h b/lib/meta-flow.h
> index c11f7ab..865ce59 100644
> --- a/lib/meta-flow.h
> +++ b/lib/meta-flow.h
> @@ -46,6 +46,7 @@ enum OVS_PACKED_ENUM mf_field_id {
>      MFF_IN_PORT_OXM,            /* be32 */
>      MFF_SKB_PRIORITY,           /* be32 */
>      MFF_PKT_MARK,               /* be32 */
> +    MFF_CONN_STATE,             /* u8 */
>
>  #if FLOW_N_REGS == 8
>      MFF_REG0,                   /* be32 */
> diff --git a/lib/nx-match.c b/lib/nx-match.c
> index 1d3205f..dec38c7 100644
> --- a/lib/nx-match.c
> +++ b/lib/nx-match.c
> @@ -616,7 +616,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm,
> const struct match *match,
>      int match_len;
>      int i;
>
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 27);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 28);
>
>      /* Metadata. */
>      if (match->wc.masks.dp_hash) {
> @@ -732,6 +732,10 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm,
> const struct match *match,
>      nxm_put_32m(b, mf_oxm_header(MFF_PKT_MARK, oxm),
> htonl(flow->pkt_mark),
>                  htonl(match->wc.masks.pkt_mark));
>
> +    /* Connection state. */
> +    nxm_put_8m(b, NXM_NX_CONN_STATE, flow->conn_state,
> +               match->wc.masks.conn_state);
> +
>      /* OpenFlow 1.1+ Metadata. */
>      nxm_put_64m(b, mf_oxm_header(MFF_METADATA, oxm),
>                  flow->metadata, match->wc.masks.metadata);
> diff --git a/lib/odp-execute.c b/lib/odp-execute.c
> index e1e9b57..8681eda 100644
> --- a/lib/odp-execute.c
> +++ b/lib/odp-execute.c
> @@ -88,6 +88,10 @@ odp_execute_set_action(struct dpif_packet *packet,
> const struct nlattr *a,
>          md->pkt_mark = nl_attr_get_u32(a);
>          break;
>
> +    case OVS_KEY_ATTR_CONN_STATE:
> +        md->conn_state = nl_attr_get_u8(a);
> +        break;
> +
>      case OVS_KEY_ATTR_ETHERNET:
>          odp_eth_set_addrs(&packet->ofpbuf,
>                         nl_attr_get_unspec(a, sizeof(struct
> ovs_key_ethernet)));
> @@ -314,6 +318,10 @@ odp_execute_actions__(void *dp, struct dpif_packet
> **packets, int cnt,
>              }
>              break;
>
> +        case OVS_ACTION_ATTR_CONNTRACK:
> +            /* xxx I don't think there's anything we can do here. */
> +            break;
> +
>          case OVS_ACTION_ATTR_UNSPEC:
>          case __OVS_ACTION_ATTR_MAX:
>              OVS_NOT_REACHED();
> diff --git a/lib/odp-util.c b/lib/odp-util.c
> index ffc3673..1c02db7 100644
> --- a/lib/odp-util.c
> +++ b/lib/odp-util.c
> @@ -83,6 +83,7 @@ odp_action_len(uint16_t type)
>      case OVS_ACTION_ATTR_HASH: return sizeof(struct ovs_action_hash);
>      case OVS_ACTION_ATTR_SET: return -2;
>      case OVS_ACTION_ATTR_SAMPLE: return -2;
> +    case OVS_ACTION_ATTR_CONNTRACK: return sizeof(uint16_t);
>
>      case OVS_ACTION_ATTR_UNSPEC:
>      case __OVS_ACTION_ATTR_MAX:
> @@ -104,6 +105,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char
> *namebuf, size_t bufsize)
>      case OVS_KEY_ATTR_ENCAP: return "encap";
>      case OVS_KEY_ATTR_PRIORITY: return "skb_priority";
>      case OVS_KEY_ATTR_SKB_MARK: return "skb_mark";
> +    case OVS_KEY_ATTR_CONN_STATE: return "conn_state";
>      case OVS_KEY_ATTR_TUNNEL: return "tunnel";
>      case OVS_KEY_ATTR_IN_PORT: return "in_port";
>      case OVS_KEY_ATTR_ETHERNET: return "eth";
> @@ -477,6 +479,11 @@ format_odp_action(struct ds *ds, const struct nlattr
> *a)
>      case OVS_ACTION_ATTR_SAMPLE:
>          format_odp_sample_action(ds, a);
>          break;
> +    case OVS_ACTION_ATTR_CONNTRACK: {
> +        uint16_t zone = nl_attr_get_u16(a);
> +        ds_put_format(ds, "conntrack(zone=%"PRIu16")", zone);
> +        break;
> +    }
>      case OVS_ACTION_ATTR_UNSPEC:
>      case __OVS_ACTION_ATTR_MAX:
>      default:
> @@ -741,6 +748,16 @@ parse_odp_action(const char *s, const struct simap
> *port_names,
>          }
>      }
>
> +    {
> +        int zone;
> +        int n = -1;
> +
> +        if (ovs_scan(s, "conntrack(zone=%i)%n", &zone, &n)) {
> +            nl_msg_put_u16(actions, OVS_ACTION_ATTR_CONNTRACK, zone);
> +            return n;
> +        }
> +    }
> +
>      return -EINVAL;
>  }
>
> @@ -795,6 +812,7 @@ odp_flow_key_attr_len(uint16_t type)
>      case OVS_KEY_ATTR_SKB_MARK: return 4;
>      case OVS_KEY_ATTR_DP_HASH: return 4;
>      case OVS_KEY_ATTR_RECIRC_ID: return 4;
> +    case OVS_KEY_ATTR_CONN_STATE: return 1;
>      case OVS_KEY_ATTR_TUNNEL: return -2;
>      case OVS_KEY_ATTR_IN_PORT: return 4;
>      case OVS_KEY_ATTR_ETHERNET: return sizeof(struct ovs_key_ethernet);
> @@ -1163,6 +1181,13 @@ format_odp_key_attr(const struct nlattr *a, const
> struct nlattr *ma,
>          }
>          break;
>
> +    case OVS_KEY_ATTR_CONN_STATE:
> +        ds_put_format(ds, "%#"PRIx8, nl_attr_get_u8(a));
> +        if (!is_exact) {
> +            ds_put_format(ds, "/%#"PRIx8, nl_attr_get_u8(ma));
> +        }
> +        break;
> +
>      case OVS_KEY_ATTR_TUNNEL:
>          memset(&tun_key, 0, sizeof tun_key);
>          if (odp_tun_key_from_attr(a, &tun_key) == ODP_FIT_ERROR) {
> @@ -1780,6 +1805,25 @@ parse_odp_key_mask_attr(const char *s, const struct
> simap *port_names,
>      }
>
>      {
> +        uint8_t conn_state;
> +        uint8_t conn_state_mask;
> +        int n = -1;
> +
> +        if (mask && ovs_scan(s, "conn_state(%"SCNi8"/%"SCNi8")%n",
> +                             &conn_state, &conn_state_mask, &n)) {
> +            nl_msg_put_u8(key, OVS_KEY_ATTR_CONN_STATE, conn_state);
> +            nl_msg_put_u8(mask, OVS_KEY_ATTR_CONN_STATE, conn_state_mask);
> +            return n;
> +        } else if (ovs_scan(s, "conn_state(%"SCNi8")%n", &conn_state,
> &n)) {
> +            nl_msg_put_u8(key, OVS_KEY_ATTR_CONN_STATE, conn_state);
> +            if (mask) {
> +                nl_msg_put_u8(mask, OVS_KEY_ATTR_CONN_STATE, UINT8_MAX);
> +            }
> +            return n;
> +        }
> +    }
> +
> +    {
>          uint64_t tun_id, tun_id_mask;
>          struct flow_tnl tun_key, tun_key_mask;
>          int n = -1;
> @@ -2594,6 +2638,7 @@ odp_flow_key_from_flow__(struct ofpbuf *buf, const
> struct flow *flow,
>      }
>
>      nl_msg_put_u32(buf, OVS_KEY_ATTR_SKB_MARK, data->pkt_mark);
> +    nl_msg_put_u8(buf, OVS_KEY_ATTR_CONN_STATE, data->conn_state);
>
>      if (recirc) {
>          nl_msg_put_u32(buf, OVS_KEY_ATTR_RECIRC_ID, data->recirc_id);
> @@ -2813,6 +2858,9 @@ odp_key_from_pkt_metadata(struct ofpbuf *buf, const
> struct pkt_metadata *md)
>      }
>
>      nl_msg_put_u32(buf, OVS_KEY_ATTR_SKB_MARK, md->pkt_mark);
> +#if 0
> +    nl_msg_put_u8(buf, OVS_KEY_ATTR_CONN_STATE, md->conn_state);
> +#endif
>
>      /* Add an ingress port attribute if 'odp_in_port' is not the magical
>       * value "ODPP_NONE". */
> @@ -2860,6 +2908,12 @@ odp_key_to_pkt_metadata(const struct nlattr *key,
> size_t key_len,
>              md->pkt_mark = nl_attr_get_u32(nla);
>              wanted_attrs &= ~(1u << OVS_KEY_ATTR_SKB_MARK);
>              break;
> +#if 0
> +        case OVS_KEY_ATTR_CONN_STATE:
> +            md->conn_state = nl_attr_get_u8(nla);
> +            wanted_attrs &= ~(1u << OVS_KEY_ATTR_CONN_STATE);
> +            break;
> +#endif
>          case OVS_KEY_ATTR_TUNNEL: {
>              enum odp_key_fitness res;
>
> @@ -3434,6 +3488,11 @@ odp_flow_key_to_flow__(const struct nlattr *key,
> size_t key_len,
>          expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_SKB_MARK;
>      }
>
> +    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_CONN_STATE)) {
> +        flow->conn_state = nl_attr_get_u8(attrs[OVS_KEY_ATTR_CONN_STATE]);
> +        expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CONN_STATE;
> +    }
> +
>      if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TUNNEL)) {
>          enum odp_key_fitness res;
>
> diff --git a/lib/odp-util.h b/lib/odp-util.h
> index b9b33e5..92c55fc 100644
> --- a/lib/odp-util.h
> +++ b/lib/odp-util.h
> @@ -110,6 +110,7 @@ void odp_portno_names_destroy(struct hmap
> *portno_names);
>   *  OVS_KEY_ATTR_SKB_MARK                4    --     4      8
>   *  OVS_KEY_ATTR_DP_HASH                 4    --     4      8
>   *  OVS_KEY_ATTR_RECIRC_ID               4    --     4      8
> + *  OVS_KEY_ATTR_CONN_STATE              1     3     4      8
>   *  OVS_KEY_ATTR_ETHERNET               12    --     4     16
>   *  OVS_KEY_ATTR_ETHERTYPE               2     2     4      8  (outer
> VLAN ethertype)
>   *  OVS_KEY_ATTR_VLAN                    2     2     4      8
> @@ -119,13 +120,13 @@ void odp_portno_names_destroy(struct hmap
> *portno_names);
>   *  OVS_KEY_ATTR_ICMPV6                  2     2     4      8
>   *  OVS_KEY_ATTR_ND                     28    --     4     32
>   *  ----------------------------------------------------------
> - *  total                                                 488
> + *  total                                                 496
>   *
>   * We include some slack space in case the calculation isn't quite right
> or we
>   * add another field and forget to adjust this value.
>   */
>  #define ODPUTIL_FLOW_KEY_BYTES 512
> -BUILD_ASSERT_DECL(FLOW_WC_SEQ == 27);
> +BUILD_ASSERT_DECL(FLOW_WC_SEQ == 28);
>
>  /* A buffer with sufficient size and alignment to hold an
> nlattr-formatted flow
>   * key.  An array of "struct nlattr" might not, in theory, be sufficiently
> diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
> index 61ee5dc..eca3b50 100644
> --- a/lib/ofp-actions.c
> +++ b/lib/ofp-actions.c
> @@ -267,6 +267,9 @@ enum ofp_raw_action_type {
>
>      /* NX1.0+(29): struct nx_action_sample. */
>      NXAST_RAW_SAMPLE,
> +
> +    /* NX1.0+(32): struct nx_action_conntrack. */
> +    NXAST_RAW_CONNTRACK,
>  };
>
>  /* OpenFlow actions are always a multiple of 8 bytes in length. */
> @@ -3924,6 +3927,86 @@ format_SAMPLE(const struct ofpact_sample *a, struct
> ds *s)
>                    a->obs_domain_id, a->obs_point_id);
>  }
>
> +/* Action structure for NXAST_CONNTRACK.
> + *
> + * Pass traffic to the connection tracker.  If 'flags' is
> + * NX_CONNTRACK_F_RECIRC, traffic is recirculated back to flow table
> + * with the NXM_NX_CONN_STATE and NXM_NX_CONN_STATE_W matches set.  A
> + * standard "resubmit" action is not sufficient, since connection
> + * tracking occurs outside of the classifier.  The 'zone' argument
> + * specifies a context within which the tracking is done. */
> +struct nx_action_conntrack {
> +    ovs_be16 type;              /* OFPAT_VENDOR. */
> +    ovs_be16 len;               /* 16. */
> +    ovs_be32 vendor;            /* NX_VENDOR_ID. */
> +    ovs_be16 subtype;           /* NXAST_CONNTRACK. */
> +    ovs_be16 flags;             /* Either 0 or NX_CONNTRACK_F_RECIRC. */
> +    ovs_be16 zone;              /* Connection tracking context. */
> +    uint8_t  pad[2];
> +};
> +OFP_ASSERT(sizeof(struct nx_action_conntrack) == 16);
> +
> +static enum ofperr
> +decode_NXAST_RAW_CONNTRACK(const struct nx_action_conntrack *nac,
> +                           struct ofpbuf *out)
> +{
> +    struct ofpact_conntrack *conntrack;
> +
> +    conntrack = ofpact_put_CONNTRACK(out);
> +    conntrack->flags = ntohs(nac->flags);
> +    conntrack->zone = ntohs(nac->zone);
> +
> +    return 0;
> +}
> +
> +static void
> +encode_CONNTRACK(const struct ofpact_conntrack *conntrack,
> +              enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
> +{
> +    struct nx_action_conntrack *nac;
> +
> +    nac = put_NXAST_CONNTRACK(out);
> +    nac->flags = htons(conntrack->flags);
> +    nac->zone = htons(conntrack->zone);
> +}
> +
> +/* Parses 'arg' as the argument to a "conntrack" action, and appends such
> an
> + * action to 'ofpacts'.
> + *
> + * Returns NULL if successful, otherwise a malloc()'d string describing
> the
> + * error.  The caller is responsible for freeing the returned string. */
> +static char * WARN_UNUSED_RESULT
> +parse_CONNTRACK(char *arg, struct ofpbuf *ofpacts,
> +             enum ofputil_protocol *usable_protocols OVS_UNUSED)
> +{
> +    struct ofpact_conntrack *oc = ofpact_put_CONNTRACK(ofpacts);
> +    char *key, *value;
> +
> +    while (ofputil_parse_key_value(&arg, &key, &value)) {
> +        char *error = NULL;
> +
> +        if (!strcmp(key, "flags")) {
> +            error = str_to_u16(value, "flags", &oc->flags);
> +        } else if (!strcmp(key, "zone")) {
> +            error = str_to_u16(value, "zone", &oc->zone);
> +        } else {
> +            error = xasprintf("invalid key \"%s\" in \"conntrack\"
> argument",
> +                              key);
> +        }
> +        if (error) {
> +            return error;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static void
> +format_CONNTRACK(const struct ofpact_conntrack *a, struct ds *s)
> +{
> +    ds_put_format(s, "conntrack(flags=%"PRIu16",zone=%"PRIu16")",
> +                  a->flags, a->zone);
> +}
> +
>  /* Meter instruction. */
>
>  static void
> @@ -4304,6 +4387,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
>          return true;
>      case OFPACT_BUNDLE:
>      case OFPACT_CLEAR_ACTIONS:
> +    case OFPACT_CONNTRACK:
>      case OFPACT_CONTROLLER:
>      case OFPACT_DEC_MPLS_TTL:
>      case OFPACT_DEC_TTL:
> @@ -4376,6 +4460,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact
> *a)
>       * in the action set is undefined. */
>      case OFPACT_BUNDLE:
>      case OFPACT_CONTROLLER:
> +    case OFPACT_CONNTRACK:
>      case OFPACT_ENQUEUE:
>      case OFPACT_EXIT:
>      case OFPACT_FIN_TIMEOUT:
> @@ -4599,6 +4684,7 @@ ovs_instruction_type_from_ofpact_type(enum
> ofpact_type type)
>      case OFPACT_NOTE:
>      case OFPACT_EXIT:
>      case OFPACT_SAMPLE:
> +    case OFPACT_CONNTRACK:
>      default:
>          return OVSINST_OFPIT11_APPLY_ACTIONS;
>      }
> @@ -5160,6 +5246,9 @@ ofpact_check__(enum ofputil_protocol
> *usable_protocols, struct ofpact *a,
>      case OFPACT_SAMPLE:
>          return 0;
>
> +    case OFPACT_CONNTRACK:
> +        return 0;
> +
>      case OFPACT_CLEAR_ACTIONS:
>          return 0;
>
> @@ -5578,6 +5667,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact,
> ofp_port_t port)
>      case OFPACT_GOTO_TABLE:
>      case OFPACT_METER:
>      case OFPACT_GROUP:
> +    case OFPACT_CONNTRACK:
>      default:
>          return false;
>      }
> diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
> index 5436f24..a3a4b41 100644
> --- a/lib/ofp-actions.h
> +++ b/lib/ofp-actions.h
> @@ -105,6 +105,7 @@
>      OFPACT(NOTE,            ofpact_note,        data, "note")           \
>      OFPACT(EXIT,            ofpact_null,        ofpact, "exit")         \
>      OFPACT(SAMPLE,          ofpact_sample,      ofpact, "sample")       \
> +    OFPACT(CONNTRACK,       ofpact_conntrack,   ofpact, "conntrack")    \
>                                                                          \
>      /* Instructions. */                                                 \
>      OFPACT(METER,           ofpact_meter,       ofpact, "meter")        \
> @@ -472,6 +473,23 @@ BUILD_ASSERT_DECL(offsetof(struct ofpact_nest,
> actions) % OFPACT_ALIGNTO == 0);
>  BUILD_ASSERT_DECL(offsetof(struct ofpact_nest, actions)
>                    == sizeof(struct ofpact_nest));
>
> +/* Bits for 'flags' in struct nx_action_conntrack.
> + *
> + * If NX_CONNTRACK_F_RECIRC is set, then the packet will be recirculated
> + * through the datapath after running through the connection tracker. */
> +enum nx_conntrack_flags {
> +    NX_CONNTRACK_F_RECIRC = 1 << 0
> +};
> +
> +/* OFPACT_CONNTRACK.
> + *
> + * Used for NXAST_CONNTRACK. */
> +struct ofpact_conntrack {
> +    struct ofpact ofpact;
> +    uint16_t flags;
> +    uint16_t zone;
> +};
> +
>  static inline size_t
>  ofpact_nest_get_action_len(const struct ofpact_nest *on)
>  {
> diff --git a/lib/ofp-print.c b/lib/ofp-print.c
> index 43bfa17..12e0f6c 100644
> --- a/lib/ofp-print.c
> +++ b/lib/ofp-print.c
> @@ -141,6 +141,10 @@ ofp_print_packet_in(struct ds *string, const struct
> ofp_header *oh,
>          ds_put_format(string, " pkt_mark=0x%"PRIx32, pin.fmd.pkt_mark);
>      }
>
> +    if (pin.fmd.conn_state != 0) {
> +        ds_put_format(string, " conn_state=0x%"PRIx8, pin.fmd.conn_state);
> +    }
> +
>      ds_put_format(string, " (via %s)",
>                    ofputil_packet_in_reason_to_string(pin.reason,
> reasonbuf,
>                                                       sizeof reasonbuf));
> diff --git a/lib/ofp-util.c b/lib/ofp-util.c
> index c8d38e8..f352336 100644
> --- a/lib/ofp-util.c
> +++ b/lib/ofp-util.c
> @@ -185,7 +185,7 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
>  void
>  ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
>  {
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 27);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 28);
>
>      /* Initialize most of wc. */
>      flow_wildcards_init_catchall(wc);
> @@ -3276,6 +3276,7 @@ ofputil_decode_packet_in_finish(struct
> ofputil_packet_in *pin,
>      pin->fmd.metadata = match->flow.metadata;
>      memcpy(pin->fmd.regs, match->flow.regs, sizeof pin->fmd.regs);
>      pin->fmd.pkt_mark = match->flow.pkt_mark;
> +    pin->fmd.conn_state = match->flow.conn_state;
>  }
>
>  enum ofperr
> @@ -3412,6 +3413,10 @@ ofputil_packet_in_to_match(const struct
> ofputil_packet_in *pin,
>          match_set_pkt_mark(match, pin->fmd.pkt_mark);
>      }
>
> +    if (pin->fmd.conn_state != 0) {
> +        match_set_conn_state(match, pin->fmd.conn_state);
> +    }
> +
>      match_set_in_port(match, pin->fmd.in_port);
>  }
>
> diff --git a/lib/packets.h b/lib/packets.h
> index 0258745..c7bddf5 100644
> --- a/lib/packets.h
> +++ b/lib/packets.h
> @@ -62,6 +62,7 @@ struct pkt_metadata {
>      uint32_t skb_priority;      /* Packet priority for QoS. */
>      uint32_t pkt_mark;          /* Packet mark. */
>      union flow_in_port in_port; /* Input port. */
> +    uint8_t conn_state;         /* Connection state. */
>  };
>
>  #define PKT_METADATA_INITIALIZER(PORT) \
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index d366500..5e49701 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -2401,13 +2401,14 @@ compose_output_action__(struct xlate_ctx *ctx,
> ofp_port_t ofp_port,
>      struct flow *flow = &ctx->xin->flow;
>      ovs_be16 flow_vlan_tci;
>      uint32_t flow_pkt_mark;
> +    uint8_t flow_conn_state;
>      uint8_t flow_nw_tos;
>      odp_port_t out_port, odp_port;
>      uint8_t dscp;
>
>      /* If 'struct flow' gets additional metadata, we'll need to zero it
> out
>       * before traversing a patch port. */
> -    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 27);
> +    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 28);
>
>      if (!xport) {
>          xlate_report(ctx, "Nonexistent output port");
> @@ -2488,6 +2489,7 @@ compose_output_action__(struct xlate_ctx *ctx,
> ofp_port_t ofp_port,
>
>      flow_vlan_tci = flow->vlan_tci;
>      flow_pkt_mark = flow->pkt_mark;
> +    flow_conn_state = flow->conn_state;
>      flow_nw_tos = flow->nw_tos;
>
>      if (dscp_from_skb_priority(xport, flow->skb_priority, &dscp)) {
> @@ -2574,6 +2576,7 @@ compose_output_action__(struct xlate_ctx *ctx,
> ofp_port_t ofp_port,
>      /* Restore flow */
>      flow->vlan_tci = flow_vlan_tci;
>      flow->pkt_mark = flow_pkt_mark;
> +    flow->conn_state = flow_conn_state;
>      flow->nw_tos = flow_nw_tos;
>  }
>
> @@ -3480,6 +3483,7 @@ ofpact_needs_recirculation_after_mpls(const struct
> xlate_ctx *ctx,
>      case OFPACT_WRITE_ACTIONS:
>      case OFPACT_CLEAR_ACTIONS:
>      case OFPACT_SAMPLE:
> +    case OFPACT_CONNTRACK:
>          return false;
>
>      case OFPACT_SET_IPV4_SRC:
> @@ -3819,6 +3823,19 @@ do_xlate_actions(const struct ofpact *ofpacts,
> size_t ofpacts_len,
>          case OFPACT_SAMPLE:
>              xlate_sample_action(ctx, ofpact_get_SAMPLE(a));
>              break;
> +
> +        case OFPACT_CONNTRACK: {
> +            struct ofpact_conntrack *ofc = ofpact_get_CONNTRACK(a);
> +
> +            nl_msg_put_u16(ctx->xout->odp_actions,
> +                           OVS_ACTION_ATTR_CONNTRACK, ofc->zone);
> +            /* xxx Need to put the recirc here. */
> +            if (ofc->flags & NX_CONNTRACK_F_RECIRC) {
> +                nl_msg_put_u32(ctx->xout->odp_actions,
> OVS_ACTION_ATTR_RECIRC,
> +                               0);  /* xxx Choose real recird id */
> +            }
> +            break;
> +        }
>          }
>      }
>  }
> diff --git a/ofproto/ofproto-unixctl.man b/ofproto/ofproto-unixctl.man
> index 89013d9..83820ee 100644
> --- a/ofproto/ofproto-unixctl.man
> +++ b/ofproto/ofproto-unixctl.man
> @@ -103,6 +103,8 @@ only metadata. The metadata can be:
>  Packet QoS priority.
>  .IP \fIpkt_mark\fR
>  Mark of the packet.
> +.IP \fIconn_state\fR
> +Connection state of the packet.
>  .IP \fItun_id\fR
>  The tunnel ID on which the packet arrived.
>  .IP \fIin_port\fR
> diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
> index c50b1a8..c1b4ae4 100644
> --- a/tests/dpif-netdev.at
> +++ b/tests/dpif-netdev.at
> @@ -65,7 +65,7 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p1
> 'in_port(1),eth(src=50:54:00:00:00:
>  sleep 1
>
>  AT_CHECK([cat ovs-vswitchd.log | grep -A 1 'miss upcall' | tail -n 1],
> [0], [dnl
>
> -skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>
> +skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>  ])
>  AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_XOUT], [0],
> [dnl
>  
> recirc_id=0,skb_priority=0,ip,in_port=1,vlan_tci=0x0000/0x1fff,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_frag=no,
> actions: <del>
> @@ -78,10 +78,10 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p1
> 'in_port(1),eth(src=50:54:00:00:00:
>  sleep 1
>
>  AT_CHECK([cat ovs-vswitchd.log | grep -A 1 'miss upcall' | tail -n 1],
> [0], [dnl
>
> -skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>
> +skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>  ])
>  AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_XOUT], [0],
> [dnl
> -pkt_mark=0,recirc_id=0,skb_priority=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0,
> actions: <del>
> +pkt_mark=0,recirc_id=0,skb_priority=0,conn_state=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0,
> actions: <del>
>  
> recirc_id=0,skb_priority=0,ip,in_port=1,vlan_tci=0x0000/0x1fff,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_frag=no,
> actions: <del>
>  ])
>
> @@ -100,10 +100,10 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p1
> 'in_port(1),eth(src=50:54:00:00:00:
>  sleep 1
>
>  AT_CHECK([cat ovs-vswitchd.log | grep -A 1 'miss upcall' | tail -n 1],
> [0], [dnl
>
> -skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>
> +skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>  ])
>  AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_DUMP | STRIP_XOUT], [0], [dnl
>
> -skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=
> 10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions: <del>
>
> +skb_priority(0),skb_mark(0/0),conn_state(0/0),recirc_id(0),dp_hash(0/0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=
> 10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions: <del>
>  ])
>
>  # Now, the same again without megaflows.
> @@ -113,11 +113,11 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p1
> 'in_port(1),eth(src=50:54:00:00:00:
>  sleep 1
>
>  AT_CHECK([cat ovs-vswitchd.log | grep -A 1 'miss upcall' | tail -n 1],
> [0], [dnl
>
> -skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>
> +skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)
>  ])
>  AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_DUMP | STRIP_XOUT], [0], [dnl
> -skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0),
> packets:0, bytes:0, used:never, actions: <del>
>
> -skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=
> 10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions: <del>
> +skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0),
> packets:0, bytes:0, used:never, actions: <del>
>
> +skb_priority(0),skb_mark(0/0),conn_state(0/0),recirc_id(0),dp_hash(0/0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=
> 10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions: <del>
>  ])
>
>  OVS_VSWITCHD_STOP
> diff --git a/tests/odp.at b/tests/odp.at
> index 5c00764..f1ca721 100644
> --- a/tests/odp.at
> +++ b/tests/odp.at
> @@ -33,52 +33,52 @@
> in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8848),mpl
>  ])
>
>  (echo '# Valid forms without tun_id or VLAN header.'
> - set 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/'
> odp-base.txt
> + set
> 's/^/skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/'
> odp-base.txt
>
>   set '
> -s/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/
> +s/^/skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/
>  ' odp-base.txt
>
>
>   echo
>   echo '# Valid forms with tunnel header.'
> - sed
> 's/^/skb_priority(0),tunnel(tun_id=0x7f10354,src=10.10.10.10,dst=20.20.20.20,tos=0x0,ttl=64,flags(csum,key)),skb_mark(0x1234),recirc_id(0),dp_hash(0),/'
> odp-base.txt
> + sed
> 's/^/skb_priority(0),tunnel(tun_id=0x7f10354,src=10.10.10.10,dst=20.20.20.20,tos=0x0,ttl=64,flags(csum,key)),skb_mark(0x1234),conn_state(0),recirc_id(0),dp_hash(0),/'
> odp-base.txt
>
>   echo
>   echo '# Valid forms with VLAN header.'
> - sed 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/
> + sed
> 's/^/skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/
>  s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
>  s/$/)/' odp-base.txt
>
>   echo
>   echo '# Valid forms with MPLS header.'
> - sed 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/
> + sed
> 's/^/skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/
>  s/\(eth([[^)]]*),?\)/\1,eth_type(0x8847),mpls(label=100,tc=7,ttl=64,bos=1)/'
> odp-base.txt
>
>   echo
>   echo '# Valid forms with MPLS multicast header.'
> - sed 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/
> + sed
> 's/^/skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/
>  s/\(eth([[^)]]*),?\)/\1,eth_type(0x8848),mpls(label=100,tc=7,ttl=64,bos=1)/'
> odp-base.txt
>
>   echo
>   echo '# Valid forms with tunnel and VLAN headers.'
> - sed
> 's/^/skb_priority(0),tunnel(tun_id=0xfedcba9876543210,src=10.0.0.1,dst=10.0.0.2,tos=0x8,ttl=128,flags(key)),skb_mark(0),recirc_id(0),dp_hash(0),/
> + sed
> 's/^/skb_priority(0),tunnel(tun_id=0xfedcba9876543210,src=10.0.0.1,dst=10.0.0.2,tos=0x8,ttl=128,flags(key)),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/
>  s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
>  s/$/)/' odp-base.txt
>
>   echo
>   echo '# Valid forms with QOS priority, tunnel, and VLAN headers.'
> - sed
> 's/^/skb_priority(0x1234),tunnel(tun_id=0xfedcba9876543210,src=10.10.10.10,dst=20.20.20.20,tos=0x8,ttl=64,flags(key)),skb_mark(0),recirc_id(0),dp_hash(0),/
> + sed
> 's/^/skb_priority(0x1234),tunnel(tun_id=0xfedcba9876543210,src=10.10.10.10,dst=20.20.20.20,tos=0x8,ttl=64,flags(key)),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/
>  s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
>  s/$/)/' odp-base.txt
>
>   echo
>   echo '# Valid forms with IP first fragment.'
> -sed 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/'
> odp-base.txt | sed -n 's/,frag=no),/,frag=first),/p'
> +sed
> 's/^/skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/'
> odp-base.txt | sed -n 's/,frag=no),/,frag=first),/p'
>
>   echo
>   echo '# Valid forms with IP later fragment.'
> -sed 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/'
> odp-base.txt | sed -n 's/,frag=no),.*/,frag=later)/p'
> +sed
> 's/^/skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),/'
> odp-base.txt | sed -n 's/,frag=no),.*/,frag=later)/p'
>  ) > odp-in.txt
>  AT_CAPTURE_FILE([odp-in.txt])
>
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 7e7d907..66f0416 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -4629,12 +4629,12 @@
> skb_priority(0),recirc_id(0),in_port(3),eth_type(0x0800),ipv4(src=
> 10.0.0.2/0.0.0
>  ])
>
>  AT_CHECK([ovs-appctl dpif/dump-flows -m br0 | sort | STRIP_USED], [0],
> [dnl
>
> -skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p1),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions:drop
>
> -skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p2),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=0/0,code=0/0),
> packets:0, bytes:0, used:never, actions:drop
>
> +skb_priority(0),skb_mark(0/0),conn_state(0/0),recirc_id(0),dp_hash(0/0),in_port(p1),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions:drop
>
> +skb_priority(0),skb_mark(0/0),conn_state(0/0),recirc_id(0),dp_hash(0/0),in_port(p2),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=0/0,code=0/0),
> packets:0, bytes:0, used:never, actions:drop
>  ])
>
>  AT_CHECK([ovs-appctl dpif/dump-flows -m br1 | sort | STRIP_USED], [0],
> [dnl
>
> -skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p3),eth(src=50:54:00:00:00:09/00:00:00:00:00:00,dst=50:54:00:00:00:0a/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions:drop
>
> +skb_priority(0),skb_mark(0/0),conn_state(0/0),recirc_id(0),dp_hash(0/0),in_port(p3),eth(src=50:54:00:00:00:09/00:00:00:00:00:00,dst=50:54:00:00:00:0a/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:0, bytes:0, used:never, actions:drop
>  ])
>
>  OVS_VSWITCHD_STOP
> @@ -4768,10 +4768,10 @@
> recirc_id=0,skb_priority=0,ip,in_port=101,nw_frag=no, actions:100,2,3
>  ])
>
>  AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(100).*packets:9' |
> FILTER_FLOW_DUMP], [0], [dnl
>
> -skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(100),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:9, bytes:540, used:0.0s, actions:101,3,2
>
> +skb_priority(0),skb_mark(0/0),conn_state(0/0),recirc_id(0),dp_hash(0/0),in_port(100),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:9, bytes:540, used:0.0s, actions:101,3,2
>  ])
>  AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(101).*packets:4' |
> FILTER_FLOW_DUMP], [0], [dnl
>
> -skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(101),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:4, bytes:240, used:0.0s, actions:100,2,3
>
> +skb_priority(0),skb_mark(0/0),conn_state(0/0),recirc_id(0),dp_hash(0/0),in_port(101),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=
> 192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no/0xff),icmp(type=8/0,code=0/0),
> packets:4, bytes:240, used:0.0s, actions:100,2,3
>  ])
>
>  AT_CHECK([ovs-ofctl dump-ports br0 pbr0], [0], [dnl
> @@ -5318,12 +5318,12 @@ for i in 1 2 3 4; do
>  done
>  sleep 1
>  AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0],
> [dnl
> -pkt_mark=0,recirc_id=0,skb_priority=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0,
> actions:2
> -pkt_mark=0,recirc_id=0,skb_priority=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0,
> actions:drop
> +pkt_mark=0,recirc_id=0,skb_priority=0,conn_state=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0,
> actions:2
> +pkt_mark=0,recirc_id=0,skb_priority=0,conn_state=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0,
> actions:drop
>  ])
>  AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_DUMP | grep 'packets:3'],
> [0], [dnl
> -skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0),
> packets:3, bytes:180, used:0.0s, actions:2
> -skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0),
> packets:3, bytes:180, used:0.0s, actions:drop
> +skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0),
> packets:3, bytes:180, used:0.0s, actions:2
> +skb_priority(0),skb_mark(0),conn_state(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0),
> packets:3, bytes:180, used:0.0s, actions:drop
>  ])
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
> diff --git a/tests/ofproto.at b/tests/ofproto.at
> index d2e7196..e51155d 100644
> --- a/tests/ofproto.at
> +++ b/tests/ofproto.at
> @@ -1167,7 +1167,7 @@ OVS_VSWITCHD_START
>        instructions:
> meter,apply_actions,clear_actions,write_actions,write_metadata,goto_table
>        Write-Actions and Apply-Actions features:
>          actions: output group set_field strip_vlan push_vlan mod_nw_ttl
> dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
> -        supported on Set-Field: tun_id tun_src tun_dst metadata in_port
> in_port_oxm pkt_mark reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 xreg0 xreg1
> xreg2 xreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc
> ip_src ip_dst ipv6_src ipv6_dst nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa
> arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst
> +        supported on Set-Field: tun_id tun_src tun_dst metadata in_port
> in_port_oxm pkt_mark conn_state reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7
> xreg0 xreg1 xreg2 xreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp
> mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst nw_tos ip_dscp nw_ecn
> nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src
> udp_dst sctp_src sctp_dst
>      matching:
>        dp_hash: arbitrary mask
>        recirc_id: exact match or wildcard
> @@ -1178,6 +1178,7 @@ OVS_VSWITCHD_START
>        in_port: exact match or wildcard
>        in_port_oxm: exact match or wildcard
>        pkt_mark: arbitrary mask
> +      conn_state: arbitrary mask
>        reg0: arbitrary mask
>        reg1: arbitrary mask
>        reg2: arbitrary mask
> @@ -1247,7 +1248,7 @@ AT_CHECK(
>  # Check that the configuration was updated.
>  mv expout orig-expout
>  sed 's/classifier/main/
> -73s/1000000/1024/' < orig-expout > expout
> +74s/1000000/1024/' < orig-expout > expout
>  AT_CHECK([ovs-ofctl -O OpenFlow13 dump-table-features br0 | sed '/^$/d
>  /^OFPST_TABLE_FEATURES/d'], [0], [expout])
>  OVS_VSWITCHD_STOP
> diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
> index be82ed9..03b6988 100644
> --- a/utilities/ovs-ofctl.8.in
> +++ b/utilities/ovs-ofctl.8.in
> @@ -1108,6 +1108,26 @@ system components in order to facilitate
> interaction between subsystems.
>  On Linux this corresponds to the skb mark but the exact implementation is
>  platform-dependent.
>  .
> +.IP \fBconn_state=\fIvalue\fR[\fB/\fImask\fR]
> +Matches packet connection state \fIvalue\fR either exactly or with
> optional
> +\fImask\fR.  The following flags are defined:
> +.RS
> +.IP \fB0x80\fR
> +Connection tracking has occurred.
> +.IP \fB0x40\fR
> +The flow did not initiate the connection.
> +.RE
> +.IP
> +The following values describe the state of the connection:
> +.RS
> +.IP \fB0x01\fR
> +This is the beginning of a new connection.
> +.IP \fB0x02\fR
> +This is part of an already existing connection.
> +.IP \fB0x04\fR
> +This is a new connection that is "expected".
> +.RE
> +.
>  .PP
>  Defining IPv6 flows (those with \fBdl_type\fR equal to 0x86dd) requires
>  support for NXM.  The following shorthand notations are available for
> --
> 1.7.5.4
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
>
_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to