[ovs-dev] [RFCv4 04/10] userspace: Add support for connection tracking.

Joe Stringer joestringer at nicira.com
Thu Apr 30 19:38:38 UTC 2015


From: Justin Pettit <jpettit at nicira.com>

This patch adds support for interacting with a connection tracking module from
the OpenFlow pipeline. In particular, there is a new action to send packets to
the connection tracker "ct(), and an NXM to match connection tracking state
"conn_state". Conn_state is initially zero when a packet first enters the
OpenFlow pipeline, but may be modified by executing the conntrack action with
the 'recirc' flag.

The conntrack action has two optional flags:
'Commit' - Cause the state of the connection for this packet to persist
           across multiple executions of the ct() action.
'Recirc' - Makes the conntrack action similar to an output action. It
           recirculates the packet back to the start of the OpenFlow pipeline
           with the existing OpenFlow metadata and makes the conntrack fields
           (eg conn_state) available for matching.

The conn_state may have a combination of the following state flags:
'trk' - Packet has passed through the connection tracker.
'new' - This is the first time conntrack has seen the connection, or the
        conntrack has not been told to persist state for this connection.
'est' - The connection is considered 'established'. This usually means that
        packets have been seen in both directions.
'rel' - This connection is related to an existing connection which is
        followed by the connection tracker.
'rpl' - This packet is in the 'reply' direction when compared with the packet
        that initiated this connection.
'inv' - This packet is considered to be an 'invalid' part of a connection. For
        example, an unsolicited TCP ack that doesn't belong to an existing
        connection.

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 del-flows br0
ovs-ofctl add-flow br0 "in_port=1,conn_state=-trk,tcp,action=ct(commit),2"
ovs-ofctl add-flow br0 "in_port=2,conn_state=-trk,tcp,action=ct(recirc)"
ovs-ofctl add-flow br0 "in_port=2,conn_state=+trk+est-new,tcp,action=1"
ovs-ofctl add-flow br0 "in_port=2,conn_state=+trk-est+new,tcp,action=drop"
ovs-ofctl add-flow br0 priority=10,action=normal

Signed-off-by: Joe Stringer <joestringer at nicira.com>
CC: Justin Pettit <jpettit at nicira.com>
---
v4:
- Reject flows like: "conn_state=+inv,... actions:ct(commit),..."
- Conntrack action requires IP/IPv6.
- Make all odp conntrack actions optional
- Fix ovs-dpctl string parsing.
- Add various tests to the kmod testsuite
- Rebase
---
 NEWS                                              |    3 +
 build-aux/extract-ofp-fields                      |    1 +
 datapath/flow_netlink.c                           |    2 +-
 datapath/linux/compat/include/linux/openvswitch.h |   33 ++++
 lib/dpif-netdev.c                                 |   24 ++-
 lib/dpif.c                                        |    1 +
 lib/flow.c                                        |   33 +++-
 lib/flow.h                                        |    5 +-
 lib/match.c                                       |   30 +++
 lib/match.h                                       |    3 +
 lib/meta-flow.c                                   |  114 +++++++++++
 lib/meta-flow.h                                   |   26 +++
 lib/nx-match.c                                    |    6 +
 lib/odp-execute.c                                 |    4 +
 lib/odp-util.c                                    |  125 ++++++++++++
 lib/odp-util.h                                    |    3 +-
 lib/ofp-actions.c                                 |  102 ++++++++++
 lib/ofp-actions.h                                 |   22 +++
 lib/ofp-print.c                                   |    4 +
 lib/ofp-util.c                                    |    5 +
 lib/packets.c                                     |   21 ++
 lib/packets.h                                     |   14 ++
 ofproto/ofproto-dpif-xlate.c                      |   53 ++++-
 ofproto/ofproto-dpif.c                            |   56 ++++++
 ofproto/ofproto-dpif.h                            |    1 +
 ofproto/ofproto-unixctl.man                       |    2 +
 python/ovs/daemon.py                              |    6 +-
 tests/automake.mk                                 |    1 +
 tests/dpif-netdev.at                              |    2 +-
 tests/kmod-macros.at                              |   12 ++
 tests/kmod-traffic.at                             |  214 ++++++++++++++++++++-
 tests/ofproto-dpif.at                             |    4 +-
 tests/ofproto.at                                  |    3 +-
 tests/test-conntrack.py                           |   51 +++++
 utilities/ovs-ofctl.8.in                          |   56 ++++++
 35 files changed, 1019 insertions(+), 23 deletions(-)
 create mode 100755 tests/test-conntrack.py

diff --git a/NEWS b/NEWS
index 882a381..18f7ae4 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,9 @@ Post-v2.3.0
    - Add bash command-line completion support for ovs-appctl/ovs-dpctl/
      ovs-ofctl/ovsdb-tool commands.  Please check
      utilities/ovs-command-compgen.INSTALL.md for how to use.
+   - Add support for connection tracking through the new "conntrack" action
+     and "conn_state" match field.  Only available on Linux kernels with
+     the connection tracking module loaded.
    - The "learn" action supports a new flag "delete_learned" that causes
      the learned flows to be deleted when the flow with the "learn" action
      is deleted.
diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
index b15b01d..78121ec 100755
--- a/build-aux/extract-ofp-fields
+++ b/build-aux/extract-ofp-fields
@@ -23,6 +23,7 @@ TYPES = {"u8": 1,
 
 FORMATTING = {"decimal":            ("MFS_DECIMAL",      1, 8),
               "hexadecimal":        ("MFS_HEXADECIMAL",  1, 8),
+              "conn state":         ("MFS_CONN_STATE",   1, 1),
               "Ethernet":           ("MFS_ETHERNET",     6, 6),
               "IPv4":               ("MFS_IPV4",         4, 4),
               "IPv6":               ("MFS_IPV6",        16,16),
diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
index 890a889..baf6539 100644
--- a/datapath/flow_netlink.c
+++ b/datapath/flow_netlink.c
@@ -281,7 +281,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 */
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index f53bc81..6c857d5 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -343,6 +343,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 of OVS_CS_F_* */
 
 #ifdef __KERNEL__
 	/* Only used within kernel data path. */
@@ -456,6 +457,15 @@ struct ovs_key_nd {
 	__u8	nd_tll[ETH_ALEN];
 };
 
+/* OVS_KEY_ATTR_CONN_STATE flags */
+#define OVS_CS_F_NEW               0x01 /* Beginning of a new connection. */
+#define OVS_CS_F_ESTABLISHED       0x02 /* Part of an existing connection. */
+#define OVS_CS_F_RELATED           0x04 /* Related to an established
+					 * connection. */
+#define OVS_CS_F_INVALID           0x20 /* Could not track connection. */
+#define OVS_CS_F_REPLY_DIR         0x40 /* Flow is in the reply direction. */
+#define OVS_CS_F_TRACKED           0x80 /* Conntrack has occurred. */
+
 /**
  * enum ovs_flow_attr - attributes for %OVS_FLOW_* commands.
  * @OVS_FLOW_ATTR_KEY: Nested %OVS_KEY_ATTR_* attributes specifying the flow
@@ -640,6 +650,26 @@ struct ovs_action_push_tnl {
 #endif
 
 /**
+ * enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
+ * @OVS_CT_ATTR_FLAGS: u32 connection tracking flags.
+ */
+enum ovs_ct_attr {
+	OVS_CT_ATTR_UNSPEC,
+	OVS_CT_ATTR_FLAGS,      /* u8 of OVS_CT_F_*. */
+	__OVS_CT_ATTR_MAX
+};
+
+#define OVS_CT_ATTR_MAX (__OVS_CT_ATTR_MAX - 1)
+
+/*
+ * OVS_CT_ATTR_FLAGS flags - bitmask of %OVS_CT_F_*
+ * @OVS_CT_F_COMMIT: Commits the flow to the conntrack hashtable in the
+ * specified zone. Future packets for the current connection will be
+ * considered as 'established' or 'related'.
+ */
+#define OVS_CT_F_COMMIT		0x01
+
+/**
  * enum ovs_action_attr - Action types.
  *
  * @OVS_ACTION_ATTR_OUTPUT: Output packet to port.
@@ -670,6 +700,8 @@ struct ovs_action_push_tnl {
  * 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_CT: Track the connection. Populate the conntrack-related
+ * entries in the flow key.
  *
  * 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
@@ -697,6 +729,7 @@ enum ovs_action_attr {
 				       * data immediately followed by a mask.
 				       * The data must be zero for the unmasked
 				       * bits. */
+	OVS_ACTION_ATTR_CT,           /* One nested OVS_CT_ATTR_* . */
 
 #ifndef __KERNEL__
 	OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index f1d65f5..627679b 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1926,6 +1926,11 @@ dpif_netdev_flow_from_nlattrs(const struct nlattr *key, uint32_t key_len,
         return EINVAL;
     }
 
+    /* Userspace datapath doesn't support conntrack. */
+    if (flow->conn_state) {
+        return EINVAL;
+    }
+
     return 0;
 }
 
@@ -3506,12 +3511,27 @@ dp_execute_cb(void *aux_, struct dp_packet **packets, int cnt,
         VLOG_WARN("Packet dropped. Max recirculation depth exceeded.");
         break;
 
+    case OVS_ACTION_ATTR_CT:
+        /* If a flow with this action is slow-pathed, datapath assistance is
+         * required to implement it. However, we don't support this action
+         * in the userspace datapath. */
+        VLOG_WARN("Cannot execute conntrack action in userspace.");
+        break;
+
+    case OVS_ACTION_ATTR_SET:
+    case OVS_ACTION_ATTR_SET_MASKED: {
+        const struct nlattr *set = nl_attr_get(a);
+        enum ovs_key_attr set_type = nl_attr_type(set);
+
+        VLOG_WARN("Cannot execute set_field (type=%d) action in userspace.",
+                  set_type);
+        break;
+    }
+
     case OVS_ACTION_ATTR_PUSH_VLAN:
     case OVS_ACTION_ATTR_POP_VLAN:
     case OVS_ACTION_ATTR_PUSH_MPLS:
     case OVS_ACTION_ATTR_POP_MPLS:
-    case OVS_ACTION_ATTR_SET:
-    case OVS_ACTION_ATTR_SET_MASKED:
     case OVS_ACTION_ATTR_SAMPLE:
     case OVS_ACTION_ATTR_HASH:
     case OVS_ACTION_ATTR_UNSPEC:
diff --git a/lib/dpif.c b/lib/dpif.c
index b8f30a5..f6c2c3e 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1093,6 +1093,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet **packets, int cnt,
     ovs_assert(cnt == 1);
 
     switch ((enum ovs_action_attr)type) {
+    case OVS_ACTION_ATTR_CT:
     case OVS_ACTION_ATTR_OUTPUT:
     case OVS_ACTION_ATTR_TUNNEL_PUSH:
     case OVS_ACTION_ATTR_TUNNEL_POP:
diff --git a/lib/flow.c b/lib/flow.c
index e54280a..840d051 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -178,6 +178,24 @@ BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
     }                                                                   \
 }
 
+/* Are there any other asserts we need for pushing one octet? */
+#define miniflow_push_uint8_(MF, OFS, VALUE)                            \
+{                                                                       \
+    MINIFLOW_ASSERT(MF.data < MF.end &&                                 \
+                    ((OFS) % 8 != 0                                     \
+                     || !(MF.map & (UINT64_MAX << (OFS) / 8))))         \
+                                                                        \
+    if ((OFS) % 8 == 0) {                                               \
+        *(uint8_t *)MF.data = VALUE;                                    \
+        MF.map |= UINT64_C(1) << (OFS) / 8;                             \
+    } else if ((OFS) % 8 < 7) {                                         \
+        *((uint8_t *)MF.data + (OFS % 8)) = VALUE;                      \
+    } else {                                                            \
+        *((uint8_t *)MF.data + 7) = VALUE;                              \
+        MF.data++;                                                      \
+    }                                                                   \
+}
+
 #define miniflow_pad_to_64_(MF, OFS)                                    \
 {                                                                   \
     MINIFLOW_ASSERT((OFS) % 8 != 0);                                    \
@@ -247,6 +265,9 @@ BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
 #define miniflow_push_be16(MF, FIELD, VALUE)                        \
     miniflow_push_be16_(MF, offsetof(struct flow, FIELD), VALUE)
 
+#define miniflow_push_uint8(MF, FIELD, VALUE)                      \
+    miniflow_push_uint8_(MF, offsetof(struct flow, FIELD), VALUE)
+
 #define miniflow_pad_to_64(MF, FIELD)                       \
     miniflow_pad_to_64_(MF, offsetof(struct flow, FIELD))
 
@@ -447,6 +468,11 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
         miniflow_pad_to_64(mf, conj_id);
     }
 
+    if (md->conn_state) {
+        miniflow_push_uint8(mf, conn_state, md->conn_state);
+        miniflow_pad_to_64(mf, pad1);
+    }
+
     /* Initialize packet's layer pointer and offsets. */
     l2 = data;
     dp_packet_set_frame(packet, data);
@@ -774,6 +800,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;
 }
 
@@ -879,6 +906,9 @@ flow_format(struct ds *ds, const struct flow *flow)
     if (!flow->dp_hash) {
         WC_UNMASK_FIELD(wc, dp_hash);
     }
+    if (!flow->conn_state) {
+        WC_UNMASK_FIELD(wc, conn_state);
+    }
     for (int i = 0; i < FLOW_N_REGS; i++) {
         if (!flow->regs[i]) {
             WC_UNMASK_FIELD(wc, regs[i]);
@@ -941,6 +971,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc,
 
     WC_MASK_FIELD(wc, skb_priority);
     WC_MASK_FIELD(wc, pkt_mark);
+    WC_MASK_FIELD(wc, conn_state);
     WC_MASK_FIELD(wc, recirc_id);
     WC_MASK_FIELD(wc, dp_hash);
     WC_MASK_FIELD(wc, in_port);
@@ -1024,7 +1055,7 @@ flow_wc_map(const struct flow *flow)
     /* Metadata fields that can appear on packet input. */
     map |= MINIFLOW_MAP(skb_priority) | MINIFLOW_MAP(pkt_mark)
         | MINIFLOW_MAP(recirc_id) | MINIFLOW_MAP(dp_hash)
-        | MINIFLOW_MAP(in_port)
+        | MINIFLOW_MAP(conn_state) | MINIFLOW_MAP(in_port)
         | MINIFLOW_MAP(dl_dst) | MINIFLOW_MAP(dl_src)
         | MINIFLOW_MAP(dl_type) | MINIFLOW_MAP(vlan_tci);
 
diff --git a/lib/flow.h b/lib/flow.h
index dcb5bb0..783a9ca 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -106,8 +106,9 @@ struct flow {
     union flow_in_port in_port; /* Input port.*/
     uint32_t recirc_id;         /* Must be exact match. */
     uint32_t conj_id;           /* Conjunction ID. */
+    uint8_t conn_state ;        /* Connection state. */
+    uint8_t pad1[5];            /* Pad to 48 bits. */
     ofp_port_t actset_output;   /* Output port in action set. */
-    uint8_t pad1[6];            /* Pad to 64 bits. */
 
     /* L2, Order the same as in the Ethernet header! (64-bit aligned) */
     uint8_t dl_dst[ETH_ADDR_LEN]; /* Ethernet destination address. */
@@ -191,6 +192,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. */
 };
 
@@ -753,6 +755,7 @@ pkt_metadata_from_flow(struct pkt_metadata *md, 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;
 }
 
 static inline bool is_ip_any(const struct flow *flow)
diff --git a/lib/match.c b/lib/match.c
index 7d0b409..d34cb64 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -278,6 +278,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;
@@ -944,6 +958,22 @@ match_format(const struct match *match, struct ds *s, int priority)
         ds_put_char(s, ',');
     }
 
+    if (wc->masks.conn_state) {
+        if (wc->masks.conn_state == UINT8_MAX) {
+            ds_put_cstr(s, "conn_state=");
+            if (f->conn_state) {
+                format_flags(s, packet_conn_state_to_string, f->conn_state,
+                             '|');
+            } else {
+                ds_put_cstr(s, "0"); /* No state. */
+            }
+        } else {
+            format_flags_masked(s, "conn_state", packet_conn_state_to_string,
+                                f->conn_state, wc->masks.conn_state);
+        }
+        ds_put_char(s, ',');
+    }
+
     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 6633304..37723d0 100644
--- a/lib/match.h
+++ b/lib/match.h
@@ -78,6 +78,9 @@ void match_set_tun_gbp_flags(struct match *match, uint8_t flags);
 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[ETH_ADDR_LEN]);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 124b525..c64e605 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -132,6 +132,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:
@@ -417,6 +419,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:
@@ -558,6 +561,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;
@@ -779,6 +786,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;
@@ -1011,6 +1022,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;
@@ -1276,6 +1291,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;
@@ -1525,6 +1545,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;
@@ -2022,6 +2046,81 @@ mf_from_tcp_flags_string(const char *s, ovs_be16 *flagsp, ovs_be16 *maskp)
     return NULL;
 }
 
+/* xxx Possible to do a parse_flags()-like function from lib/odp-utils.c
+ * xxx and share it was mf_from_tcp_flags_string. */
+static char *
+mf_from_conn_state_string(const char *s, uint8_t *flagsp, uint8_t *maskp)
+{
+    uint8_t flags = 0;
+    uint8_t mask = 0;
+    uint8_t bit;
+    int n;
+
+    if (ovs_scan(s, "%"SCNi8"/%"SCNi8"%n", &flags, &mask, &n) && !s[n]) {
+        *flagsp = flags;
+        *maskp = mask;
+        return NULL;
+    }
+    if (ovs_scan(s, "%"SCNi8"%n", &flags, &n) && !s[n]) {
+        *flagsp = flags;
+        *maskp = UINT8_MAX;
+        return NULL;
+    }
+
+    while (*s != '\0') {
+        bool set;
+        int name_len;
+
+        switch (*s) {
+        case '+':
+            set = true;
+            break;
+        case '-':
+            set = false;
+            break;
+        default:
+            return xasprintf("%s: Connection state flag must be preceded "
+                             "by '+' (for SET) or '-' (NOT SET)", s);
+        }
+        s++;
+
+        name_len = strcspn(s,"+-");
+
+        for (bit = 1; bit; bit <<= 1) {
+            const char *fname = packet_conn_state_to_string(bit);
+            size_t len;
+
+            if (!fname) {
+                continue;
+            }
+
+            len = strlen(fname);
+            if (len != name_len) {
+                continue;
+            }
+            if (!strncmp(s, fname, len)) {
+                if (mask & bit) {
+                    return xasprintf("%s: Each connection state flag can be "
+                                     "specified only once", s);
+                }
+                if (set) {
+                    flags |= bit;
+                }
+                mask |= bit;
+                break;
+            }
+        }
+
+        if (!bit) {
+            return xasprintf("%s: unknown connection state flag(s)", s);
+        }
+        s += name_len;
+    }
+
+    *flagsp = flags;
+    *maskp = mask;
+    return NULL;
+}
 
 /* Parses 's', a string value for field 'mf', into 'value' and 'mask'.  Returns
  * NULL if successful, otherwise a malloc()'d string describing the error. */
@@ -2044,6 +2143,11 @@ mf_parse(const struct mf_field *mf, const char *s,
                                        (uint8_t *) value, (uint8_t *) mask);
         break;
 
+    case MFS_CONN_STATE:
+        ovs_assert(mf->n_bytes == sizeof(uint8_t));
+        error = mf_from_conn_state_string(s, &value->u8, &mask->u8);
+        break;
+
     case MFS_ETHERNET:
         error = mf_from_ethernet_string(mf, s, value->mac, mask->mac);
         break;
@@ -2171,6 +2275,12 @@ mf_format_tcp_flags_string(ovs_be16 value, ovs_be16 mask, struct ds *s)
                         TCP_FLAGS(mask));
 }
 
+static void
+mf_format_conn_state_string(uint8_t value, uint8_t mask, struct ds *s)
+{
+    format_flags_masked(s, NULL, packet_conn_state_to_string, value, mask);
+}
+
 /* Appends to 's' a string representation of field 'mf' whose value is in
  * 'value' and 'mask'.  'mask' may be NULL to indicate an exact match. */
 void
@@ -2207,6 +2317,10 @@ mf_format(const struct mf_field *mf,
         mf_format_integer_string(mf, (uint8_t *) value, (uint8_t *) mask, s);
         break;
 
+    case MFS_CONN_STATE:
+        mf_format_conn_state_string(value->u8, mask ? mask->u8 : UINT8_MAX, s);
+        break;
+
     case MFS_ETHERNET:
         eth_format_masked(value->mac, mask->mac, s);
         break;
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 265f066..b4fc4d8 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -542,6 +542,31 @@ enum OVS_PACKED_ENUM mf_field_id {
      */
     MFF_PKT_MARK,
 
+    /* "conn_state".
+     *
+     * Connection tracking state.  The field is populated by the
+     * NXAST_CT 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 related to an
+     *                     existing connection.
+     *
+     * Type: u8.
+     * Maskable: bitwise.
+     * Formatting: conn state.
+     * Prerequisites: none.
+     * Access: read-only.
+     * NXM: NXM_NX_CONN_STATE(37) since v2.4.
+     * OXM: none.
+     */
+    MFF_CONN_STATE,
+
 #if FLOW_N_REGS == 8
     /* "reg<N>".
      *
@@ -1482,6 +1507,7 @@ enum OVS_PACKED_ENUM mf_string {
     MFS_HEXADECIMAL,
 
     /* Other formats. */
+    MFS_CONN_STATE,             /* Conn* state */
     MFS_ETHERNET,
     MFS_IPV4,
     MFS_IPV6,
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 21f291c..162cd70 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -997,6 +997,12 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match,
     nxm_put_32m(b, MFF_PKT_MARK, oxm, htonl(flow->pkt_mark),
                 htonl(match->wc.masks.pkt_mark));
 
+    /* Connection tracking. */
+    if (match->wc.masks.conn_state) {
+        nxm_put_8m(b, MFF_CONN_STATE, oxm, flow->conn_state,
+                   match->wc.masks.conn_state);
+    }
+
     /* OpenFlow 1.1+ Metadata. */
     nxm_put_64m(b, MFF_METADATA, oxm,
                 flow->metadata, match->wc.masks.metadata);
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index b785104..06aeb9b 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -326,6 +326,7 @@ odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
     case OVS_KEY_ATTR_ICMP:
     case OVS_KEY_ATTR_ICMPV6:
     case OVS_KEY_ATTR_TCP_FLAGS:
+    case OVS_KEY_ATTR_CONN_STATE:
     case __OVS_KEY_ATTR_MAX:
     default:
         OVS_NOT_REACHED();
@@ -414,6 +415,7 @@ odp_execute_masked_set_action(struct dp_packet *packet,
 
     case OVS_KEY_ATTR_TUNNEL:    /* Masked data not supported for tunnel. */
     case OVS_KEY_ATTR_UNSPEC:
+    case OVS_KEY_ATTR_CONN_STATE:
     case OVS_KEY_ATTR_ENCAP:
     case OVS_KEY_ATTR_ETHERTYPE:
     case OVS_KEY_ATTR_IN_PORT:
@@ -476,6 +478,7 @@ requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_TUNNEL_POP:
     case OVS_ACTION_ATTR_USERSPACE:
     case OVS_ACTION_ATTR_RECIRC:
+    case OVS_ACTION_ATTR_CT:
         return true;
 
     case OVS_ACTION_ATTR_SET:
@@ -611,6 +614,7 @@ odp_execute_actions(void *dp, struct dp_packet **packets, int cnt, bool steal,
         case OVS_ACTION_ATTR_TUNNEL_POP:
         case OVS_ACTION_ATTR_USERSPACE:
         case OVS_ACTION_ATTR_RECIRC:
+        case OVS_ACTION_ATTR_CT:
         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 147b1e7..d4fca67 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -90,6 +90,7 @@ odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_SET: return -2;
     case OVS_ACTION_ATTR_SET_MASKED: return -2;
     case OVS_ACTION_ATTR_SAMPLE: return -2;
+    case OVS_ACTION_ATTR_CT: return -2;
 
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
@@ -111,6 +112,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";
@@ -611,6 +613,26 @@ format_odp_tnl_push_action(struct ds *ds, const struct nlattr *attr)
 }
 
 static void
+format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
+{
+    static const struct nl_policy ovs_conntrack_policy[] = {
+        [OVS_CT_ATTR_FLAGS] = { .type = NL_A_U32, .optional = true },
+    };
+    struct nlattr *a[ARRAY_SIZE(ovs_conntrack_policy)];
+    uint32_t flags;
+
+    if (!nl_parse_nested(attr, ovs_conntrack_policy, a, ARRAY_SIZE(a))) {
+        ds_put_cstr(ds, "ct(error)");
+        return;
+    }
+
+    flags = nl_attr_get_u32(a[OVS_CT_ATTR_FLAGS]);
+
+    ds_put_format(ds, "ct(%s)",
+                  flags & OVS_CT_F_COMMIT ? "commit" : "");
+}
+
+static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
     int expected_len;
@@ -699,6 +721,9 @@ 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_CT:
+        format_odp_conntrack_action(ds, a);
+        break;
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
     default:
@@ -1002,6 +1027,48 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
 }
 
 static int
+parse_conntrack_action(const char *s_, struct ofpbuf *actions)
+{
+    const char *s = s_;
+
+    if (ovs_scan(s, "ct(")) {
+        uint32_t flags = 0;
+        size_t start;
+        char *end;
+
+        s += 3;
+        end = strchr(s, ')');
+        if (!end) {
+            return -EINVAL;
+        }
+
+        while (s != end) {
+            int n = -1;
+
+            s += strspn(s, delimiters);
+            if (ovs_scan(s, "commit%n", &n)) {
+                flags |= OVS_CT_F_COMMIT;
+                continue;
+            }
+
+            if (n < 0) {
+                return -EINVAL;
+            }
+            s += n;
+        }
+        s++;
+
+        start = nl_msg_start_nested(actions, OVS_ACTION_ATTR_CT);
+        if (flags) {
+            nl_msg_put_u32(actions, OVS_CT_ATTR_FLAGS, flags);
+        }
+        nl_msg_end_nested(actions, start);
+    }
+
+    return s_ - s;
+}
+
+static int
 parse_odp_action(const char *s, const struct simap *port_names,
                  struct ofpbuf *actions)
 {
@@ -1159,6 +1226,15 @@ parse_odp_action(const char *s, const struct simap *port_names,
     }
 
     {
+        int retval;
+
+        retval = parse_conntrack_action(s, actions);
+        if (retval) {
+            return retval;
+        }
+    }
+
+    {
         struct ovs_action_push_tnl data;
         int n;
 
@@ -1170,6 +1246,7 @@ parse_odp_action(const char *s, const struct simap *port_names,
             return n;
         }
     }
+
     return -EINVAL;
 }
 
@@ -1224,6 +1301,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);
@@ -1843,6 +1921,17 @@ format_odp_key_attr(const struct nlattr *a, const struct nlattr *ma,
         }
         break;
 
+    case OVS_KEY_ATTR_CONN_STATE:
+        /* XXX: This format doesn't deserialize. */
+        if (!is_exact) {
+            format_flags_masked(ds, NULL, packet_conn_state_to_string,
+                                nl_attr_get_u8(a), nl_attr_get_u8(ma));
+        } else {
+            format_flags(ds, packet_conn_state_to_string,
+                         nl_attr_get_u8(a), ',');
+        }
+        break;
+
     case OVS_KEY_ATTR_TUNNEL: {
         struct flow_tnl key, mask_;
         struct flow_tnl *mask = ma ? &mask_ : NULL;
@@ -2445,6 +2534,24 @@ scan_tcp_flags(const char *s, ovs_be16 *key, ovs_be16 *mask)
 }
 
 static int
+scan_conn_state(const char *s, uint8_t *key, uint8_t *mask)
+{
+    uint32_t flags, fmask;
+    int n;
+
+    n = parse_flags(s, packet_conn_state_to_string, &flags,
+                    UINT8_MAX, mask ? &fmask : NULL);
+    if (n >= 0) {
+        *key = htons(flags);
+        if (mask) {
+            *mask = htons(fmask);
+        }
+        return n;
+    }
+    return 0;
+}
+
+static int
 scan_frag(const char *s, uint8_t *key, uint8_t *mask)
 {
     int n;
@@ -2750,6 +2857,8 @@ parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
                              OVS_KEY_ATTR_RECIRC_ID);
     SCAN_SINGLE("dp_hash(", uint32_t, u32, OVS_KEY_ATTR_DP_HASH);
 
+    SCAN_SINGLE("conn_state(", uint8_t, conn_state, OVS_KEY_ATTR_CONN_STATE);
+
     SCAN_BEGIN("tunnel(", struct flow_tnl) {
         SCAN_FIELD("tun_id=", be64, tun_id);
         SCAN_FIELD("src=", ipv4, ip_src);
@@ -2982,6 +3091,9 @@ 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);
 
+    if (data->conn_state) {
+        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);
         nl_msg_put_u32(buf, OVS_KEY_ATTR_DP_HASH, data->dp_hash);
@@ -3179,6 +3291,10 @@ 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 (md->conn_state) {
+        nl_msg_put_u8(buf, OVS_KEY_ATTR_CONN_STATE, md->conn_state);
+    }
+
     /* Add an ingress port attribute if 'odp_in_port' is not the magical
      * value "ODPP_NONE". */
     if (md->in_port.odp_port != ODPP_NONE) {
@@ -3225,6 +3341,10 @@ 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;
+        case OVS_KEY_ATTR_CONN_STATE:
+            md->conn_state = nl_attr_get_u8(nla);
+            wanted_attrs &= ~(1u << OVS_KEY_ATTR_CONN_STATE);
+            break;
         case OVS_KEY_ATTR_TUNNEL: {
             enum odp_key_fitness res;
 
@@ -3776,6 +3896,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 4f0e794..37735ba 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -120,6 +120,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
@@ -129,7 +130,7 @@ 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.
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 2240b86..f546992 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -284,6 +284,9 @@ enum ofp_raw_action_type {
 
     /* NX1.0+(34): struct nx_action_conjunction. */
     NXAST_RAW_CONJUNCTION,
+
+    /* NX1.0+(35): struct nx_action_conntrack. */
+    NXAST_RAW_CT,
 };
 
 /* OpenFlow actions are always a multiple of 8 bytes in length. */
@@ -4367,6 +4370,91 @@ format_SAMPLE(const struct ofpact_sample *a, struct ds *s)
                   a->obs_domain_id, a->obs_point_id);
 }
 
+/* Action structure for NXAST_CT.
+ *
+ * Pass traffic to the connection tracker.  If 'flags' is
+ * NX_CT_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_CT. */
+    ovs_be16 flags;             /* Zero or more NX_CT_F_* flags.
+                                 * Unspecified flag bits must be zero. */
+    ovs_be16 zone;              /* Connection tracking context. */
+    uint8_t  pad[2];
+};
+OFP_ASSERT(sizeof(struct nx_action_conntrack) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac, struct ofpbuf *out)
+{
+    struct ofpact_conntrack *conntrack;
+
+    conntrack = ofpact_put_CT(out);
+    conntrack->flags = ntohs(nac->flags);
+    conntrack->zone = ntohs(nac->zone);
+
+    return 0;
+}
+
+static void
+encode_CT(const struct ofpact_conntrack *conntrack,
+          enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+    struct nx_action_conntrack *nac;
+
+    nac = put_NXAST_CT(out);
+    nac->flags = htons(conntrack->flags);
+    nac->zone = htons(conntrack->zone);
+}
+
+/* Parses 'arg' as the argument to a "ct" 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 * OVS_WARN_UNUSED_RESULT
+parse_CT(char *arg, struct ofpbuf *ofpacts,
+         enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+    struct ofpact_conntrack *oc = ofpact_put_CT(ofpacts);
+    char *key, *value;
+
+    oc->flags = 0;
+    while (ofputil_parse_key_value(&arg, &key, &value)) {
+        char *error = NULL;
+
+        if (!strcmp(key, "commit")) {
+            oc->flags |= NX_CT_F_COMMIT;
+        } else if (!strcmp(key, "recirc")) {
+            oc->flags |= NX_CT_F_RECIRC;
+        } else if (!strcmp(key, "zone")) {
+            error = str_to_u16(value, "zone", &oc->zone);
+        } else {
+            error = xasprintf("invalid key \"%s\" in \"ct\" argument",
+                              key);
+        }
+        if (error) {
+            return error;
+        }
+    }
+    return NULL;
+}
+
+static void
+format_CT(const struct ofpact_conntrack *a, struct ds *s)
+{
+    ds_put_format(s, "ct(%s%szone=%"PRIu16")",
+                  a->flags & NX_CT_F_COMMIT ? "commit," : "",
+                  a->flags & NX_CT_F_RECIRC ? "recirc," : "",
+                  a->zone);
+}
+
 /* Meter instruction. */
 
 static void
@@ -4746,6 +4834,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
         return true;
     case OFPACT_BUNDLE:
     case OFPACT_CLEAR_ACTIONS:
+    case OFPACT_CT:
     case OFPACT_CONTROLLER:
     case OFPACT_DEC_MPLS_TTL:
     case OFPACT_DEC_TTL:
@@ -4819,6 +4908,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_CT:
     case OFPACT_ENQUEUE:
     case OFPACT_EXIT:
     case OFPACT_UNROLL_XLATE:
@@ -5046,6 +5136,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_EXIT:
     case OFPACT_UNROLL_XLATE:
     case OFPACT_SAMPLE:
+    case OFPACT_CT:
     default:
         return OVSINST_OFPIT11_APPLY_ACTIONS;
     }
@@ -5600,6 +5691,16 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
     case OFPACT_SAMPLE:
         return 0;
 
+    case OFPACT_CT: {
+        struct ofpact_conntrack *oc = ofpact_get_CT(a);
+
+        if (!dl_type_is_ip_any(flow->dl_type)
+            || (flow->conn_state & CS_INVALID && oc->flags & NX_CT_F_COMMIT)) {
+            inconsistent_match(usable_protocols);
+        }
+        return 0;
+    }
+
     case OFPACT_CLEAR_ACTIONS:
         return 0;
 
@@ -6040,6 +6141,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_CT:
     default:
         return false;
     }
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 785c814..6d1bad8 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -106,6 +106,7 @@
     OFPACT(EXIT,            ofpact_null,        ofpact, "exit")         \
     OFPACT(SAMPLE,          ofpact_sample,      ofpact, "sample")       \
     OFPACT(UNROLL_XLATE,    ofpact_unroll_xlate, ofpact, "unroll_xlate") \
+    OFPACT(CT,              ofpact_conntrack,   ofpact, "ct")           \
                                                                         \
     /* Instructions. */                                                 \
     OFPACT(METER,           ofpact_meter,       ofpact, "meter")        \
@@ -465,6 +466,27 @@ 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_CT_F_COMMIT is set, then the connection entry is moved from the
+ * unconfirmed to confirmed list in the tracker.
+ *
+ * If NX_CT_F_RECIRC is set, then the packet will be recirculated through
+ * the datapath after running through the connection tracker. */
+enum nx_conntrack_flags {
+    NX_CT_F_COMMIT = 1 << 0,
+    NX_CT_F_RECIRC = 1 << 1
+};
+
+/* OFPACT_CT.
+ *
+ * Used for NXAST_CT. */
+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 cec074f..3e3884c 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -158,6 +158,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 60cc674..6d30f95 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -3311,6 +3311,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
@@ -3453,6 +3454,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.c b/lib/packets.c
index 419c6af..c29d941 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -1057,3 +1057,24 @@ packet_csum_pseudoheader(const struct ip_header *ip)
 
     return partial;
 }
+
+const char *
+packet_conn_state_to_string(uint32_t flag)
+{
+    switch (flag) {
+    case CS_REPLY_DIR:
+        return "rpl";
+    case CS_TRACKED:
+        return "trk";
+    case CS_NEW:
+        return "new";
+    case CS_ESTABLISHED:
+        return "est";
+    case CS_RELATED:
+        return "rel";
+    case CS_INVALID:
+        return "inv";
+    default:
+        return NULL;
+    }
+}
diff --git a/lib/packets.h b/lib/packets.h
index b146a50..af2c767 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -65,6 +65,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) \
@@ -585,6 +586,17 @@ struct tcp_header {
 };
 BUILD_ASSERT_DECL(TCP_HEADER_LEN == sizeof(struct tcp_header));
 
+/* Connection states */
+#define CS_NEW               0x01
+#define CS_ESTABLISHED       0x02
+#define CS_RELATED           0x04
+#define CS_INVALID           0x20
+#define CS_REPLY_DIR         0x40
+#define CS_TRACKED           0x80
+
+/* Undefined connection state bits. */
+#define CS_UNSUPPORTED_MASK  0x18
+
 #define ARP_HRD_ETHERNET 1
 #define ARP_PRO_IP 0x0800
 #define ARP_OP_REQUEST 1
@@ -808,4 +820,6 @@ void compose_arp(struct dp_packet *b, const uint8_t eth_src[ETH_ADDR_LEN],
                  ovs_be32 ip_src, ovs_be32 ip_dst);
 uint32_t packet_csum_pseudoheader(const struct ip_header *);
 
+const char *packet_conn_state_to_string(uint32_t flag);
+
 #endif /* packets.h */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 71b8bef..edefd05 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -2723,6 +2723,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
     struct flow_tnl flow_tnl;
     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;
     bool tnl_push_pop_send = false;
@@ -2866,6 +2867,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 (count_skb_priorities(xport)) {
@@ -2985,6 +2987,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;
 }
 
@@ -3449,9 +3452,10 @@ execute_controller_action(struct xlate_ctx *ctx, int len,
     dp_packet_delete(packet);
 }
 
-/* Called only when ctx->recirc_action_offset is set. */
 static void
-compose_recirculate_action(struct xlate_ctx *ctx)
+compose_recirculate_action__(struct xlate_ctx *ctx, struct ofpbuf *stack,
+                             int action_offset, uint32_t ofpacts_len,
+                             const struct ofpact *ofpacts)
 {
     struct recirc_metadata md;
     bool use_masked;
@@ -3464,16 +3468,15 @@ compose_recirculate_action(struct xlate_ctx *ctx)
 
     recirc_metadata_from_flow(&md, &ctx->xin->flow);
 
-    ovs_assert(ctx->recirc_action_offset >= 0);
+    ovs_assert(action_offset >= 0);
 
     /* Only allocate recirculation ID if we have a packet. */
     if (ctx->xin->packet) {
         /* Allocate a unique recirc id for the given metadata state in the
          * flow.  The life-cycle of this recirc id is managed by associating it
          * with the udpif key ('ukey') created for each new datapath flow. */
-        id = recirc_alloc_id_ctx(ctx->xbridge->ofproto, 0, &md, &ctx->stack,
-                                 ctx->recirc_action_offset,
-                                 ctx->action_set.size, ctx->action_set.data);
+        id = recirc_alloc_id_ctx(ctx->xbridge->ofproto, 0, &md, stack,
+                                 action_offset, ofpacts_len, ofpacts);
         if (!id) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
             VLOG_ERR_RL(&rl, "Failed to allocate recirculation id");
@@ -3484,14 +3487,21 @@ compose_recirculate_action(struct xlate_ctx *ctx)
         /* Look up an existing recirc id for the given metadata state in the
          * flow.  No new reference is taken, as the ID is RCU protected and is
          * only required temporarily for verification. */
-        id = recirc_find_id(ctx->xbridge->ofproto, 0, &md, &ctx->stack,
-                            ctx->recirc_action_offset,
-                            ctx->action_set.size, ctx->action_set.data);
+        id = recirc_find_id(ctx->xbridge->ofproto, 0, &md, stack,
+                            action_offset, ofpacts_len, ofpacts);
         /* We let zero 'id' to be used in the RECIRC action below, which will
          * fail all revalidations as zero is not a valid recirculation ID. */
     }
 
     nl_msg_put_u32(ctx->xout->odp_actions, OVS_ACTION_ATTR_RECIRC, id);
+}
+
+/* Called only when ctx->recirc_action_offset is set. */
+static void
+compose_recirculate_action(struct xlate_ctx *ctx)
+{
+    compose_recirculate_action__(ctx, &ctx->stack, ctx->recirc_action_offset,
+                                 ctx->action_set.size, ctx->action_set.data);
 
     /* Undo changes done by recirculation. */
     ctx->action_set.size = ctx->recirc_action_offset;
@@ -4039,6 +4049,7 @@ recirc_unroll_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
         case OFPACT_WRITE_ACTIONS:
         case OFPACT_METER:
         case OFPACT_SAMPLE:
+        case OFPACT_CT:
             break;
 
             /* These need not be copied for restoration. */
@@ -4062,6 +4073,26 @@ recirc_unroll_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
     }
 
 static void
+compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
+{
+    struct ofpbuf *odp_actions = ctx->xout->odp_actions;
+    uint32_t flags = 0;
+    size_t ct_offset;
+
+    if (ofc->flags & NX_CT_F_COMMIT) {
+        flags |= OVS_CT_F_COMMIT;
+    }
+
+    ct_offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_CT);
+    nl_msg_put_u32(odp_actions, OVS_CT_ATTR_FLAGS, flags);
+    nl_msg_end_nested(odp_actions, ct_offset);
+
+    if (ofc->flags & NX_CT_F_RECIRC) {
+        compose_recirculate_action__(ctx, NULL, 0, 0, NULL);
+    }
+}
+
+static void
 do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
                  struct xlate_ctx *ctx)
 {
@@ -4425,6 +4456,10 @@ 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_CT:
+            compose_conntrack_action(ctx, ofpact_get_CT(a));
+            break;
         }
 
         /* Check if need to store this and the remaining actions for later
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 1e3db44..72f219f 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1199,6 +1199,36 @@ check_masked_set_action(struct dpif_backer *backer)
     return !error;
 }
 
+#define CHECK_FEATURE(FIELD)                                                \
+static bool                                                                 \
+check_##FIELD(struct dpif_backer *backer)                                   \
+{                                                                           \
+    struct flow flow;                                                       \
+    struct odputil_keybuf keybuf;                                           \
+    struct ofpbuf key;                                                      \
+    bool enable;                                                            \
+                                                                            \
+    memset(&flow, 0, sizeof flow);                                          \
+    flow.FIELD = 1;                                                         \
+                                                                            \
+    ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);                         \
+    odp_flow_key_from_flow(&key, &flow, NULL, 0, true);                     \
+    enable = dpif_probe_feature(backer->dpif, #FIELD, &key, NULL);          \
+                                                                            \
+    if (enable) {                                                           \
+        VLOG_INFO("%s: Datapath supports "#FIELD, dpif_name(backer->dpif)); \
+    } else {                                                                \
+        VLOG_INFO("%s: Datapath does not support "#FIELD,                   \
+                  dpif_name(backer->dpif));                                 \
+    }                                                                       \
+                                                                            \
+    return enable;                                                          \
+}
+
+CHECK_FEATURE(conn_state)
+
+#undef CHECK_FEATURE
+
 static void
 check_support(struct dpif_backer *backer)
 {
@@ -1210,6 +1240,7 @@ check_support(struct dpif_backer *backer)
     backer->support.masked_set_action = check_masked_set_action(backer);
     backer->support.ufid = check_ufid(backer);
     backer->support.tnl_push_pop = dpif_supports_tnl_push_pop(backer->dpif);
+    backer->support.conn_state = check_conn_state(backer);
 }
 
 static int
@@ -3890,10 +3921,35 @@ rule_dealloc(struct rule *rule_)
 }
 
 static enum ofperr
+rule_check(struct rule *rule)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->ofproto);
+    struct match match;
+
+    minimatch_expand(&rule->cr.match, &match);
+
+    if (match.wc.masks.conn_state && !ofproto->backer->support.conn_state) {
+        return OFPERR_OFPBMC_BAD_FIELD;
+    }
+    if (match.wc.masks.conn_state & CS_UNSUPPORTED_MASK) {
+        return OFPERR_OFPBMC_BAD_MASK;
+    }
+
+    return 0;
+}
+
+static enum ofperr
 rule_construct(struct rule *rule_)
     OVS_NO_THREAD_SAFETY_ANALYSIS
 {
     struct rule_dpif *rule = rule_dpif_cast(rule_);
+    int error;
+
+    error = rule_check(rule_);
+    if (error) {
+        return error;
+    }
+
     ovs_mutex_init_adaptive(&rule->stats_mutex);
     rule->stats.n_packets = 0;
     rule->stats.n_bytes = 0;
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index f9f62ab..2a85e47 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -89,6 +89,7 @@ struct dpif_backer_support {
     bool recirc;
     bool tnl_push_pop;
     bool ufid;
+    bool conn_state;
 };
 
 size_t ofproto_dpif_get_max_mpls_depth(const struct ofproto_dpif *);
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/python/ovs/daemon.py b/python/ovs/daemon.py
index 4a704c3..74aa144 100644
--- a/python/ovs/daemon.py
+++ b/python/ovs/daemon.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2010, 2011, 2012 Nicira, Inc.
+# Copyright (c) 2010, 2011, 2012, 2015 Nicira, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -486,11 +486,11 @@ def _check_already_running():
                % (_pidfile, os.strerror(pid)))
 
 
-def add_args(parser):
+def add_args(parser, pidfile=None):
     """Populates 'parser', an ArgumentParser allocated using the argparse
     module, with the command line arguments required by the daemon module."""
 
-    pidfile = make_pidfile_name(None)
+    pidfile = make_pidfile_name(pidfile)
 
     group = parser.add_argument_group(title="Daemon Options")
     group.add_argument("--detach", action="store_true",
diff --git a/tests/automake.mk b/tests/automake.mk
index e63fb19..7191004 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -310,6 +310,7 @@ tests_test_type_props_SOURCES = tests/test-type-props.c
 # Python tests.
 CHECK_PYFILES = \
 	tests/appctl.py \
+	tests/test-conntrack.py \
 	tests/test-daemon.py \
 	tests/test-json.py \
 	tests/test-jsonrpc.py \
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 067f900..6ab908c 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -82,7 +82,7 @@ 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)
 ])
 AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_XOUT], [0], [dnl
-pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,icmp,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,icmp_type=8,icmp_code=0, actions: <del>
+pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,conn_state=0,icmp,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,icmp_type=8,icmp_code=0, actions: <del>
 recirc_id=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>
 ])
 
diff --git a/tests/kmod-macros.at b/tests/kmod-macros.at
index 5824940..df500c3 100644
--- a/tests/kmod-macros.at
+++ b/tests/kmod-macros.at
@@ -77,3 +77,15 @@ m4_define([ADD_VETH],
       AT_CHECK([ip netns exec $2 ip link set dev $1 up])
     ]
 )
+
+# NETNS_DAEMONIZE([namespace], [command], [pidfile])
+#
+# Run 'command' as a background process within 'namespace' and record its pid
+# to 'pidfile'.
+#
+m4_define([NETNS_DAEMONIZE],
+    [ echo "ip netns exec $1 $2 & echo \$! > $3"
+      ip netns exec $1 $2 & echo $! > $3
+      echo "kill \`cat $3\`" >> cleanup
+    ]
+)
diff --git a/tests/kmod-traffic.at b/tests/kmod-traffic.at
index f7783be..85b966c 100644
--- a/tests/kmod-traffic.at
+++ b/tests/kmod-traffic.at
@@ -1,4 +1,6 @@
-AT_BANNER([kmod-sanity])
+m4_define([FORMAT_CT], [[grep "dst=$1" | sed -e 's/port=[0-9]*/port=<cleared>/g' -e 's/  */ /g' | cut -d' ' -f4- | sort | uniq]])
+
+AT_BANNER([kmod-traffic])
 
 AT_SETUP([kmod - ping between two ports])
 OVS_KMOD_VSWITCHD_START(
@@ -14,3 +16,213 @@ AT_CHECK([ip netns exec at_ns0 bash -c "ping -q -c 3 -i 0.3 -w 2 10.1.1.2 > ping
 
 OVS_KMOD_VSWITCHD_STOP([], DEL_NAMESPACES(at_ns0, at_ns1))
 AT_CLEANUP
+
+AT_SETUP([conntrack - controller])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+OVS_KMOD_VSWITCHD_START(
+   [set-fail-mode br0 standalone -- ])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+
+dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+priority=1,action=drop
+priority=10,arp,action=normal
+in_port=1,udp,action=ct(commit,zone=0),controller
+in_port=2,conn_state=-trk,udp,action=ct(recirc,zone=0)
+in_port=2,conn_state=+trk+est-new,udp,action=controller
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+AT_CAPTURE_FILE([ofctl_monitor.log])
+AT_CHECK([ovs-ofctl monitor br0 65534 invalid_ttl --detach --no-chdir --pidfile 2> ofctl_monitor.log])
+
+dnl Send an unsolicited reply from port 2. This should be dropped.
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 2 ct\(recirc,zone=0\) '50540000000a50540000000908004500001c00000000001100000a0101020a0101010002000100080000'])
+
+dnl OK, now start a new connection from port 1.
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 1 ct\(commit,zone=0\),controller '50540000000a50540000000908004500001c00000000001100000a0101010a0101020001000200080000'])
+
+dnl Now try a reply from port 2.
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 2 ct\(recirc,zone=0\) '50540000000a50540000000908004500001c00000000001100000a0101020a0101010002000100080000'])
+
+dnl Check this output. We only see the latter two packets, not the first.
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): total_len=42 in_port=1 (via action) data_len=42 (unbuffered)
+udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_tos=0,nw_ecn=0,nw_ttl=0,tp_src=1,tp_dst=2 udp_csum:0
+NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=42 in_port=2 conn_state=0xc2 (via action) data_len=42 (unbuffered)
+udp,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.1.1.2,nw_dst=10.1.1.1,nw_tos=0,nw_ecn=0,nw_ttl=0,tp_src=2,tp_dst=1 udp_csum:0
+])
+
+OVS_KMOD_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - IPv4 HTTP])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+OVS_KMOD_VSWITCHD_START(
+   [set-fail-mode br0 standalone -- ])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+
+dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+priority=1,action=drop
+priority=10,arp,action=normal
+priority=10,icmp,action=normal
+in_port=1,tcp,action=ct(commit,zone=0),2
+in_port=2,conn_state=-trk,tcp,action=ct(recirc,zone=0)
+in_port=2,conn_state=+trk+est-new,tcp,action=1
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl Basic connectivity check.
+AT_CHECK([ip netns exec at_ns0 bash -c "ping -q -c 3 -i 0.3 -w 2 10.1.1.2 >/dev/null"])
+
+dnl HTTP requests from ns0->ns1 should work fine.
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-conntrack.py]], [test-conntrack0.pid])
+AT_CHECK([ip netns exec at_ns0 wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log])
+
+AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2)], [0], [dnl
+TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=0 use=1
+])
+
+dnl HTTP requests from ns1->ns0 should fail due to network failure.
+dnl Try 5 times, in 1 second intervals.
+NETNS_DAEMONIZE([at_ns0], [[$PYTHON $srcdir/test-conntrack.py]], [test-conntrack1.pid])
+AT_CHECK([ip netns exec at_ns1 wget 10.1.1.1 -t 3 -T 1 -v -o wget1.log], [4])
+
+OVS_KMOD_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - IPv6 HTTP])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+OVS_KMOD_VSWITCHD_START(
+   [set-fail-mode br0 standalone -- ])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "fc00::1/96")
+ADD_VETH(p1, at_ns1, br0, "fc00::2/96")
+
+dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+priority=1,action=drop
+priority=10,icmp6,action=normal
+in_port=1,tcp6,action=ct(commit,zone=0),2
+in_port=2,conn_state=-trk,tcp6,action=ct(recirc,zone=0)
+in_port=2,conn_state=+trk+est-new,tcp6,action=1
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl Without this sleep, we get occasional failures due to the following error:
+dnl "connect: Cannot assign requested address"
+sleep 2;
+
+dnl Basic connectivity check.
+AT_CHECK([ip netns exec at_ns0 ping6 -q -c 3 -i 0.3 -w 2 fc00::2 >ping.output])
+
+dnl HTTP requests from ns0->ns1 should work fine.
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-conntrack.py http6]], [test-conntrack0.pid])
+
+AT_CHECK([ip netns exec at_ns0 wget http://[[fc00::2]] -t 3 -T 1 --retry-connrefused -v -o wget0.log])
+
+dnl HTTP requests from ns1->ns0 should fail due to network failure.
+dnl Try 5 times, in 1 second intervals.
+NETNS_DAEMONIZE([at_ns0], [[$PYTHON $srcdir/test-conntrack.py http6]], [test-conntrack1.pid])
+AT_CHECK([ip netns exec at_ns1 wget http://[[fc00::1]] -t 3 -T 1 -v -o wget1.log], [4])
+
+OVS_KMOD_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - commit, recirc])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+OVS_KMOD_VSWITCHD_START(
+   [set-fail-mode br0 standalone -- ])
+
+ADD_NAMESPACES(at_ns0, at_ns1, at_ns2, at_ns3)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+ADD_VETH(p2, at_ns2, br0, "10.1.1.3/24")
+ADD_VETH(p3, at_ns3, br0, "10.1.1.4/24")
+
+dnl Allow any traffic from ns0->ns1, ns2->ns3.
+AT_DATA([flows.txt], [dnl
+priority=1,action=drop
+priority=10,arp,action=normal
+priority=10,icmp,action=normal
+in_port=1,tcp,conn_state=-trk,action=ct(commit,recirc)
+in_port=1,tcp,conn_state=+trk,action=2
+in_port=2,tcp,conn_state=-trk,action=ct(recirc)
+in_port=2,tcp,conn_state=+trk,action=1
+in_port=3,tcp,conn_state=-trk,action=set_field:0->metadata,ct(recirc)
+in_port=3,tcp,conn_state=+trk,metadata=0,action=set_field:1->metadata,ct(commit,recirc)
+in_port=3,tcp,conn_state=+trk,metadata=1,action=4
+in_port=4,tcp,conn_state=-trk,action=ct(commit,recirc)
+in_port=4,tcp,conn_state=+trk,action=3
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl HTTP requests from p0->p1 should work fine.
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-conntrack.py]], [test-conntrack0.pid])
+AT_CHECK([ip netns exec at_ns0 wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log])
+
+dnl HTTP requests from p2->p3 should work fine.
+NETNS_DAEMONIZE([at_ns3], [[$PYTHON $srcdir/test-conntrack.py]], [test-conntrack1.pid])
+AT_CHECK([ip netns exec at_ns2 wget 10.1.1.4 -t 3 -T 1 --retry-connrefused -v -o wget1.log])
+
+OVS_KMOD_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conntrack - invalid])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+OVS_KMOD_VSWITCHD_START(
+   [set-fail-mode br0 standalone -- ])
+
+ADD_NAMESPACES(at_ns0, at_ns1, at_ns2, at_ns3)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+ADD_VETH(p2, at_ns2, br0, "10.1.1.3/24")
+ADD_VETH(p3, at_ns3, br0, "10.1.1.4/24")
+
+dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
+AT_DATA([flows.txt], [dnl
+priority=1,action=drop
+priority=10,arp,action=normal
+priority=10,icmp,action=normal
+in_port=1,tcp,action=ct(zone=0),2
+in_port=2,conn_state=-trk,tcp,action=ct(recirc,zone=0)
+in_port=2,conn_state=+trk+new,tcp,action=1
+in_port=3,tcp,action=ct(zone=0),4
+in_port=4,conn_state=-trk,tcp,action=ct(recirc,zone=0)
+in_port=4,conn_state=+trk+inv,tcp,action=3
+in_port=4,conn_state=+trk+new,tcp,action=3
+])
+
+ovs-appctl vlog/set dpif:dbg
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl We set up our rules to allow the request without committing. The return
+dnl traffic can't be identified, because the initial request wasn't committed.
+dnl For the first pair of ports, this means that the connection fails.
+NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-conntrack.py]], [test-conntrack0.pid])
+AT_CHECK([ip netns exec at_ns0 wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log], [4])
+
+dnl For the second pair, we allow packets from invalid connections, so it works.
+NETNS_DAEMONIZE([at_ns3], [[$PYTHON $srcdir/test-conntrack.py]], [test-conntrack1.pid])
+AT_CHECK([ip netns exec at_ns2 wget 10.1.1.4 -t 3 -T 1 --retry-connrefused -v -o wget1.log])
+
+OVS_KMOD_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 139dfdd..0ad80e9 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -6142,8 +6142,8 @@ for i in 1 2 3 4; do
 done
 sleep 1
 AT_CHECK([cat ovs-vswitchd.log | STRIP_UFID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
-pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,icmp,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,icmp_type=8,icmp_code=0, actions:2
-pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,icmp,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,icmp_type=8,icmp_code=0, actions:drop
+pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,conn_state=0,icmp,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,icmp_type=8,icmp_code=0, actions:2
+pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,conn_state=0,icmp,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,icmp_type=8,icmp_code=0, actions:drop
 ])
 AT_CHECK([cat ovs-vswitchd.log | STRIP_UFID | 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
diff --git a/tests/ofproto.at b/tests/ofproto.at
index f4e5321..205d80b 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1512,6 +1512,7 @@ OVS_VSWITCHD_START
       in_port_oxm: exact match or wildcard
       actset_output: exact match or wildcard
       pkt_mark: arbitrary mask
+      conn_state: arbitrary mask
       reg0: arbitrary mask
       reg1: arbitrary mask
       reg2: arbitrary mask
@@ -1581,7 +1582,7 @@ AT_CHECK(
 # Check that the configuration was updated.
 mv expout orig-expout
 sed 's/classifier/main/
-77s/1000000/1024/' < orig-expout > expout
+78s/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/tests/test-conntrack.py b/tests/test-conntrack.py
new file mode 100755
index 0000000..8c8b7b8
--- /dev/null
+++ b/tests/test-conntrack.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2015 Nicira, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import socket
+
+from BaseHTTPServer import HTTPServer
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+from SocketServer import TCPServer
+
+
+class TCPServerV6(HTTPServer):
+    address_family = socket.AF_INET6
+
+
+def main():
+    SERVERS = {
+        'http':  [TCPServer,   SimpleHTTPRequestHandler, 80],
+        'http6': [TCPServerV6, SimpleHTTPRequestHandler, 80],
+    }
+
+    parser = argparse.ArgumentParser(
+            description='Run basic application servers.')
+    parser.add_argument('proto', default='http', nargs='?',
+            help='protocol to serve (http, http6)')
+    args = parser.parse_args()
+
+    if args.proto not in SERVERS:
+        parser.print_help()
+        exit(1)
+
+    constructor = SERVERS[args.proto][0]
+    handler = SERVERS[args.proto][1]
+    port = SERVERS[args.proto][2]
+    srv = constructor(('', port), handler)
+    srv.serve_forever()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index c667aa4..c677118 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1182,6 +1182,42 @@ is used only with the \fBconjunction\fR action (see below).
 .IP
 This field was introduced in Open vSwitch 2.4.
 .
+.IP \fBconn_state=\fIflags\fB/\fImask\fR
+.IQ \fBconn_state=\fR[\fB+\fIflag\fR...][\fB-\fIflag\fR...]
+Bitwise match on connection state flags.  The flags are only available
+after a call to the \fBct\fR action with the "recirc" flag set.
+.IP
+The \fIflags\fR and \fImask\fR are 8-bit numbers written in decimal or
+in hexadecimal prefixed by \fB0x\fR.  Each 1-bit in \fImask\fR requires
+that the corresponding bit in \fIflags\fR must match.  Each 0-bit in
+\fImask\fR causes the corresponding bit to be ignored.
+.IP
+Alternatively, the flags can be specified by their symbolic names
+(listed below), each preceded by either \fB+\fR for a flag that must
+be set, or \fB\-\fR for a flag that must be unset, without any other
+delimiters between the flags.  Flags not mentioned are wildcarded.  For
+example, \fBtcp,conn_state=+trk\-new\fR matches TCP packets that
+have been run through the connection tracker and do not establish a new
+flow.
+.IP
+The following flags describe the state of the tracking:
+.RS
+.IP "\fB0x80: trk\fR"
+Connection tracking has occurred.
+.IP "\fB0x40: rpl\fR"
+The flow is in the reply direction, meaning it did not initiate the
+connection.
+.IP "\fB0x20: inv\fR"
+The flow is invalid, meaning that the connection tracker couldn't identify the
+connection.
+.IP "\fB0x01: new\fR"
+This is the beginning of a new connection.
+.IP "\fB0x02: est\fR"
+This is part of an already existing connection.
+.IP "\fB0x04: rel\fR"
+This is a new connection that is related to an existing connection.
+.RE
+.
 .PP
 Defining IPv6 flows (those with \fBdl_type\fR equal to 0x86dd) requires
 support for NXM.  The following shorthand notations are available for
@@ -1419,6 +1455,26 @@ OpenFlow implementations do not support queuing at all.
 Restores the queue to the value it was before any \fBset_queue\fR
 actions were applied.
 .
+.IP \fBct\fR
+.IQ \fBct\fB(\fR[\fIargument\fR][\fB,\fIargument\fR...]\fB)
+Run the packet through the connection tracker.  The following arguments
+are supported:
+
+.RS
+.IP \fBrecirc\fR
+The packet should be sent back for further processing with the
+\fBconn_state\fR match fields set.
+.IP \fBcommit\fR
+Commit the flow to the connection tracking module.
+.IP \fBzone\fR
+A 16-bit context id that can be used to isolate connections into
+separate domains that allow overlapping network addresses.  If a zone is
+not provided, then the default is to use zone zero.
+.RE
+.IP
+Currently, connection tracking is only available on Linux kernels with the
+conntrack module loaded.
+.
 .IP \fBdec_ttl\fR
 .IQ \fBdec_ttl\fB[\fR(\fIid1,id2\fI)\fR]\fR
 Decrement TTL of IPv4 packet or hop limit of IPv6 packet.  If the
-- 
1.7.10.4




More information about the dev mailing list