Add the ability to match and set the "conn_label" field that associates
the label with a connection tracking entry. This is only available on on
systems that support connection tracking.

OpenFlow rules which have a "set_field:X->conn_label" action must have a prior
ct() action in the action list. The connection tracking entry in the zone
specified by the prior ct() action is the entry which the label is associated
with. For example, "action=ct(zone=1),ct(zone=2),set_field:1->conn_label" will
apply the conn_label to the connection in zone 2, but not the entry in zone 1.
For the conn_label to persist, the connection must be committed.

Here's a simple example flow table to allow outbound TCP traffic from port 1;
Associate label "1" with all connections, and match on that label:

ovs-ofctl del-flows br0
ovs-ofctl add-flow br0 in_port=1,tcp, \
  action=ct(commit),set_field:1->conn_label,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,conn_label=1,tcp,action=1

Signed-off-by: Joe Stringer <joestrin...@nicira.com>
---
 build-aux/extract-ofp-fields                      |    4 +-
 datapath/flow_netlink.c                           |    2 +-
 datapath/linux/compat/include/linux/openvswitch.h |    6 ++
 lib/dpif-netdev.c                                 |    3 +-
 lib/flow.c                                        |   25 ++++++--
 lib/flow.h                                        |    9 ++-
 lib/match.c                                       |   39 +++++++++++-
 lib/match.h                                       |    3 +
 lib/meta-flow.c                                   |   70 +++++++++++++++++++++
 lib/meta-flow.h                                   |   19 ++++++
 lib/nx-match.c                                    |   14 ++++-
 lib/odp-execute.c                                 |    5 +-
 lib/odp-util.c                                    |   55 +++++++++++++++-
 lib/odp-util.h                                    |    7 ++-
 lib/ofp-actions.c                                 |    4 +-
 lib/ofp-print.c                                   |    3 +
 lib/ofp-util.c                                    |    7 ++-
 lib/packets.h                                     |    1 +
 ofproto/ofproto-dpif-rid.h                        |    2 +-
 ofproto/ofproto-dpif-xlate.c                      |    5 +-
 ofproto/ofproto-dpif.c                            |   18 ++++--
 ofproto/ofproto-dpif.h                            |    1 +
 tests/dpif-netdev.at                              |    2 +-
 tests/kmod-traffic.at                             |   39 ++++++++++++
 tests/ofproto-dpif.at                             |    4 +-
 tests/ofproto.at                                  |    5 +-
 utilities/ovs-ofctl.8.in                          |   17 +++--
 27 files changed, 328 insertions(+), 41 deletions(-)

diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
index 78121ec..c5ae5ab 100755
--- a/build-aux/extract-ofp-fields
+++ b/build-aux/extract-ofp-fields
@@ -19,11 +19,13 @@ TYPES = {"u8": 1,
          "be32": 4,
          "MAC": 6,
          "be64": 8,
-         "IPv6": 16}
+         "IPv6": 16,
+         "u128": 16}
 
 FORMATTING = {"decimal":            ("MFS_DECIMAL",      1, 8),
               "hexadecimal":        ("MFS_HEXADECIMAL",  1, 8),
               "conn state":         ("MFS_CONN_STATE",   1, 1),
+              "conn label":         ("MFS_CONN_LABEL",  16,16),
               "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 93422fe..5c494b5 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 != 25);
+       BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 26);
 
        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 d915333..37526f9 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -346,6 +346,7 @@ enum ovs_key_attr {
        OVS_KEY_ATTR_CONN_STATE,/* u8 of OVS_CS_F_* */
        OVS_KEY_ATTR_CONN_ZONE, /* u16 connection tracking zone. */
        OVS_KEY_ATTR_CONN_MARK, /* u32 connection tracking mark */
+       OVS_KEY_ATTR_CONN_LABEL,/* 16-octet connection tracking label */
 
 #ifdef __KERNEL__
        /* Only used within kernel data path. */
@@ -459,6 +460,11 @@ struct ovs_key_nd {
        __u8    nd_tll[ETH_ALEN];
 };
 
+#define OVS_CT_LABEL_LEN       16
+struct ovs_key_conn_label {
+       __u8    conn_label[OVS_CT_LABEL_LEN];
+};
+
 /* 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. */
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 6e809d5..190aed3 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1927,7 +1927,8 @@ dpif_netdev_flow_from_nlattrs(const struct nlattr *key, 
uint32_t key_len,
     }
 
     /* Userspace datapath doesn't support conntrack. */
-    if (flow->conn_state || flow->conn_zone || flow->conn_mark) {
+    if (flow->conn_state || flow->conn_zone || flow->conn_mark
+        || ovs_u128_nonzero(flow->conn_label)) {
         return EINVAL;
     }
 
diff --git a/lib/flow.c b/lib/flow.c
index 75d82e6..1ffb602 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -119,7 +119,7 @@ struct mf_ctx {
  * away.  Some GCC versions gave warnings on ALWAYS_INLINE, so these are
  * defined as macros. */
 
-#if (FLOW_WC_SEQ != 31)
+#if (FLOW_WC_SEQ != 32)
 #define MINIFLOW_ASSERT(X) ovs_assert(X)
 BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will have runtime "
                "assertions enabled. Consider updating FLOW_WC_SEQ after "
@@ -253,6 +253,9 @@ BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() will 
have runtime "
     MF.map |= UINT64_C(3) << ofs64; /* Both words. */           \
 }
 
+#define miniflow_push_uint64(MF, FIELD, VALUE)                      \
+    miniflow_push_uint64_(MF, offsetof(struct flow, FIELD), VALUE)
+
 #define miniflow_push_uint32(MF, FIELD, VALUE)                      \
     miniflow_push_uint32_(MF, offsetof(struct flow, FIELD), VALUE)
 
@@ -474,6 +477,10 @@ miniflow_extract(struct dp_packet *packet, struct miniflow 
*dst)
         miniflow_push_uint8(mf, conn_state, md->conn_state);
         miniflow_pad_to_64(mf, pad1);
     }
+    if (ovs_u128_nonzero(md->conn_label)) {
+        miniflow_push_words(mf, conn_label, &md->conn_label,
+                            sizeof md->conn_label / 8);
+    }
 
     /* Initialize packet's layer pointer and offsets. */
     l2 = data;
@@ -790,7 +797,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 == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
     fmd->dp_hash = flow->dp_hash;
     fmd->recirc_id = flow->recirc_id;
@@ -805,6 +812,7 @@ flow_get_metadata(const struct flow *flow, struct 
flow_metadata *fmd)
     fmd->conn_state = flow->conn_state;
     fmd->conn_zone = flow->conn_zone;
     fmd->conn_mark = flow->conn_mark;
+    fmd->conn_label = flow->conn_label;
     fmd->in_port = flow->in_port.ofp_port;
 }
 
@@ -919,6 +927,9 @@ flow_format(struct ds *ds, const struct flow *flow)
     if (!flow->conn_mark) {
         WC_UNMASK_FIELD(wc, conn_mark);
     }
+    if (is_all_zeros(&flow->conn_label, sizeof(flow->conn_label))) {
+        WC_UNMASK_FIELD(wc, conn_label);
+    }
     for (int i = 0; i < FLOW_N_REGS; i++) {
         if (!flow->regs[i]) {
             WC_UNMASK_FIELD(wc, regs[i]);
@@ -958,7 +969,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards 
*wc,
     memset(&wc->masks, 0x0, sizeof wc->masks);
 
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
     if (flow->tunnel.ip_dst) {
         if (flow->tunnel.flags & FLOW_TNL_F_KEY) {
@@ -984,6 +995,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards 
*wc,
     WC_MASK_FIELD(wc, conn_state);
     WC_MASK_FIELD(wc, conn_zone);
     WC_MASK_FIELD(wc, conn_mark);
+    WC_MASK_FIELD(wc, conn_label);
     WC_MASK_FIELD(wc, recirc_id);
     WC_MASK_FIELD(wc, dp_hash);
     WC_MASK_FIELD(wc, in_port);
@@ -1060,7 +1072,7 @@ uint64_t
 flow_wc_map(const struct flow *flow)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
     uint64_t map = (flow->tunnel.ip_dst) ? MINIFLOW_MAP(tunnel) : 0;
 
@@ -1069,6 +1081,7 @@ flow_wc_map(const struct flow *flow)
         | MINIFLOW_MAP(recirc_id) | MINIFLOW_MAP(dp_hash)
         | MINIFLOW_MAP(in_port) | MINIFLOW_MAP(conn_mark)
         | MINIFLOW_MAP(conn_zone) | MINIFLOW_MAP(conn_state)
+        | MINIFLOW_MAP(conn_label)
         | MINIFLOW_MAP(dl_dst) | MINIFLOW_MAP(dl_src)
         | MINIFLOW_MAP(dl_type) | MINIFLOW_MAP(vlan_tci);
 
@@ -1113,7 +1126,7 @@ void
 flow_wildcards_clear_non_packet_fields(struct flow_wildcards *wc)
 {
     /* Update this function whenever struct flow changes. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
     memset(&wc->masks.metadata, 0, sizeof wc->masks.metadata);
     memset(&wc->masks.regs, 0, sizeof wc->masks.regs);
@@ -1672,7 +1685,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 and dp_hash. */
-        BUILD_ASSERT(FLOW_WC_SEQ == 31);
+        BUILD_ASSERT(FLOW_WC_SEQ == 32);
         memset((char *) flow + FLOW_SEGMENT_2_ENDS_AT, 0,
                sizeof(struct flow) - FLOW_SEGMENT_2_ENDS_AT);
         flow->dp_hash = 0;
diff --git a/lib/flow.h b/lib/flow.h
index 97aa3ca..2b6ea05 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 31
+#define FLOW_WC_SEQ 32
 
 /* Number of Open vSwitch extension 32-bit registers. */
 #define FLOW_N_REGS 8
@@ -110,6 +110,7 @@ struct flow {
     uint16_t conn_zone;         /* Connection Zone. */
     uint8_t conn_state;         /* Connection state. */
     uint8_t pad1[1];            /* Pad to 64 bits. */
+    ovs_u128 conn_label;        /* Connection label. */
     ofp_port_t actset_output;   /* Output port in action set. */
     uint8_t pad2[6];            /* Pad to 64 bits. */
 
@@ -159,8 +160,8 @@ BUILD_ASSERT_DECL(sizeof(struct flow) % sizeof(uint64_t) == 
0);
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
 BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t)
-                  == sizeof(struct flow_tnl) + 200
-                  && FLOW_WC_SEQ == 31);
+                  == sizeof(struct flow_tnl) + 216
+                  && FLOW_WC_SEQ == 32);
 
 /* Incremental points at which flow classification may be performed in
  * segments.
@@ -196,6 +197,7 @@ struct flow_metadata {
     uint32_t regs[FLOW_N_REGS];      /* Registers. */
     uint32_t pkt_mark;               /* Packet mark. */
     ofp_port_t in_port;              /* OpenFlow port or zero. */
+    ovs_u128 conn_label;             /* Connection label. */
     uint32_t conn_mark;              /* Connection mark. */
     uint16_t conn_zone;              /* Connection zone. */
     uint8_t conn_state;              /* Connection state. */
@@ -763,6 +765,7 @@ pkt_metadata_from_flow(struct pkt_metadata *md, const 
struct flow *flow)
     md->conn_state = flow->conn_state;
     md->conn_zone = flow->conn_zone;
     md->conn_mark = flow->conn_mark;
+    md->conn_label = flow->conn_label;
 }
 
 static inline bool is_ip_any(const struct flow *flow)
diff --git a/lib/match.c b/lib/match.c
index 3e8f928..0967f72 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -313,6 +313,26 @@ match_set_conn_mark_masked(struct match *match, uint32_t 
conn_mark,
 }
 
 void
+match_set_conn_label(struct match *match, ovs_u128 conn_label)
+{
+    ovs_u128 mask;
+
+    mask.u64.lo = UINT64_MAX;
+    mask.u64.hi = UINT64_MAX;
+    match_set_conn_label_masked(match, conn_label, mask);
+}
+
+void
+match_set_conn_label_masked(struct match *match, ovs_u128 value,
+                            ovs_u128 mask)
+{
+    match->flow.conn_label.u64.lo = value.u64.lo & mask.u64.lo;
+    match->flow.conn_label.u64.hi = value.u64.hi & mask.u64.hi;
+    match->wc.masks.conn_label.u64.lo = mask.u64.lo;
+    match->wc.masks.conn_label.u64.hi = mask.u64.hi;
+}
+
+void
 match_set_dl_type(struct match *match, ovs_be16 dl_type)
 {
     match->wc.masks.dl_type = OVS_BE16_MAX;
@@ -949,6 +969,19 @@ format_flow_tunnel(struct ds *s, const struct match *match)
     }
 }
 
+static void
+format_conn_label_masked(struct ds *s, const ovs_u128 *key,
+                         const ovs_u128 *mask)
+{
+    if (ovs_u128_nonzero(*mask)) {
+        ds_put_format(s, "conn_label="U128_FMT, U128_ARGS(key));
+        if (!is_all_ones(mask, sizeof(*mask))) {
+            ds_put_format(s, "/"U128_FMT, U128_ARGS(mask));
+        }
+        ds_put_char(s, ',');
+    }
+}
+
 /* Appends a string representation of 'match' to 's'.  If 'priority' is
  * different from OFP_DEFAULT_PRIORITY, includes it in 's'. */
 void
@@ -962,7 +995,7 @@ match_format(const struct match *match, struct ds *s, int 
priority)
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
     if (priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "priority=%d,", priority);
@@ -1018,6 +1051,10 @@ match_format(const struct match *match, struct ds *s, 
int priority)
         format_uint32_masked(s, "conn_mark", f->conn_mark, 
wc->masks.conn_mark);
     }
 
+    if (ovs_u128_nonzero(wc->masks.conn_label)) {
+        format_conn_label_masked(s, &f->conn_label, &wc->masks.conn_label);
+    }
+
     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 b512200..bf2da41 100644
--- a/lib/match.h
+++ b/lib/match.h
@@ -85,6 +85,9 @@ void match_set_conn_zone(struct match *, uint16_t conn_zone);
 void match_set_conn_mark(struct match *, uint32_t conn_mark);
 void match_set_conn_mark_masked(struct match *, uint32_t conn_mark,
                                 uint32_t mask);
+void match_set_conn_label(struct match *, ovs_u128 conn_label);
+void match_set_conn_label_masked(struct match *, ovs_u128 conn_label,
+                                 ovs_u128 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 4fbe18d..8020635 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -138,6 +138,9 @@ mf_is_all_wild(const struct mf_field *mf, const struct 
flow_wildcards *wc)
         return !wc->masks.conn_zone;
     case MFF_CONN_MARK:
         return !wc->masks.conn_mark;
+    case MFF_CONN_LABEL:
+        return is_all_zeros(&wc->masks.conn_label,
+                            sizeof(wc->masks.conn_label));
     CASE_MFF_REGS:
         return !wc->masks.regs[mf->id - MFF_REG0];
     CASE_MFF_XREGS:
@@ -426,6 +429,7 @@ mf_is_value_valid(const struct mf_field *mf, const union 
mf_value *value)
     case MFF_CONN_STATE:
     case MFF_CONN_ZONE:
     case MFF_CONN_MARK:
+    case MFF_CONN_LABEL:
     CASE_MFF_REGS:
     CASE_MFF_XREGS:
     case MFF_ETH_SRC:
@@ -579,6 +583,10 @@ mf_get_value(const struct mf_field *mf, const struct flow 
*flow,
         value->be32 = htonl(flow->conn_mark);
         break;
 
+    case MFF_CONN_LABEL:
+        memcpy(&value->u128, &flow->conn_label, sizeof(flow->conn_label));
+        break;
+
     CASE_MFF_REGS:
         value->be32 = htonl(flow->regs[mf->id - MFF_REG0]);
         break;
@@ -812,6 +820,10 @@ mf_set_value(const struct mf_field *mf,
         match_set_conn_mark(match, ntohl(value->be32));
         break;
 
+    case MFF_CONN_LABEL:
+        match_set_conn_label(match, value->u128);
+        break;
+
     CASE_MFF_REGS:
         match_set_reg(match, mf->id - MFF_REG0, ntohl(value->be32));
         break;
@@ -1056,6 +1068,10 @@ mf_set_flow_value(const struct mf_field *mf,
         flow->conn_mark = ntohl(value->be32);
         break;
 
+    case MFF_CONN_LABEL:
+        memcpy(&flow->conn_label, &value->u128, sizeof(flow->conn_label));
+        break;
+
     CASE_MFF_REGS:
         flow->regs[mf->id - MFF_REG0] = ntohl(value->be32);
         break;
@@ -1336,6 +1352,12 @@ mf_set_wild(const struct mf_field *mf, struct match 
*match)
         match->wc.masks.conn_mark = 0;
         break;
 
+    case MFF_CONN_LABEL:
+        memset(&match->flow.conn_label, 0, sizeof(match->flow.conn_label));
+        memset(&match->wc.masks.conn_label, 0,
+               sizeof(match->wc.masks.conn_label));
+        break;
+
     CASE_MFF_REGS:
         match_set_reg_masked(match, mf->id - MFF_REG0, 0, 0);
         break;
@@ -1595,6 +1617,11 @@ mf_set(const struct mf_field *mf,
                                    ntohl(mask->be32));
         break;
 
+    case MFF_CONN_LABEL:
+        match_set_conn_label_masked(match, value->u128,
+                                    mask->u128);
+        break;
+
     case MFF_ETH_DST:
         match_set_dl_dst_masked(match, value->mac, mask->mac);
         break;
@@ -1790,6 +1817,31 @@ syntax_error:
 }
 
 static char *
+mf_from_u128_string(const struct mf_field *mf, const char *s,
+                    ovs_u128 *valuep, ovs_u128 *maskp)
+{
+    int n;
+
+    ovs_assert(mf->n_bytes == sizeof(*valuep));
+
+    n = -1;
+    if (ovs_scan(s, U128_SCAN_FMT"%n", U128_SCAN_ARGS(valuep), &n)
+        && n == strlen(s)) {
+        memset(maskp, 0xff, sizeof(*maskp));
+        return NULL;
+    }
+
+    n = -1;
+    if (ovs_scan(s, U128_SCAN_FMT"/"U128_SCAN_FMT"%n",
+                 U128_SCAN_ARGS(valuep), U128_SCAN_ARGS(maskp), &n)
+        && n == strlen(s)) {
+        return NULL;
+    }
+
+    return xasprintf("%s: invalid u128 for %s", s, mf->name);
+}
+
+static char *
 mf_from_ethernet_string(const struct mf_field *mf, const char *s,
                         uint8_t mac[ETH_ADDR_LEN],
                         uint8_t mask[ETH_ADDR_LEN])
@@ -2194,6 +2246,11 @@ mf_parse(const struct mf_field *mf, const char *s,
         error = mf_from_conn_state_string(s, &value->u8, &mask->u8);
         break;
 
+    case MFS_CONN_LABEL:
+        ovs_assert(mf->n_bytes == sizeof(ovs_u128));
+        error = mf_from_u128_string(mf, s, &value->u128, &mask->u128);
+        break;
+
     case MFS_ETHERNET:
         error = mf_from_ethernet_string(mf, s, value->mac, mask->mac);
         break;
@@ -2327,6 +2384,15 @@ 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);
 }
 
+static void
+mf_format_conn_label_string(ovs_u128 value, ovs_u128 *mask, struct ds *s)
+{
+    ds_put_format(s, "conn_label="U128_FMT, U128_ARGS(&value));
+    if (mask) {
+        ds_put_format(s, "/"U128_FMT, U128_ARGS(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
@@ -2367,6 +2433,10 @@ mf_format(const struct mf_field *mf,
         mf_format_conn_state_string(value->u8, mask ? mask->u8 : UINT8_MAX, s);
         break;
 
+    case MFS_CONN_LABEL:
+        mf_format_conn_label_string(value->u128, (ovs_u128 *)mask, 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 c70cfaa..7bd1bb1 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -599,6 +599,23 @@ enum OVS_PACKED_ENUM mf_field_id {
      */
     MFF_CONN_MARK,
 
+    /* "conn_label".
+     *
+     * Connection tracking label.  The label is carried with the
+     * connection tracking state.  On Linux this is held in the
+     * conntrack label extension but the exact implementation is
+     * platform-dependent.
+     *
+     * Type: u128.
+     * Maskable: bitwise.
+     * Formatting: conn label.
+     * Prerequisites: none.
+     * Access: read/write.
+     * NXM: NXM_NX_CONN_LABEL(42) since v2.4.
+     * OXM: none.
+     */
+    MFF_CONN_LABEL,
+
 #if FLOW_N_REGS == 8
     /* "reg<N>".
      *
@@ -1540,6 +1557,7 @@ enum OVS_PACKED_ENUM mf_string {
 
     /* Other formats. */
     MFS_CONN_STATE,             /* Conn* state */
+    MFS_CONN_LABEL,             /* Conn* label */
     MFS_ETHERNET,
     MFS_IPV4,
     MFS_IPV6,
@@ -1605,6 +1623,7 @@ union mf_value {
     ovs_be32 be32;
     ovs_be16 be16;
     uint8_t u8;
+    ovs_u128 u128;
 };
 BUILD_ASSERT_DECL(sizeof(union mf_value) == 16);
 
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 2a13c35..ebc7b5f 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -745,6 +745,14 @@ nxm_put_frag(struct ofpbuf *b, const struct match *match,
                nw_frag_mask == FLOW_NW_FRAG_MASK ? UINT8_MAX : nw_frag_mask);
 }
 
+static void
+nxm_put_conn_label(struct ofpbuf *b,
+                   enum mf_field_id field, enum ofp_version version,
+                   const ovs_u128 value, const ovs_u128 mask)
+{
+    nxm_put(b, field, version, &value, &mask, sizeof(value));
+}
+
 /* Appends to 'b' a set of OXM or NXM matches for the IPv4 or IPv6 fields in
  * 'match'.  */
 static void
@@ -865,7 +873,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 == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
     /* Metadata. */
     if (match->wc.masks.dp_hash) {
@@ -1010,6 +1018,10 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const 
struct match *match,
         nxm_put_32m(b, MFF_CONN_MARK, oxm, htonl(flow->conn_mark),
                     htonl(match->wc.masks.conn_mark));
     }
+    if (ovs_u128_nonzero(match->wc.masks.conn_label)) {
+        nxm_put_conn_label(b, MFF_CONN_LABEL, oxm, flow->conn_label,
+                           match->wc.masks.conn_label);
+    }
 
     /* OpenFlow 1.1+ Metadata. */
     nxm_put_64m(b, MFF_METADATA, oxm,
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index a1c5e07..8095777 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -329,6 +329,7 @@ odp_execute_set_action(struct dp_packet *packet, const 
struct nlattr *a)
     case OVS_KEY_ATTR_CONN_STATE:
     case OVS_KEY_ATTR_CONN_ZONE:
     case OVS_KEY_ATTR_CONN_MARK:
+    case OVS_KEY_ATTR_CONN_LABEL:
     case __OVS_KEY_ATTR_MAX:
     default:
         OVS_NOT_REACHED();
@@ -420,6 +421,7 @@ odp_execute_masked_set_action(struct dp_packet *packet,
     case OVS_KEY_ATTR_CONN_STATE:
     case OVS_KEY_ATTR_CONN_ZONE:
     case OVS_KEY_ATTR_CONN_MARK:
+    case OVS_KEY_ATTR_CONN_LABEL:
     case OVS_KEY_ATTR_ENCAP:
     case OVS_KEY_ATTR_ETHERTYPE:
     case OVS_KEY_ATTR_IN_PORT:
@@ -491,7 +493,8 @@ requires_datapath_assistance(const struct nlattr *a)
         enum ovs_key_attr set_type = nl_attr_type(set);
 
         /* Conntrack set_field() actions need to be executed in datapath. */
-        if (set_type == OVS_KEY_ATTR_CONN_MARK) {
+        if (set_type == OVS_KEY_ATTR_CONN_MARK
+            || set_type == OVS_KEY_ATTR_CONN_LABEL) {
             return true;
         }
         return false;
diff --git a/lib/odp-util.c b/lib/odp-util.c
index d24f687..9b5ac59 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -115,6 +115,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char 
*namebuf, size_t bufsize)
     case OVS_KEY_ATTR_CONN_STATE: return "conn_state";
     case OVS_KEY_ATTR_CONN_ZONE: return "conn_zone";
     case OVS_KEY_ATTR_CONN_MARK: return "conn_mark";
+    case OVS_KEY_ATTR_CONN_LABEL: return "conn_label";
     case OVS_KEY_ATTR_TUNNEL: return "tunnel";
     case OVS_KEY_ATTR_IN_PORT: return "in_port";
     case OVS_KEY_ATTR_ETHERNET: return "eth";
@@ -1315,6 +1316,7 @@ odp_flow_key_attr_len(uint16_t type)
     case OVS_KEY_ATTR_CONN_STATE: return 1;
     case OVS_KEY_ATTR_CONN_ZONE: return 2;
     case OVS_KEY_ATTR_CONN_MARK: return 4;
+    case OVS_KEY_ATTR_CONN_LABEL: return 16;
     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);
@@ -1950,6 +1952,13 @@ format_odp_key_attr(const struct nlattr *a, const struct 
nlattr *ma,
         ds_put_format(ds, "%"PRIx16, nl_attr_get_u16(a));
         break;
 
+    case OVS_KEY_ATTR_CONN_LABEL: {
+        const ovs_u128 *mask = ma ? nl_attr_get(ma) : NULL;
+
+        odp_format_u128(ds, nl_attr_get(a), mask, true);
+        break;
+    }
+
     case OVS_KEY_ATTR_TUNNEL: {
         struct flow_tnl key, mask_;
         struct flow_tnl *mask = ma ? &mask_ : NULL;
@@ -2580,9 +2589,9 @@ scan_conn_state(const char *s, uint8_t *key, uint8_t 
*mask)
     n = parse_flags(s, packet_conn_state_to_string, &flags,
                     UINT8_MAX, mask ? &fmask : NULL);
     if (n >= 0) {
-        *key = htons(flags);
+        *key = htonl(flags);
         if (mask) {
-            *mask = htons(fmask);
+            *mask = htonl(fmask);
         }
         return n;
     }
@@ -2898,6 +2907,7 @@ parse_odp_key_mask_attr(const char *s, const struct simap 
*port_names,
     SCAN_SINGLE("conn_state(", uint8_t, conn_state, OVS_KEY_ATTR_CONN_STATE);
     SCAN_SINGLE("conn_zone(", uint16_t, u16, OVS_KEY_ATTR_CONN_ZONE);
     SCAN_SINGLE("conn_mark(", uint32_t, u32, OVS_KEY_ATTR_CONN_MARK);
+    SCAN_SINGLE("conn_label(", ovs_u128, u128, OVS_KEY_ATTR_CONN_LABEL);
 
     SCAN_BEGIN("tunnel(", struct flow_tnl) {
         SCAN_FIELD("tun_id=", be64, tun_id);
@@ -3140,6 +3150,10 @@ odp_flow_key_from_flow__(struct ofpbuf *buf, const 
struct flow *flow,
     if (data->conn_mark) {
         nl_msg_put_u32(buf, OVS_KEY_ATTR_CONN_MARK, data->conn_mark);
     }
+    if (ovs_u128_nonzero(data->conn_label)) {
+        nl_msg_put_unspec(buf, OVS_KEY_ATTR_CONN_LABEL, &data->conn_label,
+                          sizeof(data->conn_label));
+    }
     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);
@@ -3346,6 +3360,10 @@ odp_key_from_pkt_metadata(struct ofpbuf *buf, const 
struct pkt_metadata *md)
     if (md->conn_mark) {
         nl_msg_put_u32(buf, OVS_KEY_ATTR_CONN_MARK, md->conn_mark);
     }
+    if (ovs_u128_nonzero(md->conn_label)) {
+        nl_msg_put_unspec(buf, OVS_KEY_ATTR_CONN_LABEL, &md->conn_label,
+                          sizeof(md->conn_label));
+    }
 
     /* Add an ingress port attribute if 'odp_in_port' is not the magical
      * value "ODPP_NONE". */
@@ -3405,6 +3423,13 @@ odp_key_to_pkt_metadata(const struct nlattr *key, size_t 
key_len,
             md->conn_mark = nl_attr_get_u32(nla);
             wanted_attrs &= ~(1u << OVS_KEY_ATTR_CONN_MARK);
             break;
+        case OVS_KEY_ATTR_CONN_LABEL: {
+            const ovs_u128 *cl = nl_attr_get(nla);
+
+            md->conn_label = *cl;
+            wanted_attrs &= ~(1u << OVS_KEY_ATTR_CONN_LABEL);
+            break;
+        }
         case OVS_KEY_ATTR_TUNNEL: {
             enum odp_key_fitness res;
 
@@ -3968,6 +3993,12 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t 
key_len,
         flow->conn_mark = nl_attr_get_u32(attrs[OVS_KEY_ATTR_CONN_MARK]);
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CONN_MARK;
     }
+    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_CONN_LABEL)) {
+        const ovs_u128 *cl = nl_attr_get(attrs[OVS_KEY_ATTR_CONN_LABEL]);
+
+        flow->conn_label = *cl;
+        expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CONN_LABEL;
+    }
 
     if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TUNNEL)) {
         enum odp_key_fitness res;
@@ -4668,6 +4699,25 @@ commit_set_conn_mark_action(const struct flow *flow, 
struct flow *base_flow,
     }
 }
 
+static void
+commit_set_conn_label_action(const struct flow *flow, struct flow *base_flow,
+                             struct ofpbuf *odp_actions,
+                             struct flow_wildcards *wc,
+                             bool use_masked)
+{
+    ovs_u128 key, mask, base;
+
+    key = flow->conn_label;
+    base = base_flow->conn_label;
+    mask = wc->masks.conn_label;
+
+    if (commit(OVS_KEY_ATTR_CONN_LABEL, use_masked, &key, &base, &mask,
+               sizeof key, odp_actions)) {
+        base_flow->conn_label = base;
+        wc->masks.conn_label = mask;
+    }
+}
+
 /* If any of the flow key data that ODP actions can modify are different in
  * 'base' and 'flow', appends ODP actions to 'odp_actions' that change the flow
  * key from 'base' into 'flow', and then changes 'base' the same way.  Does not
@@ -4692,6 +4742,7 @@ commit_odp_actions(const struct flow *flow, struct flow 
*base,
     commit_set_priority_action(flow, base, odp_actions, wc, use_masked);
     commit_set_pkt_mark_action(flow, base, odp_actions, wc, use_masked);
     commit_set_conn_mark_action(flow, base, odp_actions, wc, use_masked);
+    commit_set_conn_label_action(flow, base, odp_actions, wc, use_masked);
 
     return slow;
 }
diff --git a/lib/odp-util.h b/lib/odp-util.h
index da70fc4..f8c532f 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -123,6 +123,7 @@ void odp_portno_names_destroy(struct hmap *portno_names);
  *  OVS_KEY_ATTR_CONN_STATE              1     3     4      8
  *  OVS_KEY_ATTR_CONN_ZONE               2     2     4      8
  *  OVS_KEY_ATTR_CONN_MARK               4    --     4      8
+ *  OVS_KEY_ATTR_CONN_LABEL             16    --     4     20
  *  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
@@ -132,13 +133,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                                                 512
+ *  total                                                 532
  *
  * 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 == 31);
+#define ODPUTIL_FLOW_KEY_BYTES 576
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
 /* 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 2d2d7e6..a6ad547 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -4374,7 +4374,9 @@ format_SAMPLE(const struct ofpact_sample *a, struct ds *s)
  *
  * 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[_W] and NXM_NX_CONN_MARK[_W] matches set.
+ * with the NXM_NX_CONN_STATE[_W], NXM_NX_CONN_MARK[_W] and
+ * NXM_NX_CONN_LABEL[_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. */
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 88be31c..feaa153 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -168,6 +168,9 @@ ofp_print_packet_in(struct ds *string, const struct 
ofp_header *oh,
     if (pin.fmd.conn_mark != 0) {
         ds_put_format(string, " conn_mark=0x%"PRIx32, pin.fmd.conn_mark);
     }
+    if (ovs_u128_nonzero(pin.fmd.conn_label)) {
+        ds_put_format(string, " conn_label="U128_FMT, 
U128_ARGS(&pin.fmd.conn_label));
+    }
 
     ds_put_format(string, " (via %s)",
                   ofputil_packet_in_reason_to_string(pin.reason, reasonbuf,
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index f1b134f..bf793db 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -195,7 +195,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 == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
     /* Initialize most of wc. */
     flow_wildcards_init_catchall(wc);
@@ -3314,6 +3314,7 @@ ofputil_decode_packet_in_finish(struct ofputil_packet_in 
*pin,
     pin->fmd.conn_state = match->flow.conn_state;
     pin->fmd.conn_zone = match->flow.conn_zone;
     pin->fmd.conn_mark = match->flow.conn_mark;
+    pin->fmd.conn_label = match->flow.conn_label;
 }
 
 enum ofperr
@@ -3467,6 +3468,10 @@ ofputil_packet_in_to_match(const struct 
ofputil_packet_in *pin,
         match_set_conn_mark(match, pin->fmd.conn_mark);
     }
 
+    if (ovs_u128_nonzero(pin->fmd.conn_label)) {
+        match_set_conn_label(match, pin->fmd.conn_label);
+    }
+
     match_set_in_port(match, pin->fmd.in_port);
 }
 
diff --git a/lib/packets.h b/lib/packets.h
index c769643..bfd2496 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -68,6 +68,7 @@ struct pkt_metadata {
     uint8_t conn_state;         /* Connection state. */
     uint16_t conn_zone;         /* Connection zone. */
     uint32_t conn_mark;         /* Connection mark. */
+    ovs_u128 conn_label;        /* Connection label. */
 };
 
 #define PKT_METADATA_INITIALIZER(PORT) \
diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h
index 81a61a2..dc533ce 100644
--- a/ofproto/ofproto-dpif-rid.h
+++ b/ofproto/ofproto-dpif-rid.h
@@ -91,7 +91,7 @@ struct rule;
 /* Metadata for restoring pipeline context after recirculation.  Helpers
  * are inlined below to keep them together with the definition for easier
  * updates. */
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 31);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
 
 struct recirc_metadata {
     /* Metadata in struct flow. */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index b514b89..bec1c64 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -2725,6 +2725,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t 
ofp_port,
     uint32_t flow_pkt_mark, flow_conn_mark;
     uint8_t flow_conn_state;
     uint16_t flow_conn_zone;
+    ovs_u128 flow_conn_label;
     uint8_t flow_nw_tos;
     odp_port_t out_port, odp_port;
     bool tnl_push_pop_send = false;
@@ -2732,7 +2733,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t 
ofp_port,
 
     /* If 'struct flow' gets additional metadata, we'll need to zero it out
      * before traversing a patch port. */
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 31);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 32);
     memset(&flow_tnl, 0, sizeof flow_tnl);
 
     if (!xport) {
@@ -2870,6 +2871,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t 
ofp_port,
     flow_pkt_mark = flow->pkt_mark;
     flow_conn_state = flow->conn_state;
     flow_conn_zone = flow->conn_zone;
+    flow_conn_label = flow->conn_label;
     flow_nw_tos = flow->nw_tos;
     flow_conn_mark = flow->conn_mark;
 
@@ -2993,6 +2995,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t 
ofp_port,
     flow->conn_state = flow_conn_state;
     flow->conn_zone = flow_conn_zone;
     flow->conn_mark = flow_conn_mark;
+    flow->conn_label = flow_conn_label;
     flow->nw_tos = flow_nw_tos;
 }
 
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 718ddfa..1e1e881 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1199,9 +1199,9 @@ check_masked_set_action(struct dpif_backer *backer)
     return !error;
 }
 
-#define CHECK_FEATURE(FIELD)                                                \
+#define CHECK_FEATURE__(NAME, FIELD)                                        \
 static bool                                                                 \
-check_##FIELD(struct dpif_backer *backer)                                   \
+check_##NAME(struct dpif_backer *backer)                                    \
 {                                                                           \
     struct flow flow;                                                       \
     struct odputil_keybuf keybuf;                                           \
@@ -1213,23 +1213,26 @@ check_##FIELD(struct dpif_backer *backer)               
                    \
                                                                             \
     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);          \
+    enable = dpif_probe_feature(backer->dpif, #NAME, &key, NULL);           \
                                                                             \
     if (enable) {                                                           \
-        VLOG_INFO("%s: Datapath supports "#FIELD, dpif_name(backer->dpif)); \
+        VLOG_INFO("%s: Datapath supports "#NAME, dpif_name(backer->dpif));  \
     } else {                                                                \
-        VLOG_INFO("%s: Datapath does not support "#FIELD,                   \
+        VLOG_INFO("%s: Datapath does not support "#NAME,                    \
                   dpif_name(backer->dpif));                                 \
     }                                                                       \
                                                                             \
     return enable;                                                          \
 }
+#define CHECK_FEATURE(FIELD) CHECK_FEATURE__(FIELD, FIELD)
 
 CHECK_FEATURE(conn_state)
 CHECK_FEATURE(conn_zone)
 CHECK_FEATURE(conn_mark)
+CHECK_FEATURE__(conn_label, conn_label.u64.lo)
 
 #undef CHECK_FEATURE
+#undef CHECK_FEATURE__
 
 static void
 check_support(struct dpif_backer *backer)
@@ -1245,6 +1248,7 @@ check_support(struct dpif_backer *backer)
     backer->support.conn_state = check_conn_state(backer);
     backer->support.conn_zone = check_conn_zone(backer);
     backer->support.conn_mark = check_conn_mark(backer);
+    backer->support.conn_label = check_conn_label(backer);
 }
 
 static int
@@ -3934,7 +3938,9 @@ rule_check(struct rule *rule)
 
     if ((match.wc.masks.conn_state && !ofproto->backer->support.conn_state)
         || (match.wc.masks.conn_zone && !ofproto->backer->support.conn_zone)
-        || (match.wc.masks.conn_mark && !ofproto->backer->support.conn_mark)) {
+        || (match.wc.masks.conn_mark && !ofproto->backer->support.conn_mark)
+        || (ovs_u128_nonzero(match.wc.masks.conn_label)
+            && !ofproto->backer->support.conn_label)) {
         return OFPERR_OFPBMC_BAD_FIELD;
     }
     if (match.wc.masks.conn_state & CS_UNSUPPORTED_MASK) {
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 5738d79..ed41f45 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -92,6 +92,7 @@ struct dpif_backer_support {
     bool conn_state;
     bool conn_zone;
     bool conn_mark;
+    bool conn_label;
 };
 
 size_t ofproto_dpif_get_max_mpls_depth(const struct ofproto_dpif *);
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 65086db..be14a49 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,conn_state=0,conn_zone=0,conn_mark=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,conn_zone=0,conn_mark=0,conn_label=00000000000000000000000000000000,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-traffic.at b/tests/kmod-traffic.at
index bff40c9..6c1737c 100644
--- a/tests/kmod-traffic.at
+++ b/tests/kmod-traffic.at
@@ -360,3 +360,42 @@ SYN_RECV src=10.1.1.3 dst=10.1.1.4 sport=<cleared> 
dport=<cleared> src=10.1.1.4
 
 OVS_KMOD_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([conntrack - conn_label])
+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(commit),set_field:000000000000000001->conn_label,2
+in_port=2,conn_state=-trk,tcp,action=ct(recirc)
+in_port=2,conn_state=+trk,conn_label=000000000000000001,tcp,action=1
+in_port=3,tcp,action=ct(commit),set_field:000000000000000002->conn_label,4
+in_port=4,conn_state=-trk,tcp,action=ct(recirc)
+in_port=4,conn_state=+trk,conn_label=000000000000000001,tcp,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 fail due to network failure.
+dnl Try 5 times, in 1 second intervals.
+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 -v -o wget1.log], [4])
+
+OVS_KMOD_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 112ed25..e788ce1 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,conn_state=0,conn_zone=0,conn_mark=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,conn_zone=0,conn_mark=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,conn_zone=0,conn_mark=0,conn_label=00000000000000000000000000000000,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,conn_zone=0,conn_mark=0,conn_label=00000000000000000000000000000000,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 7ddb4f4..2d12bcc 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1497,7 +1497,7 @@ OVS_VSWITCHD_START
       instructions: 
meter,apply_actions,clear_actions,write_actions,write_metadata$goto
       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 tun_gbp_id 
tun_gbp_flags metadata in_port in_port_oxm pkt_mark conn_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 ipv6_label 
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 nd_target nd_sll nd_tll
+        supported on Set-Field: tun_id tun_src tun_dst tun_gbp_id 
tun_gbp_flags metadata in_port in_port_oxm pkt_mark conn_mark conn_label 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 
ipv6_label 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 nd_target nd_sll nd_tll
     matching:
       dp_hash: arbitrary mask
       recirc_id: exact match or wildcard
@@ -1515,6 +1515,7 @@ OVS_VSWITCHD_START
       conn_state: arbitrary mask
       conn_zone: exact match or wildcard
       conn_mark: arbitrary mask
+      conn_label: arbitrary mask
       reg0: arbitrary mask
       reg1: arbitrary mask
       reg2: arbitrary mask
@@ -1584,7 +1585,7 @@ AT_CHECK(
 # Check that the configuration was updated.
 mv expout orig-expout
 sed 's/classifier/main/
-80s/1000000/1024/' < orig-expout > expout
+81s/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 2fc2532..0c6dc11 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1218,16 +1218,21 @@ This is part of an already existing connection.
 This is a new connection that is related to an existing connection.
 .RE
 .
+.PP
+The following fields are data associated with the connection tracker and
+can only be matched or set after running through the connection tracker
+by using the \fBct\fR action.
+.
 .IP \fBconn_zone=\fIvalue
-Matches connection zone \fIvalue\fR exactly.  The zone is associated data with
-the connection tracker and can only be matched after running through the
-connection tracker through the \fBct\fR action.
+Matches connection zone \fIvalue\fR exactly.
 .
 .IP \fBconn_mark=\fIvalue\fR[\fB/\fImask\fR]
 Matches connection mark \fIvalue\fR either exactly or with optional
-\fImask\fR.  The mark is associated data with the connection tracker and
-can only be matched or set after running through the connection tracker
-through the \fBct\fR action.
+\fImask\fR.
+.
+.IP \fBconn_label=\fIvalue\fR[\fB/\fImask\fR]
+Matches connection label \fIvalue\fR either exactly or with optional
+\fImask\fR.
 .
 .PP
 Defining IPv6 flows (those with \fBdl_type\fR equal to 0x86dd) requires
-- 
1.7.10.4

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to