[I tried to reproduce the lost email message from the patch (from github).]

With the comments below,

Acked-by: Jarno Rajahalme <[email protected]>

> This patch adds a new 32-bit metadata field to the connection tracking
> interface. When a mark is specified as part of the ct action and the
> connection is committed, the value is saved with the current connection.
The mark would be re-written by later conntrack actions specifying the mark on 
the same connection. However, I don’t know if that is useful. Should we 
restrict the mark writing only to the case when the connection is committed, 
like you write above?
> Subsequent ct lookups with the table specified will expose this metadata
> as the "ct_mark" 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 mark 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_mark)),2
>     in_port=2,ct_state=-trk,tcp,action=ct(table=1)
>     table=1,in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1
> 
> Signed-off-by: Joe Stringer <[email protected]>
> ---
>  NEWS                                              |   4 +-
>  datapath/flow_netlink.c                           |   2 +-
>  datapath/linux/compat/include/linux/openvswitch.h |   5 +
>  lib/dpif-netdev.c                                 |   6 +-
>  lib/flow.c                                        |   9 ++
>  lib/flow.h                                        |   9 +-
>  lib/match.c                                       |  18 +++
>  lib/match.h                                       |   2 +
>  lib/meta-flow.c                                   |  24 ++++
>  lib/meta-flow.h                                   |  17 +++
>  lib/nx-match.c                                    |   6 +-
>  lib/odp-execute.c                                 |   2 +
>  lib/odp-util.c                                    |  57 +++++++++-
>  lib/odp-util.h                                    |   6 +-
>  lib/ofp-actions.c                                 |  44 +++++++-
>  lib/packets.h                                     |   1 +
>  ofproto/ofproto-dpif-sflow.c                      |   1 +
>  ofproto/ofproto-dpif-xlate.c                      |  28 ++++-
>  ofproto/ofproto-dpif.c                            |   5 +-
>  ofproto/ofproto-unixctl.man                       |   2 +
>  tests/dpif-netdev.at                              |   2 +-
>  tests/odp.at                                      |   8 +-
>  tests/ofproto-dpif.at                             |   4 +-
>  tests/ofproto.at                                  |   3 +-
>  tests/system-traffic.at                           | 131 
> ++++++++++++++++++++++
>  tests/test-odp.c                                  |   1 +
>  utilities/ovs-ofctl.8.in                          |   8 ++
>  27 files changed, 380 insertions(+), 25 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index 45ae059..6eeccdc 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" match fields.  Only available on Linux kernels
> -     with the connection tracking module loaded.
> +     and "ct_state"/"ct_zone"/"ct_mark" match fields.  Only available on
> +     Linux kernels with the connection tracking module loaded.
>  
>  
>  v2.4.0 - 20 Aug 2015
> diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
> index 8527fe0..352fc87 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 != 24);
> +     BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 25);
>  
>       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 69bdf32..5c7b2af 100644
> --- a/datapath/linux/compat/include/linux/openvswitch.h
> +++ b/datapath/linux/compat/include/linux/openvswitch.h
> @@ -345,6 +345,7 @@ enum ovs_key_attr {
>                                * the accepted length of the array. */
>       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 */
>  
>  #ifdef __KERNEL__
>       /* Only used within kernel data path. */
> @@ -656,11 +657,15 @@ struct ovs_action_push_tnl {
>   * enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action.
>   * @OVS_CT_ATTR_FLAGS: u32 connection tracking flags.
>   * @OVS_CT_ATTR_ZONE: u16 connection tracking zone.
> + * @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.
So the mask is not optional in the datapath ct action? Not saying it should be, 
just asking.
>   */
>  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_MAX
>  };
>  
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 0fc5f8c..8a183c0 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -1921,7 +1921,7 @@ 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) {
> +    if (flow->ct_state || flow->ct_zone || flow->ct_mark) {
>          return EINVAL;
>      }
>  
> @@ -3607,12 +3607,12 @@ dp_execute_cb(void *aux_, struct dp_packet **packets, 
> int cnt,
>          VLOG_WARN("Cannot execute conntrack action in userspace.");
>          break;
>  
> +    case OVS_ACTION_ATTR_SET:
> +    case OVS_ACTION_ATTR_SET_MASKED:
>      case OVS_ACTION_ATTR_PUSH_VLAN:
>      case OVS_ACTION_ATTR_POP_VLAN:
>      case OVS_ACTION_ATTR_PUSH_MPLS:
>      case OVS_ACTION_ATTR_POP_MPLS:
> -    case OVS_ACTION_ATTR_SET:
> -    case OVS_ACTION_ATTR_SET_MASKED:
>      case OVS_ACTION_ATTR_SAMPLE:
>      case OVS_ACTION_ATTR_HASH:
>      case OVS_ACTION_ATTR_UNSPEC:
Seems like a spurious change, but does not matter really.
> diff --git a/lib/flow.c b/lib/flow.c
> index 032df14..3ae1432 100644
> --- a/lib/flow.c
> +++ b/lib/flow.c
> @@ -503,6 +503,7 @@ miniflow_extract(struct dp_packet *packet, struct 
> miniflow *dst)
>      }
>  
>      if (md->ct_state) {
> +        miniflow_push_uint32(mf, ct_mark, md->ct_mark);
>          miniflow_push_uint16(mf, ct_zone, md->ct_zone);
>          miniflow_push_uint8(mf, ct_state, md->ct_state);
>          miniflow_pad_to_64(mf, pad1);
> @@ -864,6 +865,9 @@ flow_get_metadata(const struct flow *flow, struct match 
> *flow_metadata)
>      if (flow->ct_zone != 0) {
>          match_set_ct_zone(flow_metadata, flow->ct_zone);
>      }
> +    if (flow->ct_mark != 0) {
> +        match_set_ct_mark(flow_metadata, flow->ct_mark);
> +    }
>  }
>  
>  char *
> @@ -1145,6 +1149,9 @@ flow_format(struct ds *ds, const struct flow *flow)
>      if (!flow->ct_zone) {
>          WC_UNMASK_FIELD(wc, ct_zone);
>      }
> +    if (!flow->ct_mark) {
> +        WC_UNMASK_FIELD(wc, ct_mark);
> +    }
>      for (int i = 0; i < FLOW_N_REGS; i++) {
>          if (!flow->regs[i]) {
>              WC_UNMASK_FIELD(wc, regs[i]);
> @@ -1221,6 +1228,7 @@ void flow_wildcards_init_for_packet(struct 
> flow_wildcards *wc,
>      WC_MASK_FIELD(wc, pkt_mark);
>      WC_MASK_FIELD(wc, ct_state);
>      WC_MASK_FIELD(wc, ct_zone);
> +    WC_MASK_FIELD(wc, ct_mark);
>      WC_MASK_FIELD(wc, recirc_id);
>      WC_MASK_FIELD(wc, dp_hash);
>      WC_MASK_FIELD(wc, in_port);
> @@ -1326,6 +1334,7 @@ flow_wc_map(const struct flow *flow, struct flowmap 
> *map)
>      FLOWMAP_SET(map, vlan_tci);
>      FLOWMAP_SET(map, ct_state);
>      FLOWMAP_SET(map, ct_zone);
> +    FLOWMAP_SET(map, ct_mark);
>  
>      /* Ethertype-dependent fields. */
>      if (OVS_LIKELY(flow->dl_type == htons(ETH_TYPE_IP))) {
> diff --git a/lib/flow.h b/lib/flow.h
> index 2d6d3ee..b5edaa0 100644
> --- a/lib/flow.h
> +++ b/lib/flow.h
> @@ -103,10 +103,12 @@ struct flow {
>      union flow_in_port in_port; /* Input port.*/
>      uint32_t recirc_id;         /* Must be exact match. */
>      uint32_t conj_id;           /* Conjunction ID. */
> +    uint32_t ct_mark;           /* Connection mark. With L3 to avoid L4 
> match.*/
Comment needs an update, as this is the “metadata” stage.
>      uint16_t ct_zone;           /* Connection Zone. */
>      uint8_t ct_state;           /* Connection state. */

I would still put at least ct_state and maybe ct_zone right after recirc_id for 
the benefit of cases where those are used, but where mark remains zero.
> -    uint8_t pad1[3];            /* Pad to 64 bits. */
> +    uint8_t pad1[1];            /* Pad to 64 bits. */
>      ofp_port_t actset_output;   /* Output port in action set. */
> +    uint8_t pad2[6];            /* Pad to 64 bits. */
>  
>      /* L2, Order the same as in the Ethernet header! (64-bit aligned) */
>      struct eth_addr dl_dst;     /* Ethernet destination address. */
> @@ -129,7 +131,7 @@ struct flow {
>      struct eth_addr arp_sha;    /* ARP/ND source hardware address. */
>      struct eth_addr arp_tha;    /* ARP/ND target hardware address. */
>      ovs_be16 tcp_flags;         /* TCP flags. With L3 to avoid matching L4. 
> */
> -    ovs_be16 pad2;              /* Pad to 64 bits. */
> +    ovs_be16 pad3;              /* Pad to 64 bits. */
>  
>      /* L4 (64-bit aligned) */
>      ovs_be16 tp_src;            /* TCP/UDP/SCTP source port. */
> @@ -155,7 +157,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) + 192
> +                  == sizeof(struct flow_tnl) + 200
>                    && FLOW_WC_SEQ == 33);
>  
>  /* Incremental points at which flow classification may be performed in
> @@ -984,6 +986,7 @@ pkt_metadata_from_flow(struct pkt_metadata *md, const 
> struct flow *flow)
>      md->in_port = flow->in_port;
>      md->ct_state = flow->ct_state;
>      md->ct_zone = flow->ct_zone;
> +    md->ct_mark = flow->ct_mark;
>  }
>  
>  static inline bool is_ip_any(const struct flow *flow)
> diff --git a/lib/match.c b/lib/match.c
> index 87f940c..7e0d7dd 100644
> --- a/lib/match.c
> +++ b/lib/match.c
> @@ -305,6 +305,20 @@ match_set_ct_zone(struct match *match, uint16_t ct_zone)
>  }
>  
>  void
> +match_set_ct_mark(struct match *match, uint32_t ct_mark)
> +{
> +    match_set_ct_mark_masked(match, ct_mark, UINT32_MAX);
> +}
> +
> +void
> +match_set_ct_mark_masked(struct match *match, uint32_t ct_mark,
> +                           uint32_t mask)
> +{
> +    match->flow.ct_mark = ct_mark & mask;
> +    match->wc.masks.ct_mark = mask;
> +}
> +
> +void
>  match_set_dl_type(struct match *match, ovs_be16 dl_type)
>  {
>      match->wc.masks.dl_type = OVS_BE16_MAX;
> @@ -1008,6 +1022,10 @@ match_format(const struct match *match, struct ds *s, 
> int priority)
>          format_uint16_masked(s, "ct_zone", f->ct_zone, wc->masks.ct_zone);
>      }
>  
> +    if (wc->masks.ct_mark) {
> +        format_uint32_masked(s, "ct_mark", f->ct_mark, wc->masks.ct_mark);
> +    }
> +
>      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 f264551..5d606ae 100644
> --- a/lib/match.h
> +++ b/lib/match.h
> @@ -86,6 +86,8 @@ void match_set_pkt_mark_masked(struct match *, uint32_t 
> pkt_mark, uint32_t mask)
>  void match_set_ct_state(struct match *, uint8_t ct_state);
>  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_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 4a7ec7f..444c2e4 100644
> --- a/lib/meta-flow.c
> +++ b/lib/meta-flow.c
> @@ -218,6 +218,8 @@ mf_is_all_wild(const struct mf_field *mf, const struct 
> flow_wildcards *wc)
>          return !wc->masks.ct_state;
>      case MFF_CT_ZONE:
>          return !wc->masks.ct_zone;
> +    case MFF_CT_MARK:
> +        return !wc->masks.ct_mark;
>      CASE_MFF_REGS:
>          return !wc->masks.regs[mf->id - MFF_REG0];
>      CASE_MFF_XREGS:
> @@ -503,6 +505,7 @@ mf_is_value_valid(const struct mf_field *mf, const union 
> mf_value *value)
>      case MFF_PKT_MARK:
>      case MFF_CT_STATE:
>      case MFF_CT_ZONE:
> +    case MFF_CT_MARK:
>      CASE_MFF_REGS:
>      CASE_MFF_XREGS:
>      case MFF_ETH_SRC:
> @@ -658,6 +661,10 @@ mf_get_value(const struct mf_field *mf, const struct 
> flow *flow,
>          value->be16 = htons(flow->ct_zone);
>          break;
>  
> +    case MFF_CT_MARK:
> +        value->be32 = htonl(flow->ct_mark);
> +        break;
> +
>      CASE_MFF_REGS:
>          value->be32 = htonl(flow->regs[mf->id - MFF_REG0]);
>          break;
> @@ -898,6 +905,10 @@ mf_set_value(const struct mf_field *mf,
>          match_set_ct_zone(match, ntohs(value->be16));
>          break;
>  
> +    case MFF_CT_MARK:
> +        match_set_ct_mark(match, ntohl(value->be32));
> +        break;
> +
>      CASE_MFF_REGS:
>          match_set_reg(match, mf->id - MFF_REG0, ntohl(value->be32));
>          break;
> @@ -1190,6 +1201,10 @@ mf_set_flow_value(const struct mf_field *mf,
>          flow->ct_zone = ntohs(value->be16);
>          break;
>  
> +    case MFF_CT_MARK:
> +        flow->ct_mark = ntohl(value->be32);
> +        break;
> +
>      CASE_MFF_REGS:
>          flow->regs[mf->id - MFF_REG0] = ntohl(value->be32);
>          break;
> @@ -1489,6 +1504,11 @@ mf_set_wild(const struct mf_field *mf, struct match 
> *match, char **err_str)
>          match->wc.masks.ct_zone = 0;
>          break;
>  
> +    case MFF_CT_MARK:
> +        match->flow.ct_mark = 0;
> +        match->wc.masks.ct_mark = 0;
> +        break;
> +
>      CASE_MFF_REGS:
>          match_set_reg_masked(match, mf->id - MFF_REG0, 0, 0);
>          break;
> @@ -1756,6 +1776,10 @@ mf_set(const struct mf_field *mf,
>          match_set_ct_state_masked(match, value->u8, mask->u8);
>          break;
>  
> +    case MFF_CT_MARK:
> +        match_set_ct_mark_masked(match, ntohl(value->be32), 
> ntohl(mask->be32));
> +        break;
> +
>      case MFF_ETH_DST:
>          match_set_dl_dst_masked(match, value->mac, mask->mac);
>          break;
> diff --git a/lib/meta-flow.h b/lib/meta-flow.h
> index 44a29e9..576f4d7 100644
> --- a/lib/meta-flow.h
> +++ b/lib/meta-flow.h
> @@ -748,6 +748,23 @@ enum OVS_PACKED_ENUM mf_field_id {
>       */
>      MFF_CT_ZONE,
>  
> +    /* "ct_mark".
> +     *
> +     * Connection tracking mark.  The mark is carried with the
> +     * connection tracking state.  On Linux this corresponds to the
> +     * nf_conn's "mark" member but the exact implementation is
> +     * platform-dependent.
> +     *
> +     * Type: be32.
> +     * Maskable: bitwise.
> +     * Formatting: hexadecimal.
> +     * Prerequisites: none.
> +     * Access: read/write.
But we prevent writing outside of conntrack action. Maybe mention that in the 
comment above.
> +     * NXM: NXM_NX_CT_MARK(107) since v2.5.
> +     * OXM: none.
> +     */
> +    MFF_CT_MARK,
> +
>  #if FLOW_N_REGS == 8
>      /* "reg<N>".
>       *
> diff --git a/lib/nx-match.c b/lib/nx-match.c
> index 4268b42..c298a6f 100644
> --- a/lib/nx-match.c
> +++ b/lib/nx-match.c
> @@ -1032,7 +1032,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, 
> const struct match *match,
>          }
>      }
>  
> -    /* Mark. */
> +    /* Packet mark. */
>      nxm_put_32m(b, MFF_PKT_MARK, oxm, htonl(flow->pkt_mark),
>                  htonl(match->wc.masks.pkt_mark));
>  
> @@ -1045,6 +1045,10 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, 
> const struct match *match,
>          nxm_put_16m(b, MFF_CT_ZONE, oxm, htons(flow->ct_zone),
>                      htons(match->wc.masks.ct_zone));
>      }
> +    if (match->wc.masks.ct_mark) {
> +        nxm_put_32m(b, MFF_CT_MARK, oxm, htonl(flow->ct_mark),
> +                    htonl(match->wc.masks.ct_mark));
> +    }
>  
>      /* OpenFlow 1.1+ Metadata. */
>      nxm_put_64m(b, MFF_METADATA, oxm,
> diff --git a/lib/odp-execute.c b/lib/odp-execute.c
> index 575631c..4d6384e 100644
> --- a/lib/odp-execute.c
> +++ b/lib/odp-execute.c
> @@ -328,6 +328,7 @@ odp_execute_set_action(struct dp_packet *packet, const 
> struct nlattr *a)
>      case OVS_KEY_ATTR_TCP_FLAGS:
>      case OVS_KEY_ATTR_CT_STATE:
>      case OVS_KEY_ATTR_CT_ZONE:
> +    case OVS_KEY_ATTR_CT_MARK:
>      case __OVS_KEY_ATTR_MAX:
>      default:
>          OVS_NOT_REACHED();
> @@ -418,6 +419,7 @@ odp_execute_masked_set_action(struct dp_packet *packet,
>      case OVS_KEY_ATTR_UNSPEC:
>      case OVS_KEY_ATTR_CT_STATE:
>      case OVS_KEY_ATTR_CT_ZONE:
> +    case OVS_KEY_ATTR_CT_MARK:
>      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 1ce6258..3784b0f 100644
> --- a/lib/odp-util.c
> +++ b/lib/odp-util.c
> @@ -137,6 +137,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char 
> *namebuf, size_t bufsize)
>      case OVS_KEY_ATTR_SKB_MARK: return "skb_mark";
>      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_TUNNEL: return "tunnel";
>      case OVS_KEY_ATTR_IN_PORT: return "in_port";
>      case OVS_KEY_ATTR_ETHERNET: return "eth";
> @@ -540,12 +541,15 @@ static const struct nl_policy ovs_conntrack_policy[] = {
>                              .min_len = sizeof(uint32_t) },
>      [OVS_CT_ATTR_ZONE] = { .type = NL_A_U16, .optional = true,
>                              .min_len = sizeof(uint16_t)},
> +    [OVS_CT_ATTR_MARK] = { .type = NL_A_UNSPEC, .optional = true,
> +                           .min_len = sizeof(uint32_t) * 2 },
>  };
>  
>  static void
>  format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr)
>  {
>      struct nlattr *a[ARRAY_SIZE(ovs_conntrack_policy)];
> +    const uint32_t *mark;
>      uint32_t flags;
>      uint16_t zone;
>  
> @@ -556,9 +560,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;
>  
>      ds_put_format(ds, "ct");
> -    if (flags || zone) {
> +    if (flags || zone || mark) {
>          ds_put_cstr(ds, "(");
>          if (flags & OVS_CT_F_COMMIT) {
>              ds_put_format(ds, "commit");
> @@ -569,6 +574,13 @@ format_odp_conntrack_action(struct ds *ds, const struct 
> nlattr *attr)
>              }
>              ds_put_format(ds, "zone=%"PRIu16, zone);
>          }
> +        if (mark) {
> +            if (ds_last(ds) != '(') {
> +                ds_put_char(ds, ',');
> +            }
> +            ds_put_format(ds, "mark=%"PRIx32"/%"PRIx32, *mark,
> +                          *(mark + 1));
> +        }
>          ds_put_cstr(ds, ")");
>      }
>  }
> @@ -1011,6 +1023,10 @@ parse_conntrack_action(const char *s_, struct ofpbuf 
> *actions)
>      if (ovs_scan(s, "ct")) {
>          uint32_t flags = 0;
>          uint16_t zone = 0;
> +        struct {
> +            uint32_t value;
> +            uint32_t mask;
> +        } ct_mark = { 0, 0 };
>          size_t start;
>          char *end;
>  
> @@ -1035,6 +1051,16 @@ parse_conntrack_action(const char *s_, struct ofpbuf 
> *actions)
>                      s += n;
>                      continue;
>                  }
> +                if (ovs_scan(s, "mark=%"SCNu32"%n", &ct_mark.value, &n)) {
> +                    s += n;
> +                    n = -1;
> +                    if (ovs_scan(s, "/%"SCNu32"%n", &ct_mark.mask, &n)) {
> +                        s += n;
> +                    } else {
> +                        ct_mark.mask = UINT32_MAX;
> +                    }
> +                    continue;
> +                }
>  
>                  if (n < 0) {
>                      return -EINVAL;
> @@ -1050,6 +1076,10 @@ parse_conntrack_action(const char *s_, struct ofpbuf 
> *actions)
>          if (zone) {
>              nl_msg_put_u16(actions, OVS_CT_ATTR_ZONE, zone);
>          }
> +        if (ct_mark.mask) {
> +            nl_msg_put_unspec(actions, OVS_CT_ATTR_MARK, &ct_mark,
> +                              sizeof(ct_mark));
> +        }
>          nl_msg_end_nested(actions, start);
>      }
>  
> @@ -1319,6 +1349,7 @@ static const struct attr_len_tbl 
> ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
>      [OVS_KEY_ATTR_ND]        = { .len = sizeof(struct ovs_key_nd) },
>      [OVS_KEY_ATTR_CT_STATE]  = { .len = 1 },
>      [OVS_KEY_ATTR_CT_ZONE]   = { .len = 2 },
> +    [OVS_KEY_ATTR_CT_MARK]   = { .len = 4 },
>  };
>  
>  /* Returns the correct length of the payload for a flow key attribute of the
> @@ -2180,6 +2211,15 @@ format_odp_key_attr(const struct nlattr *a, const 
> struct nlattr *ma,
>          }
>          break;
>  
> +    case OVS_KEY_ATTR_CT_MARK:
> +        if (verbose || !mask_empty(ma)) {
> +            ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a));
> +            if (!is_exact) {
> +                ds_put_format(ds, "/%#"PRIx32, nl_attr_get_u32(ma));
> +            }
> +        }
> +        break;
> +
>      case OVS_KEY_ATTR_CT_STATE:
>          if (!is_exact) {
>              format_flags_masked(ds, NULL, packet_ct_state_to_string,
> @@ -3309,6 +3349,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_BEGIN_NESTED("tunnel(", OVS_KEY_ATTR_TUNNEL) {
>          SCAN_FIELD_NESTED("tun_id=", ovs_be64, be64, OVS_TUNNEL_KEY_ATTR_ID);
> @@ -3550,6 +3591,9 @@ odp_flow_key_from_flow__(const struct 
> odp_flow_key_parms *parms,
>      if (parms->support.ct_zone) {
>          nl_msg_put_u16(buf, OVS_KEY_ATTR_CT_ZONE, data->ct_zone);
>      }
> +    if (parms->support.ct_mark) {
> +        nl_msg_put_u32(buf, OVS_KEY_ATTR_CT_MARK, data->ct_mark);
> +    }
>      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);
> @@ -3737,6 +3781,9 @@ odp_key_from_pkt_metadata(struct ofpbuf *buf, const 
> struct pkt_metadata *md)
>          if (md->ct_zone) {
>              nl_msg_put_u16(buf, OVS_KEY_ATTR_CT_ZONE, md->ct_zone);
>          }
> +        if (md->ct_mark) {
> +            nl_msg_put_u32(buf, OVS_KEY_ATTR_CT_MARK, md->ct_mark);
> +        }
>      }
>  
>      /* Add an ingress port attribute if 'odp_in_port' is not the magical
> @@ -3794,6 +3841,10 @@ odp_key_to_pkt_metadata(const struct nlattr *key, 
> size_t key_len,
>              md->ct_zone = nl_attr_get_u16(nla);
>              wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_ZONE);
>              break;
> +        case OVS_KEY_ATTR_CT_MARK:
> +            md->ct_mark = nl_attr_get_u32(nla);
> +            wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_MARK);
> +            break;
>          case OVS_KEY_ATTR_TUNNEL: {
>              enum odp_key_fitness res;
>  
> @@ -4356,6 +4407,10 @@ odp_flow_key_to_flow__(const struct nlattr *key, 
> size_t key_len,
>          flow->ct_zone = nl_attr_get_u16(attrs[OVS_KEY_ATTR_CT_ZONE]);
>          expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_ZONE;
>      }
> +    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_CT_MARK)) {
> +        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_TUNNEL)) {
>          enum odp_key_fitness res;
> diff --git a/lib/odp-util.h b/lib/odp-util.h
> index 5822777..fcbbbac 100644
> --- a/lib/odp-util.h
> +++ b/lib/odp-util.h
> @@ -122,6 +122,7 @@ void odp_portno_names_destroy(struct hmap *portno_names);
>   *  OVS_KEY_ATTR_RECIRC_ID               4    --     4      8
>   *  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_ETHERNET               12    --     4     16
>   *  OVS_KEY_ATTR_ETHERTYPE               2     2     4      8  (outer VLAN 
> ethertype)
>   *  OVS_KEY_ATTR_VLAN                    2     2     4      8
> @@ -131,12 +132,12 @@ 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                                                 504
> + *  total                                                 512
>   *
>   * 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
> +#define ODPUTIL_FLOW_KEY_BYTES 576
>  BUILD_ASSERT_DECL(FLOW_WC_SEQ == 33);
>  
>  /* A buffer with sufficient size and alignment to hold an nlattr-formatted 
> flow
> @@ -172,6 +173,7 @@ struct odp_support {
>      /* If true, serialise the corresponding OVS_KEY_ATTR_CONN_* field. */
>      bool ct_state;
>      bool ct_zone;
> +    bool ct_mark;
>  };
>  
>  struct odp_flow_key_parms {
> diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
> index 6ea421f..8a83249 100644
> --- a/lib/ofp-actions.c
> +++ b/lib/ofp-actions.c
> @@ -6049,13 +6049,49 @@ ofpacts_check_consistency(struct ofpact ofpacts[], 
> size_t ofpacts_len,
>              : 0);
>  }
>  
> +static const struct mf_field *
> +ofpact_get_mf_field(enum ofpact_type type, const void *ofpact)
> +{
> +    if (type == OFPACT_SET_FIELD) {
> +        const struct ofpact_set_field *orl = ofpact;
> +
> +        return orl->field;
> +    } else if (type == OFPACT_REG_MOVE) {
> +        const struct ofpact_reg_move *orm = ofpact;
> +
> +        return orm->dst.field;
> +    }
> +
> +    return NULL;
> +}
> +
> +static enum ofperr
> +unsupported_nesting(enum ofpact_type action, enum ofpact_type outer_action)
> +{
> +    VLOG_WARN("\"%s\" action doesn't support nested action \"%s\"",
> +              ofpact_name(outer_action), ofpact_name(action));
> +    return OFPERR_OFPBAC_BAD_ARGUMENT;
> +}
> +
>  static enum ofperr
>  ofpacts_verify_nested(const struct ofpact *a, enum ofpact_type outer_action)
>  {
> -    if (outer_action != OFPACT_WRITE_ACTIONS) {
> -        VLOG_WARN("\"%s\" action doesn't support nested action \"%s\"",
> -                  ofpact_name(outer_action), ofpact_name(a->type));
> -        return OFPERR_OFPBAC_BAD_ARGUMENT;
> +    const struct mf_field *field;
> +
> +    if (outer_action != OFPACT_CT && outer_action != OFPACT_WRITE_ACTIONS) {
> +        return unsupported_nesting(a->type, outer_action);
> +    }
> +
> +    field = ofpact_get_mf_field(a->type, a);
> +    if (outer_action == OFPACT_CT
> +        && (!field
> +            || (field && field->id != MFF_CT_MARK))) {
> +        return unsupported_nesting(a->type, outer_action);
> +    }
> +
> +    if (field && outer_action != OFPACT_CT && field->id == MFF_CT_MARK) {
> +        VLOG_WARN("cannot set CT fields outside of \"ct\" action");
> +        return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
>      }
>  
>      return 0;
> diff --git a/lib/packets.h b/lib/packets.h
> index ed4540c..7181c78 100644
> --- a/lib/packets.h
> +++ b/lib/packets.h
> @@ -128,6 +128,7 @@ struct pkt_metadata {
>      union flow_in_port in_port; /* Input port. */
>      uint8_t ct_state;           /* Connection state. */
>      uint16_t ct_zone;           /* Connection zone. */
> +    uint32_t ct_mark;           /* Connection mark. */
>      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 0f7d258..881188b 100644
> --- a/ofproto/ofproto-dpif-sflow.c
> +++ b/ofproto/ofproto-dpif-sflow.c
> @@ -1031,6 +1031,7 @@ sflow_read_set_action(const struct nlattr *attr,
>      case OVS_KEY_ATTR_ND:
>      case OVS_KEY_ATTR_CT_STATE:
>      case OVS_KEY_ATTR_CT_ZONE:
> +    case OVS_KEY_ATTR_CT_MARK:
>      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 44064a6..de74320 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -2807,6 +2807,7 @@ clear_conntrack(struct flow *flow)
>  {
>      flow->ct_state = 0;
>      flow->ct_zone = 0;
> +    flow->ct_mark = 0;
>  }
>  
>  static void
> @@ -2818,7 +2819,7 @@ compose_output_action__(struct xlate_ctx *ctx, 
> ofp_port_t ofp_port,
>      struct flow *flow = &ctx->xin->flow;
>      struct flow_tnl flow_tnl;
>      ovs_be16 flow_vlan_tci;
> -    uint32_t flow_pkt_mark;
> +    uint32_t flow_pkt_mark, flow_ct_mark;
>      uint8_t flow_ct_state;
>      uint16_t flow_ct_zone;
>      uint8_t flow_nw_tos;
> @@ -2971,6 +2972,7 @@ compose_output_action__(struct xlate_ctx *ctx, 
> ofp_port_t ofp_port,
>      flow_pkt_mark = flow->pkt_mark;
>      flow_ct_state = flow->ct_state;
>      flow_ct_zone = flow->ct_zone;
> +    flow_ct_mark = flow->ct_mark;

Are the ct_* fields actually changed here, i.e., do they need to be preserved? 
>      flow_nw_tos = flow->nw_tos;
>  
>      if (count_skb_priorities(xport)) {
> @@ -3095,6 +3097,7 @@ compose_output_action__(struct xlate_ctx *ctx, 
> ofp_port_t ofp_port,
>      flow->pkt_mark = flow_pkt_mark;
>      flow->ct_state = flow_ct_state;
>      flow->ct_zone = flow_ct_zone;
> +    flow->ct_mark = flow_ct_mark;
>      flow->nw_tos = flow_nw_tos;
>  }
>  
> @@ -4152,6 +4155,28 @@ recirc_unroll_actions(const struct ofpact *ofpacts, 
> size_t ofpacts_len,
>      }
>  
>  static void
> +put_ct_mark(const struct flow *flow, struct flow *base_flow,
> +            struct ofpbuf *odp_actions, struct flow_wildcards *wc)
> +{
> +    uint32_t base;
> +    struct {
> +        uint32_t key;
> +        uint32_t mask;
> +    } odp_attr;
> +
> +    base = base_flow->ct_mark;
> +    odp_attr.key = flow->ct_mark;
> +    odp_attr.mask = wc->masks.ct_mark;
> +
> +    if (odp_attr.mask && odp_attr.key != base) {

What if we set the mark in two consecutive conntrack actions, where the second 
one is the same value as in the base flow initially (i.e., 1). Since the mark 
in the second conntrack is the same as in base, it would not be “put”, so the 
second conntrack action (possibly in a different zone) would not include the 
mark at all?
> +        nl_msg_put_unspec(odp_actions, OVS_CT_ATTR_MARK, &odp_attr,
> +                          sizeof(odp_attr));
> +        base_flow->ct_mark = base;
> +        wc->masks.ct_mark = odp_attr.mask;
Did we not just assign these in the other direction above?
> +    }
> +}
> +
> +static void
>  compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
>  {
>      uint32_t flags = 0;
> @@ -4177,6 +4202,7 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct 
> ofpact_conntrack *ofc)
>      ct_offset = nl_msg_start_nested(ctx->odp_actions, OVS_ACTION_ATTR_CT);
>      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);
>      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 51ab3f0..78bb4c8 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -1263,6 +1263,7 @@ check_##NAME(struct dpif_backer *backer)                
>                     \
>  
>  CHECK_FEATURE(ct_state)
>  CHECK_FEATURE(ct_zone)
> +CHECK_FEATURE(ct_mark)
>  
>  #undef CHECK_FEATURE
>  #undef CHECK_FEATURE__
> @@ -1281,6 +1282,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);
>  }
>  
>  static int
> @@ -3987,7 +3989,8 @@ rule_check(struct rule *rule)
>      support = &ofproto_dpif_get_support(ofproto)->odp;
>  
>      if ((match.wc.masks.ct_state && !support->ct_state)
> -        || (match.wc.masks.ct_zone && !support->ct_zone)) {
> +        || (match.wc.masks.ct_zone && !support->ct_zone)
> +        || (match.wc.masks.ct_mark && !support->ct_mark)) {
>          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 892f6fc..87ef80d 100644
> --- a/ofproto/ofproto-unixctl.man
> +++ b/ofproto/ofproto-unixctl.man
> @@ -107,6 +107,8 @@ Mark of the packet.
>  Connection state of the packet.
>  .IP \fIct_zone\fR
>  Connection tracking zone for packet.
> +.IP \fIct_mark\fR
> +Connection mark 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 43108e7..502416f 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,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,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 74d44ff..bf3fa8a 100644
> --- a/tests/odp.at
> +++ b/tests/odp.at
> @@ -70,6 +70,10 @@ 
> s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
>  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
> +
> + echo
>   echo '# Valid forms with IP first fragment.'
>  sed 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/' odp-base.txt 
> | sed -n 's/,frag=no),/,frag=first),/p'
>  
> @@ -89,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),\2/' 
> odp-out.txt
> +sed -i 
> 's/\(skb_mark([[^)]]*)\),\(recirc\)/\1,ct_state(0),ct_zone(0),ct_mark(0),\2/' 
> odp-out.txt
>  
>  AT_CHECK_UNQUOTED([ovstest test-odp parse-keys < odp-in.txt], [0], [`cat 
> odp-out.txt`
>  ])
> @@ -159,7 +163,7 @@ s/$/)/' odp-base.txt
>  
>   echo
>   echo '# Valid forms with conntrack fields.'
> - sed 's/\(eth([[^)]]*),?\)/\1,ct_state(+trk),/' odp-base.txt
> + sed 
> 's/\(eth([[^)]]*),?\)/\1,ct_state(+trk),ct_mark(0x12345678\/0xFOFOFOFO),/' 
> odp-base.txt
>  
>   echo
>   echo '# Valid forms with IP first fragment.'
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 1e3013b..ce214d9 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,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,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,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
>  ])
>  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 c522a85..120a991 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 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 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
> @@ -1611,6 +1611,7 @@ head_table () {
>        pkt_mark: arbitrary mask
>        ct_state: arbitrary mask
>        ct_zone: exact match or wildcard
> +      ct_mark: arbitrary mask
>        reg0: arbitrary mask
>        reg1: arbitrary mask
>        reg2: arbitrary mask
> diff --git a/tests/system-traffic.at b/tests/system-traffic.at
> index de6b016..e7c6846 100644
> --- a/tests/system-traffic.at
> +++ b/tests/system-traffic.at
> @@ -564,6 +564,137 @@ TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> 
> dport=<cleared> src=10.1.1.2
>  OVS_TRAFFIC_VSWITCHD_STOP
>  AT_CLEANUP
>  
> +AT_SETUP([conntrack - ct_mark])
> +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 ns2 and 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_mark)),2
> +in_port=2,ct_state=-trk,tcp,action=ct(table=0)
> +in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1
> +in_port=3,tcp,action=ct(commit,exec(set_field:2->ct_mark)),4
> +in_port=4,ct_state=-trk,tcp,action=ct(table=0)
> +in_port=4,ct_state=+trk,ct_mark=1,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])
> +
> +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep TIME], [0], [dnl
> +TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> 
> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=1 
> use=1
> +])
> +
> +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])
> +
> +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.4)], [0], [dnl
> +SYN_RECV src=10.1.1.3 dst=10.1.1.4 sport=<cleared> dport=<cleared> 
> src=10.1.1.4 dst=10.1.1.3 sport=<cleared> dport=<cleared> mark=2 use=1
> +])
> +
> +OVS_TRAFFIC_VSWITCHD_STOP
> +AT_CLEANUP
> +
> +AT_SETUP([conntrack - ct_mark from register])
> +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.
> +AT_DATA([flows.txt], [dnl
> +priority=1,action=drop
> +priority=10,arp,action=normal
> +priority=10,icmp,action=normal
> +in_port=1,tcp,action=load:1->NXM_NX_REG0[[0..31]],ct(commit,exec(move:NXM_NX_REG0[[0..31]]->NXM_NX_CT_MARK[[]])),2
> +in_port=2,ct_state=-trk,tcp,action=ct(table=0)
> +in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1
> +in_port=3,tcp,action=load:2->NXM_NX_REG0[[0..31]],ct(commit,exec(move:NXM_NX_REG0[[0..31]]->NXM_NX_CT_MARK[[]])),4
> +in_port=4,ct_state=-trk,tcp,action=ct(table=0)
> +in_port=4,ct_state=+trk,ct_mark=1,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-l7.py]], [http0.pid])
> +NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o 
> wget0.log])
> +
> +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep TIME], [0], [dnl
> +TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=<cleared> dport=<cleared> 
> src=10.1.1.2 dst=10.1.1.1 sport=<cleared> dport=<cleared> [[ASSURED]] mark=1 
> use=1
> +])
> +
> +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])
> +
> +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.4)], [0], [dnl
> +SYN_RECV src=10.1.1.3 dst=10.1.1.4 sport=<cleared> dport=<cleared> 
> src=10.1.1.4 dst=10.1.1.3 sport=<cleared> dport=<cleared> mark=2 use=1
> +])
> +
> +OVS_TRAFFIC_VSWITCHD_STOP
> +AT_CLEANUP
> +
> +AT_SETUP([conntrack - ICMP related])
> +CHECK_CONNTRACK()
> +OVS_TRAFFIC_VSWITCHD_START(
> +   [set-fail-mode br0 secure -- ])
> +
> +ADD_NAMESPACES(at_ns0, at_ns1)
> +
> +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
> +ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
> +
> +dnl Allow UDP traffic from ns0->ns1. Only allow related ICMP responses back.
> +AT_DATA([flows.txt], [dnl
> +priority=1,action=drop
> +priority=10,arp,action=normal
> +in_port=1,udp,action=ct(commit,exec(set_field:1->ct_mark)),2
> +in_port=2,icmp,ct_state=-trk,action=ct(table=0)
> +in_port=2,icmp,ct_state=+trk+rel,ct_mark=1,action=1
“+trk” not needed here?
> +])
> +
> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
> +
> +dnl UDP packets from ns0->ns1 should solicit "destination unreachable" 
> response.
> +dnl We pass "-q 1" here to handle openbsd-style nc that can't quit 
> immediately.
> +NS_CHECK_EXEC([at_ns0], [bash -c "echo a | nc -q 1 -u 10.1.1.2 10000"])
> +
> +AT_CHECK([ovs-appctl revalidator/purge], [0])
> +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort | grep -v drop], 
> [0], [dnl
> + n_packets=1, n_bytes=44, udp,in_port=1 
> actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[[]])),output:2
> + n_packets=1, n_bytes=72, ct_state=+rel+trk,ct_mark=0x1,icmp,in_port=2 
> actions=output:1
> + n_packets=1, n_bytes=72, ct_state=-trk,icmp,in_port=2 actions=ct(table=0)
> + n_packets=2, n_bytes=84, priority=10,arp actions=NORMAL
> +NXST_FLOW reply:
> +])
> +
> +OVS_TRAFFIC_VSWITCHD_STOP
> +AT_CLEANUP
> +
>  AT_SETUP([conntrack - ICMP related 2])
>  CHECK_CONNTRACK()
>  OVS_TRAFFIC_VSWITCHD_START(
> diff --git a/tests/test-odp.c b/tests/test-odp.c
> index cb245d6..1c65590 100644
> --- a/tests/test-odp.c
> +++ b/tests/test-odp.c
> @@ -61,6 +61,7 @@ parse_keys(bool wc_keys)
>                      .recirc = true,
>                      .ct_state = true,
>                      .ct_zone = true,
> +                    .ct_mark = true,
>                  },
>              };
>  
> diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
> index 7e5fcaa..31644b3 100644
> --- a/utilities/ovs-ofctl.8.in
> +++ b/utilities/ovs-ofctl.8.in
> @@ -1364,6 +1364,10 @@ by using the \fBct\fR action.
>  .IP \fBct_zone=\fIvalue
>  Matches connection zone \fIvalue\fR exactly.
>  .
> +.IP \fBct_mark=\fIvalue\fR[\fB/\fImask\fR]
> +Matches connection mark \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
> @@ -1622,6 +1626,10 @@ is not provided, then the default is to use zone zero. 
> The \fBzone\fR may be
>  specified either as an immediate 16-bit \fIvalue\fR, or may be provided from 
> an
>  NXM field \fIsrc\fR. The \fIstart\fR and \fIend\fR pair are inclusive, and 
> must
>  specify a 16-bit range within the field.
> +.IP \fBmark=\fR\fImark\fR[/\fImask\fR]
> +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.

table?
>  .RE
>  .IP
>  Currently, connection tracking is only available on Linux kernels with the

_______________________________________________
dev mailing list
[email protected]
http://openvswitch.org/mailman/listinfo/dev

Reply via email to