[I did not receive this email either.]

With the comments below,

Acked-by: Jarno Rajahalme <jrajaha...@nicira.com>

> This patch adds a new 128-bit metadata field to the connection tracking
> interface. When a label is specified as part of the ct action and the
> connection is committed, the value is saved with the current connection.
Saving seems to happen also with non-commit ct actions.
> Subsequent ct lookups with the table specified will expose this metadata
> as the "ct_label" field in the flow.
> 
> For example, to allow new connections from port 1->2 and only allow
> established connections from port 2->1, and to associate a label with
> those connections:
> 
>     priority=1,action=drop
>     priority=10,arp,action=normal
>     priority=10,icmp,action=normal
>     in_port=1,tcp,action=ct(commit,exec(set_field:1->ct_label)),2
>     in_port=2,ct_state=-trk,tcp,action=ct(table=1)
>     table=1,in_port=2,ct_state=+trk,ct_label=1,tcp,action=1
> 
> Signed-off-by: Joe Stringer <joestrin...@nicira.com>
> ---
>  NEWS                                              |   4 +-
>  build-aux/extract-ofp-fields                      |   2 +
>  datapath/flow_netlink.c                           |   2 +-
>  datapath/linux/compat/include/linux/openvswitch.h |  10 ++
>  lib/dpif-netdev.c                                 |   3 +-
>  lib/flow.c                                        |  16 +++
>  lib/flow.h                                        |   4 +-
>  lib/match.c                                       |  37 +++++++
>  lib/match.h                                       |   2 +
>  lib/meta-flow.c                                   |  71 ++++++++++++++
>  lib/meta-flow.h                                   |  19 ++++
>  lib/nx-match.c                                    |  13 +++
>  lib/odp-execute.c                                 |   2 +
>  lib/odp-util.c                                    | 114 
> +++++++++++++++++++++-
>  lib/odp-util.h                                    |   4 +-
>  lib/ofp-actions.c                                 |  11 ++-
>  lib/packets.h                                     |   1 +
>  ofproto/ofproto-dpif-sflow.c                      |   1 +
>  ofproto/ofproto-dpif-xlate.c                      |  31 ++++++
>  ofproto/ofproto-dpif.c                            |   7 +-
>  ofproto/ofproto-unixctl.man                       |   2 +
>  tests/dpif-netdev.at                              |   2 +-
>  tests/odp.at                                      |   6 +-
>  tests/ofproto-dpif.at                             |   4 +-
>  tests/ofproto.at                                  |   3 +-
>  tests/system-traffic.at                           |  39 ++++++++
>  tests/test-odp.c                                  |   1 +
>  utilities/ovs-ofctl.8.in                          |   8 ++
>  28 files changed, 401 insertions(+), 18 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index 6eeccdc..7045d72 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -22,8 +22,8 @@ Post-v2.4.0
>       a Vagrant box.  See INSTALL.md for details
>     - Dropped support for GRE64 tunnel.
>     - Add support for connection tracking through the new "ct" action
> -     and "ct_state"/"ct_zone"/"ct_mark" match fields.  Only available on
> -     Linux kernels with the connection tracking module loaded.
> +     and "ct_state"/"ct_zone"/"ct_mark"/"ct_label" match fields.  Only
> +     available on Linux kernels with the connection tracking module loaded.
>  
>  
>  v2.4.0 - 20 Aug 2015
> diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
> index b0d9681..ecd535e 100755
> --- a/build-aux/extract-ofp-fields
> +++ b/build-aux/extract-ofp-fields
> @@ -20,11 +20,13 @@ TYPES = {"u8":       (1,   False),
>           "MAC":      (6,   False),
>           "be64":     (8,   False),
>           "IPv6":     (16,  False),
> +         "u128":     (16,  False),
>           "tunnelMD": (124, True)}
>  
>  FORMATTING = {"decimal":            ("MFS_DECIMAL",      1,   8),
>                "hexadecimal":        ("MFS_HEXADECIMAL",  1, 127),
>                "conn state":         ("MFS_CT_STATE",     1,   1),
> +              "conn label":         ("MFS_CT_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 352fc87..780dda4 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 5c7b2af..39a321e 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_CT_STATE,  /* u8 bitmask of OVS_CS_F_* */
>       OVS_KEY_ATTR_CT_ZONE,   /* u16 connection tracking zone. */
>       OVS_KEY_ATTR_CT_MARK,   /* u32 connection tracking mark */
> +     OVS_KEY_ATTR_CT_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_ct_label {
> +     __u8    ct_label[OVS_CT_LABEL_LEN];
> +};
> +
This would be more efficient if defined as __u32 ct_label[4].
>  /* OVS_KEY_ATTR_CT_STATE flags */
>  #define OVS_CS_F_NEW               0x01 /* Beginning of a new connection. */
>  #define OVS_CS_F_ESTABLISHED       0x02 /* Part of an existing connection. */
> @@ -660,12 +666,16 @@ struct ovs_action_push_tnl {
>   * @OVS_CT_ATTR_MARK: u32 value followed by u32 mask. For each bit set in the
>   * mask, the corresponding bit in the value is copied to the connection
>   * tracking mark field in the connection.
> + * @OVS_CT_ATTR_LABEL: %OVS_CT_LABEL_LEN value followed by %OVS_CT_LABEL_LEN
> + * mask. For each bit set in the mask, the corresponding bit in the value is
> + * copied to the connection tracking label field in the connection.
>   */
>  enum ovs_ct_attr {
>       OVS_CT_ATTR_UNSPEC,
>       OVS_CT_ATTR_FLAGS,      /* u32 bitmask of OVS_CT_F_*. */
>       OVS_CT_ATTR_ZONE,       /* u16 zone id. */
>       OVS_CT_ATTR_MARK,       /* mark to associate with this connection. */
> +     OVS_CT_ATTR_LABEL,      /* label to associate with this connection. */

Nit about the terminology: conntrack treats each bit as a label, so the 
collection of them would be “labels”?
>       __OVS_CT_ATTR_MAX
>  };
>  
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 8a183c0..666bdb8 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -1921,7 +1921,8 @@ dpif_netdev_flow_from_nlattrs(const struct nlattr *key, 
> uint32_t key_len,
>      }
>  
>      /* Userspace datapath doesn't support conntrack. */
> -    if (flow->ct_state || flow->ct_zone || flow->ct_mark) {
> +    if (flow->ct_state || flow->ct_zone || flow->ct_mark
> +        || !is_all_zeros(&flow->ct_label, sizeof(flow->ct_label))) {

Could this use ovs_u128_is_zero() function introduced in the previous patch?
>          return EINVAL;
>      }
>  
> diff --git a/lib/flow.c b/lib/flow.c
> index 3ae1432..fbd7c60 100644
> --- a/lib/flow.c
> +++ b/lib/flow.c
> @@ -267,6 +267,9 @@ BUILD_MESSAGE("FLOW_WC_SEQ changed: miniflow_extract() 
> will have runtime "
>      MF.data += 1;                   /* First word only. */      \
>  }
>  
> +#define miniflow_push_uint64(MF, FIELD, VALUE)                      \
> +    miniflow_push_uint64_(MF, offsetof(struct flow, FIELD), VALUE)
> +

Apparently this macro is not used, so it could be removed?
>  #define miniflow_push_uint32(MF, FIELD, VALUE)                      \
>      miniflow_push_uint32_(MF, offsetof(struct flow, FIELD), VALUE)
>  
> @@ -507,6 +510,11 @@ miniflow_extract(struct dp_packet *packet, struct 
> miniflow *dst)
>          miniflow_push_uint16(mf, ct_zone, md->ct_zone);
>          miniflow_push_uint8(mf, ct_state, md->ct_state);
>          miniflow_pad_to_64(mf, pad1);
> +
> +        if (!ovs_u128_is_zero(&md->ct_label)) {
> +            miniflow_push_words(mf, ct_label, &md->ct_label,
> +                                sizeof md->ct_label / 8);
Would be nice to use sizeof(uint64_t) instead of “8”.
> +        }
>      }
>  
>      /* Initialize packet's layer pointer and offsets. */
> @@ -868,6 +876,9 @@ flow_get_metadata(const struct flow *flow, struct match 
> *flow_metadata)
>      if (flow->ct_mark != 0) {
>          match_set_ct_mark(flow_metadata, flow->ct_mark);
>      }
> +    if (!is_all_zeros(&flow->ct_label, sizeof(flow->ct_label))) {
> +        match_set_ct_label(flow_metadata, flow->ct_label);
> +    }

I think we should use the u128 in struct flow, so all the is_all_zeros() calls 
introduced here could be replaced with u128 equivalent.
>  }
>  
>  char *
> @@ -1152,6 +1163,9 @@ flow_format(struct ds *ds, const struct flow *flow)
>      if (!flow->ct_mark) {
>          WC_UNMASK_FIELD(wc, ct_mark);
>      }
> +    if (is_all_zeros(&flow->ct_label, sizeof(flow->ct_label))) {
> +        WC_UNMASK_FIELD(wc, ct_label);
> +    }
>      for (int i = 0; i < FLOW_N_REGS; i++) {
>          if (!flow->regs[i]) {
>              WC_UNMASK_FIELD(wc, regs[i]);
> @@ -1229,6 +1243,7 @@ void flow_wildcards_init_for_packet(struct 
> flow_wildcards *wc,
>      WC_MASK_FIELD(wc, ct_state);
>      WC_MASK_FIELD(wc, ct_zone);
>      WC_MASK_FIELD(wc, ct_mark);
> +    WC_MASK_FIELD(wc, ct_label);
>      WC_MASK_FIELD(wc, recirc_id);
>      WC_MASK_FIELD(wc, dp_hash);
>      WC_MASK_FIELD(wc, in_port);
> @@ -1335,6 +1350,7 @@ flow_wc_map(const struct flow *flow, struct flowmap 
> *map)
>      FLOWMAP_SET(map, ct_state);
>      FLOWMAP_SET(map, ct_zone);
>      FLOWMAP_SET(map, ct_mark);
> +    FLOWMAP_SET(map, ct_label);
>  
>      /* Ethertype-dependent fields. */
>      if (OVS_LIKELY(flow->dl_type == htons(ETH_TYPE_IP))) {
> diff --git a/lib/flow.h b/lib/flow.h
> index b5edaa0..92d1ee7 100644
> --- a/lib/flow.h
> +++ b/lib/flow.h
> @@ -107,6 +107,7 @@ struct flow {
>      uint16_t ct_zone;           /* Connection Zone. */
>      uint8_t ct_state;           /* Connection state. */
>      uint8_t pad1[1];            /* Pad to 64 bits. */
> +    ovs_u128 ct_label;        /* Connection label. */

Oh, we do already :-)
>      ofp_port_t actset_output;   /* Output port in action set. */
>      uint8_t pad2[6];            /* Pad to 64 bits. */
>  
> @@ -157,7 +158,7 @@ BUILD_ASSERT_DECL(sizeof(struct flow_tnl) % 
> 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
> +                  == sizeof(struct flow_tnl) + 216
>                    && FLOW_WC_SEQ == 33);
>  
>  /* Incremental points at which flow classification may be performed in
> @@ -987,6 +988,7 @@ pkt_metadata_from_flow(struct pkt_metadata *md, const 
> struct flow *flow)
>      md->ct_state = flow->ct_state;
>      md->ct_zone = flow->ct_zone;
>      md->ct_mark = flow->ct_mark;
> +    md->ct_label = flow->ct_label;
>  }
>  
>  static inline bool is_ip_any(const struct flow *flow)
> diff --git a/lib/match.c b/lib/match.c
> index 7e0d7dd..9ca82ba 100644
> --- a/lib/match.c
> +++ b/lib/match.c
> @@ -319,6 +319,25 @@ match_set_ct_mark_masked(struct match *match, uint32_t 
> ct_mark,
>  }
>  
>  void
> +match_set_ct_label(struct match *match, ovs_u128 ct_label)
> +{
> +    ovs_u128 mask;
> +
> +    mask.u64.lo = UINT64_MAX;
> +    mask.u64.hi = UINT64_MAX;
> +    match_set_ct_label_masked(match, ct_label, mask);
> +}
> +
> +void
> +match_set_ct_label_masked(struct match *match, ovs_u128 value, ovs_u128 mask)
> +{
> +    match->flow.ct_label.u64.lo = value.u64.lo & mask.u64.lo;
> +    match->flow.ct_label.u64.hi = value.u64.hi & mask.u64.hi;
> +    match->wc.masks.ct_label.u64.lo = mask.u64.lo;
> +    match->wc.masks.ct_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;
> @@ -957,6 +976,20 @@ format_flow_tunnel(struct ds *s, const struct match 
> *match)
>      tun_metadata_match_format(s, match);
>  }
>  
> +static void
> +format_ct_label_masked(struct ds *s, const ovs_u128 *key, const ovs_u128 
> *mask)
> +{
> +    if (!is_all_zeros(mask, sizeof(*mask))) {
> +        ds_put_format(s, "ct_label=");
> +        ds_put_hex(s, key, sizeof(*key));
> +        if (!is_all_ones(mask, sizeof(*mask))) {
Here as well.
> +            ds_put_char(s, '/');
> +            ds_put_hex(s, mask, sizeof(*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
> @@ -1026,6 +1059,10 @@ match_format(const struct match *match, struct ds *s, 
> int priority)
>          format_uint32_masked(s, "ct_mark", f->ct_mark, wc->masks.ct_mark);
>      }
>  
> +    if (!is_all_zeros(&wc->masks.ct_label, sizeof(wc->masks.ct_label))) {
Ditto.
> +        format_ct_label_masked(s, &f->ct_label, &wc->masks.ct_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 5d606ae..cf23e65 100644
> --- a/lib/match.h
> +++ b/lib/match.h
> @@ -88,6 +88,8 @@ void match_set_ct_state_masked(struct match *, uint8_t 
> ct_state, uint8_t mask);
>  void match_set_ct_zone(struct match *, uint16_t ct_zone);
>  void match_set_ct_mark(struct match *, uint32_t ct_mark);
>  void match_set_ct_mark_masked(struct match *, uint32_t ct_mark, uint32_t 
> mask);
> +void match_set_ct_label(struct match *, ovs_u128 ct_label);
> +void match_set_ct_label_masked(struct match *, ovs_u128 ct_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 struct eth_addr );
> diff --git a/lib/meta-flow.c b/lib/meta-flow.c
> index 444c2e4..27d2060 100644
> --- a/lib/meta-flow.c
> +++ b/lib/meta-flow.c
> @@ -220,6 +220,8 @@ mf_is_all_wild(const struct mf_field *mf, const struct 
> flow_wildcards *wc)
>          return !wc->masks.ct_zone;
>      case MFF_CT_MARK:
>          return !wc->masks.ct_mark;
> +    case MFF_CT_LABEL:
> +        return is_all_zeros(&wc->masks.ct_label, sizeof(wc->masks.ct_label));

Ditto.
>      CASE_MFF_REGS:
>          return !wc->masks.regs[mf->id - MFF_REG0];
>      CASE_MFF_XREGS:
> @@ -506,6 +508,7 @@ mf_is_value_valid(const struct mf_field *mf, const union 
> mf_value *value)
>      case MFF_CT_STATE:
>      case MFF_CT_ZONE:
>      case MFF_CT_MARK:
> +    case MFF_CT_LABEL:
>      CASE_MFF_REGS:
>      CASE_MFF_XREGS:
>      case MFF_ETH_SRC:
> @@ -665,6 +668,10 @@ mf_get_value(const struct mf_field *mf, const struct 
> flow *flow,
>          value->be32 = htonl(flow->ct_mark);
>          break;
>  
> +    case MFF_CT_LABEL:
> +        memcpy(&value->u128, &flow->ct_label, sizeof(flow->ct_label));
Would an assignment work here?
> +        break;
> +
>      CASE_MFF_REGS:
>          value->be32 = htonl(flow->regs[mf->id - MFF_REG0]);
>          break;
> @@ -909,6 +916,10 @@ mf_set_value(const struct mf_field *mf,
>          match_set_ct_mark(match, ntohl(value->be32));
>          break;
>  
> +    case MFF_CT_LABEL:
> +        match_set_ct_label(match, value->u128);
> +        break;
> +
>      CASE_MFF_REGS:
>          match_set_reg(match, mf->id - MFF_REG0, ntohl(value->be32));
>          break;
> @@ -1205,6 +1216,10 @@ mf_set_flow_value(const struct mf_field *mf,
>          flow->ct_mark = ntohl(value->be32);
>          break;
>  
> +    case MFF_CT_LABEL:
> +        memcpy(&flow->ct_label, &value->u128, sizeof(flow->ct_label));

And here?
> +        break;
> +
>      CASE_MFF_REGS:
>          flow->regs[mf->id - MFF_REG0] = ntohl(value->be32);
>          break;
> @@ -1509,6 +1524,11 @@ mf_set_wild(const struct mf_field *mf, struct match 
> *match, char **err_str)
>          match->wc.masks.ct_mark = 0;
>          break;
>  
> +    case MFF_CT_LABEL:
> +        memset(&match->flow.ct_label, 0, sizeof(match->flow.ct_label));
> +        memset(&match->wc.masks.ct_label, 0, 
> sizeof(match->wc.masks.ct_label));
> +        break;
> +
>      CASE_MFF_REGS:
>          match_set_reg_masked(match, mf->id - MFF_REG0, 0, 0);
>          break;
> @@ -1780,6 +1800,10 @@ mf_set(const struct mf_field *mf,
>          match_set_ct_mark_masked(match, ntohl(value->be32), 
> ntohl(mask->be32));
>          break;
>  
> +    case MFF_CT_LABEL:
> +        match_set_ct_label_masked(match, value->u128, mask->u128);
> +        break;
> +
>      case MFF_ETH_DST:
>          match_set_dl_dst_masked(match, value->mac, mask->mac);
>          break;
> @@ -1971,6 +1995,32 @@ mf_from_integer_string(const struct mf_field *mf, 
> const char *s,
>  }
>  
>  static char *
> +mf_from_u128_string(const struct mf_field *mf, const char *s_,
> +                    ovs_u128 *valuep, ovs_u128 *maskp)
> +{
> +    char *s = CONST_CAST(char *, s_);
> +
> +    ovs_assert(mf->n_bytes == sizeof(*valuep));
> +
> +    if (!parse_int_string(s, (uint8_t *)valuep, sizeof(*valuep), &s)) {
> +        if (strlen(s)) {
> +            if (*s == '/'
> +                && !parse_int_string(s + 1, (uint8_t *)maskp, sizeof(*maskp),
> +                                     &s)) {
> +                return NULL;
> +            } else {
> +                /* parse error */
> +            }
> +        } else {
> +            memset(maskp, 0xff, sizeof(*maskp));
> +            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,
>                          struct eth_addr *mac, struct eth_addr *mask)
>  {
> @@ -2214,6 +2264,11 @@ mf_parse(const struct mf_field *mf, const char *s,
>          error = mf_from_ct_state_string(s, &value->u8, &mask->u8);
>          break;
>  
> +    case MFS_CT_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;
> @@ -2341,6 +2396,18 @@ mf_format_ct_state_string(uint8_t value, uint8_t mask, 
> struct ds *s)
>                          UINT8_MAX);
>  }
>  
> +static void
> +mf_format_ct_label_string(const ovs_u128 *value, const ovs_u128 *mask,
> +                            struct ds *s)
> +{
> +    ds_put_format(s, "ct_label=");
> +    ds_put_hex(s, value, sizeof(*value));
> +    if (mask) {
> +        ds_put_char(s, '/');
> +        ds_put_hex(s, mask, sizeof(*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
> @@ -2381,6 +2448,10 @@ mf_format(const struct mf_field *mf,
>          mf_format_ct_state_string(value->u8, mask ? mask->u8 : UINT8_MAX, s);
>          break;
>  
> +    case MFS_CT_LABEL:
> +        mf_format_ct_label_string(&value->u128, (ovs_u128 *)mask, s);
> +        break;
> +
>      case MFS_ETHERNET:
>          eth_format_masked(value->mac, mask ? &mask->mac : NULL, s);
>          break;
> diff --git a/lib/meta-flow.h b/lib/meta-flow.h
> index 576f4d7..2f22337 100644
> --- a/lib/meta-flow.h
> +++ b/lib/meta-flow.h
> @@ -765,6 +765,23 @@ enum OVS_PACKED_ENUM mf_field_id {
>       */
>      MFF_CT_MARK,
>  
> +    /* "ct_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.

Mention writability restrictions in the comment above?
> +     * NXM: NXM_NX_CT_LABEL(108) since v2.5.
> +     * OXM: none.
> +     */
> +    MFF_CT_LABEL,
> +
>  #if FLOW_N_REGS == 8
>      /* "reg<N>".
>       *
> @@ -1742,6 +1759,7 @@ enum OVS_PACKED_ENUM mf_string {
>  
>      /* Other formats. */
>      MFS_CT_STATE,               /* Connection tracking state */
> +    MFS_CT_LABEL,               /* Connection tracking label */
>      MFS_ETHERNET,
>      MFS_IPV4,
>      MFS_IPV6,
> @@ -1809,6 +1827,7 @@ union mf_value {
>      ovs_be32 be32;
>      ovs_be16 be16;
>      uint8_t u8;
> +    ovs_u128 u128;
>  };
>  BUILD_ASSERT_DECL(sizeof(union mf_value) == 128);
>  BUILD_ASSERT_DECL(sizeof(union mf_value) >= GENEVE_MAX_OPT_SIZE);
> diff --git a/lib/nx-match.c b/lib/nx-match.c
> index c298a6f..1d34d6a 100644
> --- a/lib/nx-match.c
> +++ b/lib/nx-match.c
> @@ -781,6 +781,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_ct_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
> @@ -1049,6 +1057,11 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, 
> const struct match *match,
>          nxm_put_32m(b, MFF_CT_MARK, oxm, htonl(flow->ct_mark),
>                      htonl(match->wc.masks.ct_mark));
>      }
> +    if (!is_all_zeros(&match->wc.masks.ct_label,
Ditto.
> +                      sizeof(match->wc.masks.ct_label))) {
> +        nxm_put_ct_label(b, MFF_CT_LABEL, oxm, flow->ct_label,
> +                         match->wc.masks.ct_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 4d6384e..209512b 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_CT_STATE:
>      case OVS_KEY_ATTR_CT_ZONE:
>      case OVS_KEY_ATTR_CT_MARK:
> +    case OVS_KEY_ATTR_CT_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_CT_STATE:
>      case OVS_KEY_ATTR_CT_ZONE:
>      case OVS_KEY_ATTR_CT_MARK:
> +    case OVS_KEY_ATTR_CT_LABEL:
>      case OVS_KEY_ATTR_ENCAP:
>      case OVS_KEY_ATTR_ETHERTYPE:
>      case OVS_KEY_ATTR_IN_PORT:
> diff --git a/lib/odp-util.c b/lib/odp-util.c
> index 3784b0f..fa5bc86 100644
> --- a/lib/odp-util.c
> +++ b/lib/odp-util.c
> @@ -138,6 +138,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char 
> *namebuf, size_t bufsize)
>      case OVS_KEY_ATTR_CT_STATE: return "ct_state";
>      case OVS_KEY_ATTR_CT_ZONE: return "ct_zone";
>      case OVS_KEY_ATTR_CT_MARK: return "ct_mark";
> +    case OVS_KEY_ATTR_CT_LABEL: return "ct_label";
>      case OVS_KEY_ATTR_TUNNEL: return "tunnel";
>      case OVS_KEY_ATTR_IN_PORT: return "in_port";
>      case OVS_KEY_ATTR_ETHERNET: return "eth";
> @@ -543,12 +544,15 @@ static const struct nl_policy ovs_conntrack_policy[] = {
>                              .min_len = sizeof(uint16_t)},
>      [OVS_CT_ATTR_MARK] = { .type = NL_A_UNSPEC, .optional = true,
>                             .min_len = sizeof(uint32_t) * 2 },
> +    [OVS_CT_ATTR_LABEL] = { .type = NL_A_UNSPEC, .optional = true,
> +                            .min_len = sizeof(ovs_u128) * 2 },
>  };
>  
>  static void
>  format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
>  {
>      struct nlattr *a[ARRAY_SIZE(ovs_conntrack_policy)];
> +    const ovs_u128 *label;
>      const uint32_t *mark;
>      uint32_t flags;
>      uint16_t zone;
> @@ -561,9 +565,10 @@ format_odp_conntrack_action(struct ds *ds, const struct 
> nlattr *attr)
>      flags = a[OVS_CT_ATTR_FLAGS] ? nl_attr_get_u32(a[OVS_CT_ATTR_FLAGS]) : 0;
>      zone = a[OVS_CT_ATTR_ZONE] ? nl_attr_get_u16(a[OVS_CT_ATTR_ZONE]) : 0;
>      mark = a[OVS_CT_ATTR_MARK] ? nl_attr_get(a[OVS_CT_ATTR_MARK]) : NULL;
> +    label = a[OVS_CT_ATTR_LABEL] ? nl_attr_get(a[OVS_CT_ATTR_LABEL]): NULL;
>  
>      ds_put_format(ds, "ct");
> -    if (flags || zone || mark) {
> +    if (flags || zone || mark || label) {
>          ds_put_cstr(ds, "(");
>          if (flags & OVS_CT_F_COMMIT) {
>              ds_put_format(ds, "commit");
> @@ -581,6 +586,15 @@ format_odp_conntrack_action(struct ds *ds, const struct 
> nlattr *attr)
>              ds_put_format(ds, "mark=%"PRIx32"/%"PRIx32, *mark,
>                            *(mark + 1));
>          }
> +        if (label) {
> +            if (ds_last(ds) != '(') {
> +                ds_put_char(ds, ',');
> +            }
> +            ds_put_format(ds, "label=");
> +            ds_put_hex(ds, label, sizeof(*label));
> +            ds_put_char(ds, '/');
> +            ds_put_hex(ds, (label + 1), sizeof(*label));
> +        }
>          ds_put_cstr(ds, ")");
>      }
>  }
> @@ -1016,6 +1030,24 @@ ovs_parse_tnl_push(const char *s, struct 
> ovs_action_push_tnl *data)
>  }
>  
>  static int
> +parse_ct_label(const char *s, struct ovs_key_ct_label *value,
> +               struct ovs_key_ct_label *mask, char **tail)
> +{
> +    if (!parse_int_string(s, (uint8_t *)value, sizeof(*value), tail)) {
> +        if (**tail == '/') {
> +            if (parse_int_string(s + 1, (uint8_t *)mask, sizeof(mask), 
> tail)) {
> +                return -EINVAL;
> +            }
> +        } else {
> +            memset(mask, 0xff, sizeof(*mask));
> +        }
> +        return 0;
> +    }
> +
> +    return -EINVAL;
> +}
> +
> +static int
>  parse_conntrack_action(const char *s_, struct ofpbuf *actions)
>  {
>      const char *s = s_;
> @@ -1027,9 +1059,15 @@ parse_conntrack_action(const char *s_, struct ofpbuf 
> *actions)
>              uint32_t value;
>              uint32_t mask;
>          } ct_mark = { 0, 0 };
> +        struct {
> +            struct ovs_key_ct_label value;
> +            struct ovs_key_ct_label mask;
> +        } ct_label;
>          size_t start;
>          char *end;
>  
> +        memset(&ct_label, 0, sizeof(ct_label));
> +
>          s += 2;
>          if (ovs_scan(s, "(")) {
>              s++;
> @@ -1039,6 +1077,7 @@ parse_conntrack_action(const char *s_, struct ofpbuf 
> *actions)
>              }
>  
>              while (s != end) {
> +                char *tail;
>                  int n = -1;
>  
>                  s += strspn(s, delimiters);
> @@ -1061,6 +1100,18 @@ parse_conntrack_action(const char *s_, struct ofpbuf 
> *actions)
>                      }
>                      continue;
>                  }
> +                if (ovs_scan(s, "label=%n", &n)) {
> +                    int error;
> +
> +                    s += n;
> +                    error = parse_ct_label(s, &ct_label.value, 
> &ct_label.mask,
> +                                           &tail);
> +                    if (error) {
> +                        return error;
> +                    }
> +                    s = tail;
> +                    continue;
> +                }
>  
>                  if (n < 0) {
>                      return -EINVAL;
> @@ -1080,6 +1131,10 @@ parse_conntrack_action(const char *s_, struct ofpbuf 
> *actions)
>              nl_msg_put_unspec(actions, OVS_CT_ATTR_MARK, &ct_mark,
>                                sizeof(ct_mark));
>          }
> +        if (!is_all_zeros(&ct_label.mask, sizeof(ct_label.mask))) {

Ditto.
> +            nl_msg_put_unspec(actions, OVS_CT_ATTR_LABEL, &ct_label,
> +                              sizeof(ct_label));
> +        }
>          nl_msg_end_nested(actions, start);
>      }
>  
> @@ -1350,6 +1405,7 @@ static const struct attr_len_tbl 
> ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
>      [OVS_KEY_ATTR_CT_STATE]  = { .len = 1 },
>      [OVS_KEY_ATTR_CT_ZONE]   = { .len = 2 },
>      [OVS_KEY_ATTR_CT_MARK]   = { .len = 4 },
> +    [OVS_KEY_ATTR_CT_LABEL]  = { .len = sizeof(struct ovs_key_ct_label) },
>  };
>  
>  /* Returns the correct length of the payload for a flow key attribute of the
> @@ -2237,6 +2293,18 @@ format_odp_key_attr(const struct nlattr *a, const 
> struct nlattr *ma,
>          }
>          break;
>  
> +    case OVS_KEY_ATTR_CT_LABEL: {
> +        const struct ovs_key_ct_label *mask = ma ? nl_attr_get(ma) : NULL;
> +
> +        if (verbose || (mask && !is_all_zeros(mask, sizeof(*mask)))) {
> +            ds_put_hex(ds, nl_attr_get(a), nl_attr_get_size(a));
> +            if (mask && !is_exact) {
> +                ds_put_char(ds, '/');
> +                ds_put_hex(ds, MASK(mask, ct_label), sizeof(*mask));
> +            }
> +        }
> +        break;
> +    }
>  
>      case OVS_KEY_ATTR_TUNNEL:
>          format_odp_tun_attr(a, ma, ds, verbose);
> @@ -2445,6 +2513,28 @@ generate_all_wildcard_mask(const struct attr_len_tbl 
> tbl[], int max,
>      return ofp->base;
>  }
>  
> +static int
> +scan_u128(const char *s_, ovs_u128 *key, ovs_u128 *mask)
> +{
> +    char *s = CONST_CAST(char *, s_);
> +    int n;
> +
> +    if (parse_int_string(s, (uint8_t *)key, sizeof(*key), &s)) {
> +        return 0;
> +    }
> +
> +    if (ovs_scan(s, "/%n", &n)) {
> +        s += n;
> +        if (parse_int_string(s, (uint8_t *)mask, sizeof(*mask), &s)) {
> +            return 0;
> +        }
> +    } else {
> +        mask->u64.hi = mask->u64.lo = UINT64_MAX;
> +    }
> +
> +    return s - s_;
> +}
> +
>  int
>  odp_ufid_from_string(const char *s_, ovs_u128 *ufid)
>  {
> @@ -3350,6 +3440,7 @@ parse_odp_key_mask_attr(const char *s, const struct 
> simap *port_names,
>      SCAN_SINGLE("ct_state(", uint8_t, ct_state, OVS_KEY_ATTR_CT_STATE);
>      SCAN_SINGLE("ct_zone(", uint16_t, u16, OVS_KEY_ATTR_CT_ZONE);
>      SCAN_SINGLE("ct_mark(", uint32_t, u32, OVS_KEY_ATTR_CT_MARK);
> +    SCAN_SINGLE("ct_label(", ovs_u128, u128, OVS_KEY_ATTR_CT_LABEL);
>  
>      SCAN_BEGIN_NESTED("tunnel(", OVS_KEY_ATTR_TUNNEL) {
>          SCAN_FIELD_NESTED("tun_id=", ovs_be64, be64, OVS_TUNNEL_KEY_ATTR_ID);
> @@ -3594,6 +3685,10 @@ odp_flow_key_from_flow__(const struct 
> odp_flow_key_parms *parms,
>      if (parms->support.ct_mark) {
>          nl_msg_put_u32(buf, OVS_KEY_ATTR_CT_MARK, data->ct_mark);
>      }
> +    if (parms->support.ct_label) {
> +        nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_LABEL, &data->ct_label,
> +                          sizeof(data->ct_label));
> +    }
>      if (parms->support.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);
> @@ -3784,6 +3879,10 @@ odp_key_from_pkt_metadata(struct ofpbuf *buf, const 
> struct pkt_metadata *md)
>          if (md->ct_mark) {
>              nl_msg_put_u32(buf, OVS_KEY_ATTR_CT_MARK, md->ct_mark);
>          }
> +        if (!ovs_u128_is_zero(&md->ct_label)) {
> +            nl_msg_put_unspec(buf, OVS_KEY_ATTR_CT_LABEL, &md->ct_label,
> +                              sizeof(md->ct_label));
> +        }
>      }
>  
>      /* Add an ingress port attribute if 'odp_in_port' is not the magical
> @@ -3845,6 +3944,13 @@ odp_key_to_pkt_metadata(const struct nlattr *key, 
> size_t key_len,
>              md->ct_mark = nl_attr_get_u32(nla);
>              wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_MARK);
>              break;
> +        case OVS_KEY_ATTR_CT_LABEL: {
> +            const ovs_u128 *cl = nl_attr_get(nla);
> +
> +            md->ct_label = *cl;
> +            wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_LABEL);
> +            break;
> +        }
>          case OVS_KEY_ATTR_TUNNEL: {
>              enum odp_key_fitness res;
>  
> @@ -4411,6 +4517,12 @@ odp_flow_key_to_flow__(const struct nlattr *key, 
> size_t key_len,
>          flow->ct_mark = nl_attr_get_u32(attrs[OVS_KEY_ATTR_CT_MARK]);
>          expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_MARK;
>      }
> +    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_CT_LABEL)) {
> +        const ovs_u128 *cl = nl_attr_get(attrs[OVS_KEY_ATTR_CT_LABEL]);
> +
> +        flow->ct_label = *cl;
> +        expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_LABEL;
> +    }
>  
>      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 fcbbbac..cf8cb02 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,7 +133,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                                                 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.
> @@ -174,6 +175,7 @@ struct odp_support {
>      bool ct_state;
>      bool ct_zone;
>      bool ct_mark;
> +    bool ct_label;
>  };
>  
>  struct odp_flow_key_parms {
> diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
> index 8a83249..52a8502 100644
> --- a/lib/ofp-actions.c
> +++ b/lib/ofp-actions.c
> @@ -6073,6 +6073,12 @@ unsupported_nesting(enum ofpact_type action, enum 
> ofpact_type outer_action)
>      return OFPERR_OFPBAC_BAD_ARGUMENT;
>  }
>  
> +static bool
> +field_requires_ct(enum mf_field_id field)
> +{
> +    return field == MFF_CT_MARK || field == MFF_CT_LABEL;
> +}
> +
>  static enum ofperr
>  ofpacts_verify_nested(const struct ofpact *a, enum ofpact_type outer_action)
>  {
> @@ -6084,12 +6090,11 @@ ofpacts_verify_nested(const struct ofpact *a, enum 
> ofpact_type outer_action)
>  
>      field = ofpact_get_mf_field(a->type, a);
>      if (outer_action == OFPACT_CT
> -        && (!field
> -            || (field && field->id != MFF_CT_MARK))) {
> +        && (!field || (field && !field_requires_ct(field->id)))) {
>          return unsupported_nesting(a->type, outer_action);
>      }
>  
> -    if (field && outer_action != OFPACT_CT && field->id == MFF_CT_MARK) {
> +    if (outer_action != OFPACT_CT && field && field_requires_ct(field->id)) {
>          VLOG_WARN("cannot set CT fields outside of \"ct\" action");
>          return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
>      }

Would this also prevent reading from the ct_fields outside of the ct_action? If 
so, maybe it shouldn’t?
> diff --git a/lib/packets.h b/lib/packets.h
> index 7181c78..65e2e1c 100644
> --- a/lib/packets.h
> +++ b/lib/packets.h
> @@ -129,6 +129,7 @@ struct pkt_metadata {
>      uint8_t ct_state;           /* Connection state. */
>      uint16_t ct_zone;           /* Connection zone. */
>      uint32_t ct_mark;           /* Connection mark. */
> +    ovs_u128 ct_label;          /* Connection label. */
>      struct flow_tnl tunnel;     /* Encapsulating tunnel parameters. Note that
>                                   * if 'ip_dst' == 0, the rest of the fields 
> may
>                                   * be uninitialized. */
> diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
> index 881188b..20fb329 100644
> --- a/ofproto/ofproto-dpif-sflow.c
> +++ b/ofproto/ofproto-dpif-sflow.c
> @@ -1032,6 +1032,7 @@ sflow_read_set_action(const struct nlattr *attr,
>      case OVS_KEY_ATTR_CT_STATE:
>      case OVS_KEY_ATTR_CT_ZONE:
>      case OVS_KEY_ATTR_CT_MARK:
> +    case OVS_KEY_ATTR_CT_LABEL:
>      case OVS_KEY_ATTR_UNSPEC:
>      case __OVS_KEY_ATTR_MAX:
>      default:
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index de74320..5da8f2f 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -2808,6 +2808,7 @@ clear_conntrack(struct flow *flow)
>      flow->ct_state = 0;
>      flow->ct_zone = 0;
>      flow->ct_mark = 0;
> +    memset(&flow->ct_label, 0, sizeof flow->ct_label);
>  }
>  
>  static void
> @@ -2822,6 +2823,7 @@ compose_output_action__(struct xlate_ctx *ctx, 
> ofp_port_t ofp_port,
>      uint32_t flow_pkt_mark, flow_ct_mark;
>      uint8_t flow_ct_state;
>      uint16_t flow_ct_zone;
> +    ovs_u128 flow_ct_label;
>      uint8_t flow_nw_tos;
>      odp_port_t out_port, odp_port;
>      bool tnl_push_pop_send = false;
> @@ -2973,6 +2975,7 @@ compose_output_action__(struct xlate_ctx *ctx, 
> ofp_port_t ofp_port,
>      flow_ct_state = flow->ct_state;
>      flow_ct_zone = flow->ct_zone;
>      flow_ct_mark = flow->ct_mark;
> +    flow_ct_label = flow->ct_label;
>      flow_nw_tos = flow->nw_tos;
>  
>      if (count_skb_priorities(xport)) {
> @@ -3098,6 +3101,7 @@ compose_output_action__(struct xlate_ctx *ctx, 
> ofp_port_t ofp_port,
>      flow->ct_state = flow_ct_state;
>      flow->ct_zone = flow_ct_zone;
>      flow->ct_mark = flow_ct_mark;
> +    flow->ct_label = flow_ct_label;
>      flow->nw_tos = flow_nw_tos;
>  }
>  
> @@ -4177,6 +4181,32 @@ put_ct_mark(const struct flow *flow, struct flow 
> *base_flow,
>  }
>  
>  static void
> +put_ct_label(const struct flow *flow, struct flow *base_flow,
> +             struct ofpbuf *odp_actions, struct flow_wildcards *wc)
> +{
> +    const ovs_u128 *key;
> +    ovs_u128 *mask, *base;
> +
> +    key = &flow->ct_label;
> +    base = &base_flow->ct_label;
> +    mask = &wc->masks.ct_label;
> +
> +    if (!is_all_zeros(mask, sizeof(*mask)) && memcmp(key, base, 
> sizeof(*key))) {
We have u128 specific functions for both uses here.

Also, what if we have multiple ct actions with labels?
> +        struct {
> +            ovs_u128 key;
> +            ovs_u128 mask;
> +        } *odp_ct_label;
> +
> +        odp_ct_label = nl_msg_put_unspec_uninit(odp_actions, 
> OVS_CT_ATTR_LABEL,
> +                                                sizeof(*odp_ct_label));
> +        odp_ct_label->key = *key;
> +        odp_ct_label->mask = *mask;
> +        base_flow->ct_label = *base;
> +        wc->masks.ct_label = *mask;
These two seem like NOPs. Maybe the intention was to update the base with the 
new value?
> +    }
> +}
> +
> +static void
>  compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
>  {
>      uint32_t flags = 0;
> @@ -4203,6 +4233,7 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct 
> ofpact_conntrack *ofc)
>      nl_msg_put_u32(ctx->odp_actions, OVS_CT_ATTR_FLAGS, flags);
>      nl_msg_put_u16(ctx->odp_actions, OVS_CT_ATTR_ZONE, zone);
>      put_ct_mark(&ctx->xin->flow, &ctx->base_flow, ctx->odp_actions, ctx->wc);
> +    put_ct_label(&ctx->xin->flow, &ctx->base_flow, ctx->odp_actions, 
> ctx->wc);
>      nl_msg_end_nested(ctx->odp_actions, ct_offset);
>  
>      if (ofc->recirc_table == NX_CT_RECIRC_NONE) {
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index 78bb4c8..ddf177a 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -1264,6 +1264,7 @@ check_##NAME(struct dpif_backer *backer)                
>                     \
>  CHECK_FEATURE(ct_state)
>  CHECK_FEATURE(ct_zone)
>  CHECK_FEATURE(ct_mark)
> +CHECK_FEATURE__(ct_label, ct_label.u64.lo)
>  
>  #undef CHECK_FEATURE
>  #undef CHECK_FEATURE__
> @@ -1283,6 +1284,7 @@ check_support(struct dpif_backer *backer)
>      backer->support.odp.ct_state = check_ct_state(backer);
>      backer->support.odp.ct_zone = check_ct_zone(backer);
>      backer->support.odp.ct_mark = check_ct_mark(backer);
> +    backer->support.odp.ct_label = check_ct_label(backer);
>  }
>  
>  static int
> @@ -3990,7 +3992,10 @@ rule_check(struct rule *rule)
>  
>      if ((match.wc.masks.ct_state && !support->ct_state)
>          || (match.wc.masks.ct_zone && !support->ct_zone)
> -        || (match.wc.masks.ct_mark && !support->ct_mark)) {
> +        || (match.wc.masks.ct_mark && !support->ct_mark)
> +        || (!support->ct_label
> +            && !is_all_zeros(&match.wc.masks.ct_label,
> +                             sizeof(match.wc.masks.ct_label)))) {

Ditto.
>          return OFPERR_OFPBMC_BAD_FIELD;
>      }
>      if (match.wc.masks.ct_state & CS_UNSUPPORTED_MASK) {
> diff --git a/ofproto/ofproto-unixctl.man b/ofproto/ofproto-unixctl.man
> index 87ef80d..53e5549 100644
> --- a/ofproto/ofproto-unixctl.man
> +++ b/ofproto/ofproto-unixctl.man
> @@ -109,6 +109,8 @@ Connection state of the packet.
>  Connection tracking zone for packet.
>  .IP \fIct_mark\fR
>  Connection mark of the packet.
> +.IP \fIct_label\fR
> +Connection label of the packet.
>  .IP \fItun_id\fR
>  The tunnel ID on which the packet arrived.
>  .IP \fIin_port\fR
> diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
> index 502416f..103f87c 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,ct_state=0,ct_zone=0,ct_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,ct_state=0,ct_zone=0,ct_mark=0,ct_label=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/odp.at b/tests/odp.at
> index bf3fa8a..702d44c 100644
> --- a/tests/odp.at
> +++ b/tests/odp.at
> @@ -71,7 +71,7 @@ s/$/)/' odp-base.txt
>  
>   echo
>   echo '# Valid forms with conntrack fields.'
> - sed 
> 's/^/skb_priority(0),skb_mark(0),ct_mark(0x12345678),recirc_id(0),dp_hash(0),/'
>  odp-base.txt
> + sed 
> 's/^/skb_priority(0),skb_mark(0),ct_mark(0x12345678),ct_label(0x1234567890abcdef1234567890abcdef),recirc_id(0),dp_hash(0),/'
>  odp-base.txt
>  
>   echo
>   echo '# Valid forms with IP first fragment.'
> @@ -93,7 +93,7 @@ s/^/ODP_FIT_TOO_LITTLE: /
>  dnl Some fields are always printed for this test, because wildcards aren't
>  dnl specified. We can skip these.
>  sed -i 's/\(skb_mark(0)\),\(ct\)/\1,ct_state(0),ct_zone(0),\2/' odp-out.txt
> -sed -i 
> 's/\(skb_mark([[^)]]*)\),\(recirc\)/\1,ct_state(0),ct_zone(0),ct_mark(0),\2/' 
> odp-out.txt
> +sed -i 
> 's/\(skb_mark([[^)]]*)\),\(recirc\)/\1,ct_state(0),ct_zone(0),ct_mark(0),ct_label(0),\2/'
>  odp-out.txt
>  
>  AT_CHECK_UNQUOTED([ovstest test-odp parse-keys < odp-in.txt], [0], [`cat 
> odp-out.txt`
>  ])
> @@ -163,7 +163,7 @@ s/$/)/' odp-base.txt
>  
>   echo
>   echo '# Valid forms with conntrack fields.'
> - sed 
> 's/\(eth([[^)]]*),?\)/\1,ct_state(+trk),ct_mark(0x12345678\/0xFOFOFOFO),/' 
> odp-base.txt
> + sed 
> 's/\(eth([[^)]]*),?\)/\1,ct_state(+trk),ct_mark(0x12345678\/0xFOFOFOFO),ct_label(0x1234567890ABCDEF1234567890ABCDEF\/0x102030405060708090A0B0C0D0E0F0),/'
>  odp-base.txt
>  
>   echo
>   echo '# Valid forms with IP first fragment.'
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index ce214d9..429b88b 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -6486,8 +6486,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,ct_state=0,ct_zone=0,ct_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,ct_state=0,ct_zone=0,ct_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,ct_state=0,ct_zone=0,ct_mark=0,ct_label=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,ct_state=0,ct_zone=0,ct_mark=0,ct_label=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 120a991..95cd75b 100644
> --- a/tests/ofproto.at
> +++ b/tests/ofproto.at
> @@ -1529,7 +1529,7 @@ head_table () {
>        instructions: 
> meter,apply_actions,clear_actions,write_actions,write_metadata,goto_table
>        Write-Actions and Apply-Actions features:
>          actions: output group set_field strip_vlan push_vlan mod_nw_ttl 
> dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
> -        supported on Set-Field: tun_id tun_src tun_dst tun_flags tun_gbp_id 
> tun_gbp_flags tun_metadata0 tun_metadata1 tun_metadata2 tun_metadata3 
> tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 
> tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 
> tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 
> tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 
> tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 
> tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 
> tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 
> tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 
> tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 
> tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 
> tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata58 
> tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 
> metadata in_port in_port_oxm pkt_mark ct_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_flags tun_gbp_id 
> tun_gbp_flags tun_metadata0 tun_metadata1 tun_metadata2 tun_metadata3 
> tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 
> tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 
> tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 
> tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 
> tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 
> tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 
> tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 
> tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 
> tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 
> tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 
> tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata58 
> tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 
> metadata in_port in_port_oxm pkt_mark ct_mark ct_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
> @@ -1612,6 +1612,7 @@ head_table () {
>        ct_state: arbitrary mask
>        ct_zone: exact match or wildcard
>        ct_mark: arbitrary mask
> +      ct_label: arbitrary mask
>        reg0: arbitrary mask
>        reg1: arbitrary mask
>        reg2: arbitrary mask
> diff --git a/tests/system-traffic.at b/tests/system-traffic.at
> index e7c6846..8c198f9 100644
> --- a/tests/system-traffic.at
> +++ b/tests/system-traffic.at
> @@ -658,6 +658,45 @@ SYN_RECV src=10.1.1.3 dst=10.1.1.4 sport=<cleared> 
> dport=<cleared> src=10.1.1.4
>  OVS_TRAFFIC_VSWITCHD_STOP
>  AT_CLEANUP
>  
> +AT_SETUP([conntrack - ct_label])
> +CHECK_CONNTRACK()
> +OVS_TRAFFIC_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.

How about at_ns2 and at_ns3?
> +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,exec(set_field:1->ct_label)),2
> +in_port=2,ct_state=-trk,tcp,action=ct(table=0)
> +in_port=2,ct_state=+trk,ct_label=000000000000000001,tcp,action=1
> +in_port=3,tcp,action=ct(commit,exec(set_field:2->ct_label)),4
> +in_port=4,ct_state=-trk,tcp,action=ct(table=0)
> +in_port=4,ct_state=+trk,ct_label=000000000000000001,tcp,action=3

Explicit priorities would be nice.
> +])
> +
> +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-l7.py]], [http0.pid])
> +NS_CHECK_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 3 times, in 1 second intervals.
> +NETNS_DAEMONIZE([at_ns3], [[$PYTHON $srcdir/test-l7.py]], [http1.pid])
> +NS_CHECK_EXEC([at_ns2], [wget 10.1.1.4 -t 3 -T 1 -v -o wget1.log], [4])
> +
> +OVS_TRAFFIC_VSWITCHD_STOP
> +AT_CLEANUP
> +
>  AT_SETUP([conntrack - ICMP related])
>  CHECK_CONNTRACK()
>  OVS_TRAFFIC_VSWITCHD_START(
> diff --git a/tests/test-odp.c b/tests/test-odp.c
> index 1c65590..4b89867 100644
> --- a/tests/test-odp.c
> +++ b/tests/test-odp.c
> @@ -62,6 +62,7 @@ parse_keys(bool wc_keys)
>                      .ct_state = true,
>                      .ct_zone = true,
>                      .ct_mark = true,
> +                    .ct_label = true,
>                  },
>              };
>  
> diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
> index 31644b3..b1ee36e 100644
> --- a/utilities/ovs-ofctl.8.in
> +++ b/utilities/ovs-ofctl.8.in
> @@ -1368,6 +1368,10 @@ Matches connection zone \fIvalue\fR exactly.
>  Matches connection mark \fIvalue\fR either exactly or with optional
>  \fImask\fR.
>  .
> +.IP \fBct_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
>  support for NXM.  The following shorthand notations are available for
> @@ -1630,6 +1634,10 @@ specify a 16-bit range within the field.
>  Store a 32-bit metadata value (masked by \fImask\fR) with the connection.
>  This will populate the \fBct_mark\fR flow field if the \fBtable\fR is
>  specified in the \fBct\fR action.
> +.IP \fBlabel=\fR\fIlabel\fR[/\fImask\fR]
> +Store a 128-bit metadata value (masked by \fImask\fR) with the connection.
> +This will populate the \fBct_label\fR flow field if the \fBtable\fR is
> +specified in the \fBct\fR action.

Ah, now I get the “table” :-)
>  .RE
>  .IP
>  Currently, connection tracking is only available on Linux kernels with the

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

Reply via email to