the next OVS release.
- Added --user option to all daemons
- 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
/* 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 */
* the accepted length of the array. */
OVS_KEY_ATTR_CT_STATE, /* u32 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. */
* table. This allows future packets for the same connection to be identified
* as 'established' or 'related'.
* @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.
*/
enum ovs_ct_attr {
OVS_CT_ATTR_UNSPEC,
OVS_CT_ATTR_COMMIT, /* No argument, commits connection. */
OVS_CT_ATTR_ZONE, /* u16 zone id. */
+ OVS_CT_ATTR_MARK, /* mark to associate with this connection. */
__OVS_CT_ATTR_MAX
};
}
/* 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;
}
miniflow_push_uint16(mf, ct_zone, md->ct_zone);
}
+ if (md->ct_state) {
+ miniflow_push_uint32(mf, ct_mark, md->ct_mark);
+ miniflow_pad_to_64(mf, pad1);
+ }
+
/* Initialize packet's layer pointer and offsets. */
l2 = data;
dp_packet_reset_offsets(packet);
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);
+ }
}
const char *ct_state_to_string(uint32_t state)
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]);
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);
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))) {
uint32_t recirc_id; /* Must be exact match. */
uint16_t ct_state; /* Connection tracking state. */
uint16_t ct_zone; /* Connection tracking zone. */
+ uint32_t ct_mark; /* Connection mark.*/
+ uint8_t pad1[4]; /* Pad to 64 bits. */
uint32_t conj_id; /* Conjunction ID. */
ofp_port_t actset_output; /* Output port in action set. */
- uint8_t pad1[2]; /* Pad to 64 bits. */
+ uint8_t pad2[2]; /* Pad to 64 bits. */
/* L2, Order the same as in the Ethernet header! (64-bit aligned) */
struct eth_addr dl_dst; /* Ethernet destination address. */
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. */
/* 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 == 34);
/* Incremental points at which flow classification may be performed in
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)
match->wc.masks.ct_zone = UINT16_MAX;
}
+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)
{
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)) {
void match_set_ct_state(struct match *, uint32_t ct_state);
void match_set_ct_state_masked(struct match *, uint32_t ct_state, uint32_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 );
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:
case MFF_SKB_PRIORITY:
case MFF_PKT_MARK:
case MFF_CT_ZONE:
+ case MFF_CT_MARK:
CASE_MFF_REGS:
CASE_MFF_XREGS:
case MFF_ETH_SRC:
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;
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;
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;
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;
match_set_ct_state_masked(match, ntohl(value->be32), ntohl(mask->be32));
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;
*/
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.
+ *
+ * Writable only from nested actions within the NXAST_CT action.
+ *
+ * Type: be32.
+ * Maskable: bitwise.
+ * Formatting: hexadecimal.
+ * Prerequisites: none.
+ * Access: read/write.
+ * NXM: NXM_NX_CT_MARK(107) since v2.5.
+ * OXM: none.
+ */
+ MFF_CT_MARK,
+
#if FLOW_N_REGS == 8
/* "reg<N>".
*
}
}
- /* Mark. */
+ /* Packet mark. */
nxm_put_32m(b, MFF_PKT_MARK, oxm, htonl(flow->pkt_mark),
htonl(match->wc.masks.pkt_mark));
htonl(match->wc.masks.ct_state));
nxm_put_16m(b, MFF_CT_ZONE, oxm, htons(flow->ct_zone),
htons(match->wc.masks.ct_zone));
+ 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,
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();
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:
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";
static const struct nl_policy ovs_conntrack_policy[] = {
[OVS_CT_ATTR_COMMIT] = { .type = NL_A_FLAG, .optional = true, },
[OVS_CT_ATTR_ZONE] = { .type = NL_A_U16, .optional = true, },
+ [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)];
- bool commit;
+ const uint32_t *mark;
uint16_t zone;
+ bool commit;
if (!nl_parse_nested(attr, ovs_conntrack_policy, a, ARRAY_SIZE(a))) {
ds_put_cstr(ds, "ct(error)");
commit = a[OVS_CT_ATTR_COMMIT] ? true : false;
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 (commit || zone) {
+ if (commit || zone || mark) {
ds_put_cstr(ds, "(");
if (commit) {
ds_put_format(ds, "commit,");
if (zone) {
ds_put_format(ds, "zone=%"PRIu16",", zone);
}
+ if (mark) {
+ ds_put_format(ds, "mark=%#"PRIx32"/%#"PRIx32",", *mark,
+ *(mark + 1));
+ }
ds_chomp(ds, ',');
ds_put_cstr(ds, ")");
}
if (ovs_scan(s, "ct")) {
bool commit = false;
uint16_t zone = 0;
+ struct {
+ uint32_t value;
+ uint32_t mask;
+ } ct_mark = { 0, 0 };
size_t start;
char *end;
s += n;
continue;
}
+ if (ovs_scan(s, "mark=%"SCNx32"%n", &ct_mark.value, &n)) {
+ s += n;
+ n = -1;
+ if (ovs_scan(s, "/%"SCNx32"%n", &ct_mark.mask, &n)) {
+ s += n;
+ } else {
+ ct_mark.mask = UINT32_MAX;
+ }
+ continue;
+ }
return -EINVAL;
}
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);
}
[OVS_KEY_ATTR_ND] = { .len = sizeof(struct ovs_key_nd) },
[OVS_KEY_ATTR_CT_STATE] = { .len = 4 },
[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
}
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 (verbose) {
ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a));
SCAN_SINGLE("ct_state(", uint32_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);
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);
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
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;
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;
* OVS_KEY_ATTR_RECIRC_ID 4 -- 4 8
* OVS_KEY_ATTR_CT_STATE 4 -- 4 8
* OVS_KEY_ATTR_CT_ZONE 2 2 4 8
+ * OVS_KEY_ATTR_CT_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
* 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 == 34);
/* A buffer with sufficient size and alignment to hold an nlattr-formatted flow
/* If true, serialise the corresponding OVS_KEY_ATTR_CONN_* field. */
bool ct_state;
bool ct_zone;
+ bool ct_mark;
};
struct odp_flow_key_parms {
struct ofpbuf *openflow, unsigned int actions_len,
enum ofp_version version, uint32_t allowed_ovsinsts,
struct ofpbuf *ofpacts, enum ofpact_type outer_action);
+static char * OVS_WARN_UNUSED_RESULT ofpacts_parse_copy(
+ const char *s_, struct ofpbuf *ofpacts,
+ enum ofputil_protocol *usable_protocols,
+ bool allow_instructions, enum ofpact_type outer_action);
/* Pull off existing actions or instructions. Used by nesting actions to keep
* ofpacts_parse() oblivious of actions nesting.
*
* It is strongly recommended that this table is later than the current
* table, to prevent loops.
+ *
+ * Zero or more actions may immediately follow this action. These actions will
+ * be executed within the context of the connection tracker, and they require
+ * the NX_CT_F_COMMIT flag to be set.
*/
struct nx_action_conntrack {
ovs_be16 type; /* OFPAT_VENDOR. */
static enum ofperr
decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac,
- enum ofp_version ofp_version OVS_UNUSED,
- struct ofpbuf *out)
+ enum ofp_version ofp_version, struct ofpbuf *out)
{
+ const size_t ct_offset = ofpacts_pull(out);
struct ofpact_conntrack *conntrack;
+ struct ofpbuf openflow;
int error = 0;
conntrack = ofpact_put_CT(out);
}
conntrack->recirc_table = nac->recirc_table;
+ ofpbuf_pull(out, sizeof(*conntrack));
+
+ ofpbuf_use_const(&openflow, nac + 1, ntohs(nac->len) - sizeof(*nac));
+ error = ofpacts_pull_openflow_actions__(&openflow, openflow.size,
+ ofp_version,
+ 1u << OVSINST_OFPIT11_APPLY_ACTIONS,
+ out, OFPACT_CT);
+ if (error) {
+ goto out;
+ }
+
+ conntrack = ofpbuf_push_uninit(out, sizeof(*conntrack));
+ out->header = &conntrack->ofpact;
+ ofpact_update_len(out, &conntrack->ofpact);
+
+ if (conntrack->ofpact.len > sizeof(*conntrack)
+ && !(conntrack->flags & NX_CT_F_COMMIT)) {
+ VLOG_WARN_RL(&rl, "CT action requires commit flag if actions are "
+ "specified.");
+ error = OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+
out:
+ ofpbuf_push_uninit(out, ct_offset);
return error;
}
static void
encode_CT(const struct ofpact_conntrack *conntrack,
- enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+ enum ofp_version ofp_version, struct ofpbuf *out)
{
struct nx_action_conntrack *nac;
+ const size_t ofs = out->size;
+ size_t len;
nac = put_NXAST_CT(out);
nac->flags = htons(conntrack->flags);
nac->zone_imm = htons(conntrack->zone_imm);
}
nac->recirc_table = conntrack->recirc_table;
+
+ len = ofpacts_put_openflow_actions(conntrack->actions,
+ ofpact_ct_get_action_len(conntrack),
+ out, ofp_version);
+ len += sizeof(*nac);
+ nac = ofpbuf_at(out, ofs, sizeof(*nac));
+ nac->len = htons(len);
}
/* Parses 'arg' as the argument to a "ct" action, and appends such an
* error. The caller is responsible for freeing the returned string. */
static char * OVS_WARN_UNUSED_RESULT
parse_CT(char *arg, struct ofpbuf *ofpacts,
- enum ofputil_protocol *usable_protocols OVS_UNUSED)
+ enum ofputil_protocol *usable_protocols)
{
+ const size_t ct_offset = ofpacts_pull(ofpacts);
struct ofpact_conntrack *oc;
char *error = NULL;
char *key, *value;
return error;
}
}
+ } else if (!strcmp(key, "exec")) {
+ /* Hide existing actions from ofpacts_parse_copy(), so the
+ * nesting can be handled transparently. */
+ ofpbuf_pull(ofpacts, sizeof(*oc));
+ error = ofpacts_parse_copy(value, ofpacts, usable_protocols, false,
+ OFPACT_CT);
+ ofpact_pad(ofpacts);
+ ofpacts->header = ofpbuf_push_uninit(ofpacts, sizeof(*oc));
+ oc = ofpacts->header;
} else {
error = xasprintf("invalid argument to \"ct\" action: `%s'", key);
}
}
}
+ ofpact_update_len(ofpacts, &oc->ofpact);
+ ofpbuf_push_uninit(ofpacts, ct_offset);
return error;
}
} else if (a->zone_imm) {
ds_put_format(s, "zone=%"PRIu16",", a->zone_imm);
}
+ if (ofpact_ct_get_action_len(a)) {
+ ds_put_cstr(s, "exec(");
+ ofpacts_format(a->actions, ofpact_ct_get_action_len(a), s);
+ ds_put_format(s, "),");
+ }
ds_chomp(s, ',');
ds_put_char(s, ')');
}
case OFPACT_CT: {
struct ofpact_conntrack *oc = ofpact_get_CT(a);
+ enum ofputil_protocol p = *usable_protocols;
if (!dl_type_is_ip_any(flow->dl_type)
|| (flow->ct_state & CS_INVALID && oc->flags & NX_CT_F_COMMIT)) {
if (oc->zone_src.field) {
return mf_check_src(&oc->zone_src, flow);
}
- return 0;
+
+ return ofpacts_check(oc->actions, ofpact_ct_get_action_len(oc),
+ flow, max_ports, table_id, n_tables, &p);
}
case OFPACT_CLEAR_ACTIONS:
: 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 bool
+field_requires_ct(enum mf_field_id field)
+{
+ return field == MFF_CT_MARK;
+}
+
+/* Apply nesting constraints for actions */
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 = ofpact_get_mf_field(a->type, a);
+
+ if (field && field_requires_ct(field->id) && outer_action != OFPACT_CT) {
+ VLOG_WARN("cannot set CT fields outside of ct action");
+ return OFPERR_OFPBAC_BAD_SET_ARGUMENT;
+ }
+
+ if (outer_action) {
+ ovs_assert(outer_action == OFPACT_WRITE_ACTIONS
+ || outer_action == OFPACT_CT);
+
+ if (outer_action == OFPACT_CT) {
+ if (!field) {
+ return unsupported_nesting(a->type, outer_action);
+ } else if (!field_requires_ct(field->id)) {
+ VLOG_WARN("%s action doesn't support nested modification "
+ "of %s", ofpact_name(outer_action), field->name);
+ return OFPERR_OFPBAC_BAD_ARGUMENT;
+ }
+ }
}
return 0;
inst = OVSINST_OFPIT13_METER;
OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
enum ovs_instruction_type next;
+ enum ofperr error;
if (a->type == OFPACT_CONJUNCTION) {
OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
return 0;
}
- if (outer_action) {
- enum ofperr error = ofpacts_verify_nested(a, outer_action);
-
- if (error) {
- return error;
- }
+ error = ofpacts_verify_nested(a, outer_action);
+ if (error) {
+ return error;
}
next = ovs_instruction_type_from_ofpact_type(a->type);
* that the packet should not be recirculated. */
#define NX_CT_RECIRC_NONE OFPTT_ALL
+/* We want to determine the size of these elements at compile time to ensure
+ * actions alignment, but we also want to allow ofpact_conntrack to have
+ * basic _put(), _get(), etc accessors defined below which access these
+ * members directly from ofpact_conntrack. An anonymous struct will serve
+ * both of these purposes. */
+#define CT_MEMBERS \
+struct { \
+ struct ofpact ofpact; \
+ uint16_t flags; \
+ uint16_t zone_imm; \
+ struct mf_subfield zone_src; \
+ uint8_t recirc_table; \
+}
+
/* OFPACT_CT.
*
* Used for NXAST_CT. */
struct ofpact_conntrack {
- struct ofpact ofpact;
- uint16_t flags;
- uint16_t zone_imm;
- struct mf_subfield zone_src;
- uint8_t recirc_table;
+ union {
+ CT_MEMBERS;
+ uint8_t pad[OFPACT_ALIGN(sizeof(CT_MEMBERS))];
+ };
+ struct ofpact actions[];
};
+BUILD_ASSERT_DECL(offsetof(struct ofpact_conntrack, actions)
+ % OFPACT_ALIGNTO == 0);
+BUILD_ASSERT_DECL(offsetof(struct ofpact_conntrack, actions)
+ == sizeof(struct ofpact_conntrack));
+
+static inline size_t
+ofpact_ct_get_action_len(const struct ofpact_conntrack *oc)
+{
+ return oc->ofpact.len - offsetof(struct ofpact_conntrack, actions);
+}
static inline size_t
ofpact_nest_get_action_len(const struct ofpact_nest *on)
uint32_t pkt_mark; /* Packet mark. */
uint16_t ct_state; /* Connection state. */
uint16_t ct_zone; /* Connection zone. */
+ uint32_t ct_mark; /* Connection mark. */
union flow_in_port in_port; /* Input port. */
struct flow_tnl tunnel; /* Encapsulating tunnel parameters. Note that
* if 'ip_dst' == 0, the rest of the fields may
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:
{
flow->ct_state = 0;
flow->ct_zone = 0;
+ flow->ct_mark = 0;
}
static void
CHECK_MPLS_RECIRCULATION(); \
}
+static void
+put_ct_mark(const struct flow *flow, struct flow *base_flow,
+ struct ofpbuf *odp_actions, struct flow_wildcards *wc)
+{
+ struct {
+ uint32_t key;
+ uint32_t mask;
+ } odp_attr;
+
+ odp_attr.key = flow->ct_mark;
+ odp_attr.mask = wc->masks.ct_mark;
+
+ if (odp_attr.mask && odp_attr.key != base_flow->ct_mark) {
+ nl_msg_put_unspec(odp_actions, OVS_CT_ATTR_MARK, &odp_attr,
+ sizeof(odp_attr));
+ }
+}
+
static void
compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc)
{
+ uint32_t old_ct_mark = ctx->base_flow.ct_mark;
size_t ct_offset;
uint16_t zone;
* conntrack action. */
xlate_commit_actions(ctx);
+ /* Process nested actions first, to populate the key. */
+ do_xlate_actions(ofc->actions, ofpact_ct_get_action_len(ofc), ctx);
+
if (ofc->zone_src.field) {
zone = mf_get_subfield(&ofc->zone_src, &ctx->xin->flow);
} else {
nl_msg_put_flag(ctx->odp_actions, OVS_CT_ATTR_COMMIT);
}
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);
+ /* Restore the original ct fields in the key. These should only be exposed
+ * after recirculation to another table. */
+ ctx->base_flow.ct_mark = old_ct_mark;
+
if (ofc->recirc_table == NX_CT_RECIRC_NONE) {
/* If we do not recirculate as part of this action, hide the results of
* connection tracking from subsequent recirculations. */
CHECK_FEATURE(ct_state)
CHECK_FEATURE(ct_zone)
+CHECK_FEATURE(ct_mark)
#undef CHECK_FEATURE
#undef CHECK_FEATURE__
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
rule_check(struct rule *rule)
{
uint16_t ct_state, ct_zone;
+ uint32_t ct_mark;
ct_state = MINIFLOW_GET_U16(rule->cr.match.flow, ct_state);
ct_zone = MINIFLOW_GET_U16(rule->cr.match.flow, ct_zone);
+ ct_mark = MINIFLOW_GET_U32(rule->cr.match.flow, ct_mark);
- if (ct_state || ct_zone) {
+ if (ct_state || ct_zone || ct_mark) {
struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->ofproto);
const struct odp_support *support = &ofproto_dpif_get_support(ofproto)->odp;
if ((ct_state && !support->ct_state)
- || (ct_zone && !support->ct_zone)) {
+ || (ct_zone && !support->ct_zone)
+ || (ct_mark && !support->ct_mark)) {
return OFPERR_OFPBMC_BAD_FIELD;
}
if (ct_state & CS_UNSUPPORTED_MASK) {
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
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>
])
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'
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`
])
echo
echo '# Valid forms with conntrack fields.'
- sed 's/\(eth([[^)]]*)\),/\1,ct_state(+trk),ct_zone(0x5\/0xff),/' odp-base.txt
+ sed 's/\(eth([[^)]]*)\),/\1,ct_state(+trk),ct_zone(0x5\/0xff),ct_mark(0x10305070\/0xf0f0f0f0),/' odp-base.txt
echo
echo '# Valid forms with IP first fragment.'
ct
ct(commit)
ct(commit,zone=5)
+ct(commit,mark=0xa0a0a0a0/0xfefefefe)
])
AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0],
[`cat actions.txt`
# bad OpenFlow10 actions: OFPBAC_BAD_SET_LEN
ffff 0018 00002320 0023 0000 00010004 001F FF 000000 0000
+# actions=ct(commit,exec(load:0xf009->NXM_NX_CT_MARK[]))
+ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0018 00002320 0007 001f 0001d604 000000000000f009
+
+# bad OpenFlow10 actions: OFPBAC_BAD_SET_ARGUMENT
+& ofp_actions|WARN|cannot set CT fields outside of ct action
+ffff 0018 00002320 0007 001f 0001d604 000000000000f009
+
+# bad OpenFlow10 actions: OFPBAC_BAD_SET_ARGUMENT
+& meta_flow|WARN|destination field ct_zone is not writable
+ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0018 00002320 0007 000f 0001d504 000000000000f009
+
+# bad OpenFlow10 actions: OFPBAC_BAD_ARGUMENT
+& ofp_actions|WARN|ct action doesn't support nested action ct
+ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0018 00002320 0023 0000 00000000 0000 FF 000000 0000
+
+# bad OpenFlow10 actions: OFPBAC_BAD_ARGUMENT
+& ofp_actions|WARN|ct action doesn't support nested modification of reg0
+ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl
+ffff 0018 00002320 0007 001f 00010004 000000000000f009
+
])
sed '/^[[#&]]/d' < test-data > input.txt
sed -n 's/^# //p; /^$/p' < test-data > expout
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
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 dnl
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 dnl
-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
+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
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
'icmp6,icmpv6_type=1 NXM,OXM' \
'icmp6,icmpv6_code=2 NXM,OXM' \
'ct_state=+trk NXM,OXM' \
- 'ct_zone=0 NXM,OXM'
+ 'ct_zone=0 NXM,OXM' \
+ 'ct_mark=0 NXM,OXM'
do
set $test_case
echo
actions=controller(max_len=123,reason=invalid_ttl,id=555)
actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
ip,actions=ct(commit,zone=5)
+ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[])))
]])
AT_CHECK([ovs-ofctl parse-flows flows.txt
NXT_FLOW_MOD: ADD table:255 actions=controller(reason=invalid_ttl,max_len=123,id=555)
NXT_FLOW_MOD: ADD table:255 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,zone=5)
+NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[]))
]])
AT_CLEANUP
dl_dst=aa:bb:cc:dd:ee:ff/00:00:00:00:00:00,actions=drop
actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
ip,actions=ct(commit,zone=5)
+ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[[]])))
])
AT_CHECK([ovs-ofctl -F nxm parse-flows flows.txt], [0], [stdout])
AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0], [dnl
NXT_FLOW_MOD: ADD actions=drop
NXT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
NXT_FLOW_MOD: ADD ip actions=ct(commit,zone=5)
+NXT_FLOW_MOD: ADD ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[[]]))
])
AT_CLEANUP
vlan_tci=0x1123/0x1fff,actions=drop
actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
ip,actions=ct(commit,zone=5)
+ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[])))
]])
AT_CHECK([ovs-ofctl -F nxm -mmm parse-flows flows.txt], [0], [stdout], [stderr])
AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
NXT_FLOW_MOD: ADD NXM_OF_VLAN_TCI_W(1123/1fff) actions=drop
NXT_FLOW_MOD: ADD <any> actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800) actions=ct(commit,zone=5)
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800) actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[]))
]])
AT_CLEANUP
NXM_OF_ETH_TYPE(0800) NXM_NX_CT_STATE_W(00000080/00000080)
NXM_OF_ETH_TYPE(0800) NXM_NX_CT_STATE_W(00000080/000000F0)
NXM_OF_ETH_TYPE(0800) NXM_NX_CT_ZONE(5a5a)
+NXM_OF_ETH_TYPE(0800) NXM_NX_CT_MARK(5a5a5a5a)
+NXM_OF_ETH_TYPE(0800) NXM_NX_CT_MARK_W(5a5a5a5a/fefefefe)
# dp_hash (testing experimenter OXM).
NXM_NX_DP_HASH(01234567)
NXM_OF_ETH_TYPE(0800), NXM_NX_CT_STATE_W(00000080/00000080)
NXM_OF_ETH_TYPE(0800), NXM_NX_CT_STATE_W(00000080/000000f0)
NXM_OF_ETH_TYPE(0800), NXM_NX_CT_ZONE(5a5a)
+NXM_OF_ETH_TYPE(0800), NXM_NX_CT_MARK(5a5a5a5a)
+NXM_OF_ETH_TYPE(0800), NXM_NX_CT_MARK_W(5a5a5a5a/fefefefe)
# dp_hash (testing experimenter OXM).
NXM_NX_DP_HASH(01234567)
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 traffic between ns0<->ns1 using the ct_mark.
+dnl Check that different marks do not match for traffic between ns2<->ns3.
+AT_DATA([flows.txt], [dnl
+priority=1,action=drop
+priority=10,arp,action=normal
+priority=10,icmp,action=normal
+priority=100,in_port=1,tcp,action=ct(commit,exec(set_field:1->ct_mark)),2
+priority=100,in_port=2,ct_state=-trk,tcp,action=ct(table=0)
+priority=100,in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1
+priority=100,in_port=3,tcp,action=ct(commit,exec(set_field:2->ct_mark)),4
+priority=100,in_port=4,ct_state=-trk,tcp,action=ct(table=0)
+priority=100,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 - 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
+priority=100,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
+priority=100,in_port=2,ct_state=-trk,tcp,action=ct(table=0)
+priority=100,in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1
+priority=100,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
+priority=100,in_port=4,ct_state=-trk,tcp,action=ct(table=0)
+priority=100,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
+priority=100,in_port=1,udp,action=ct(commit,exec(set_field:1->ct_mark)),2
+priority=100,in_port=2,icmp,ct_state=-trk,action=ct(table=0)
+priority=100,in_port=2,icmp,ct_state=+trk+rel,ct_mark=1,action=1
+])
+
+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, priority=100,udp,in_port=1 actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[[]])),output:2
+ n_packets=1, n_bytes=72, priority=100,ct_state=+rel+trk,ct_mark=0x1,icmp,in_port=2 actions=output:1
+ n_packets=1, n_bytes=72, priority=100,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(
.recirc = true,
.ct_state = true,
.ct_zone = true,
+ .ct_mark = true,
},
};
the same packet in multiple contexts then you must use the \fBct\fR action
multiple times. Introduced in Open vSwitch 2.5.
.
+.IP \fBct_mark=\fIvalue\fR[\fB/\fImask\fR]
+Matches the given 32-bit connection mark \fIvalue\fR either exactly or with
+optional \fImask\fR. This represents metadata associated with the connection
+that the current packet is part of. Introduced in Open vSwitch 2.5.
+.
.PP
Defining IPv6 flows (those with \fBdl_type\fR equal to 0x86dd) requires
support for NXM. The following shorthand notations are available for
domains, allowing overlapping network addresses in different zones. If a zone
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
-OpenFlow field \fIsrc\fR. The \fIstart\fR and \fIend\fR pair are inclusive, and
-must specify a 16-bit range within the field.
+NXM field \fIsrc\fR. The \fIstart\fR and \fIend\fR pair are inclusive, and must
+specify a 16-bit range within the field.
+.IP \fBexec\fB(\fR[\fIaction\fR][\fB,\fIaction\fR...]\fB)\fR
+Perform actions within the context of connection tracking. These actions
+are in the same format as the actions accepted as part of a flow, however
+there are additional restrictions applied. For instance, only actions which
+modify the ct fields are accepted within the \fBexec\fR action. Furthermore,
+some actions may only be performed in this context, for instance modifying the
+ct_mark field:
+.
+.RS
+.IP \fBset_field:\fIvalue\fR->ct_mark\fR
+Store a 32-bit metadata value with the connection. If the connection is
+committed, then subsequent lookups for packets in this connection will
+populate the \fBct_mark\fR flow field when the packet is sent to the
+connection tracker with the \fBtable\fR specified.
+.RE
+.IP
+The \fBcommit\fR parameter must be specified to use \fBexec(...)\fR.
+.
.RE
.IP
The \fBct\fR action may be used as a primitive to construct stateful firewalls
Currently, connection tracking is only available on Linux kernels with the
nf_conntrack module loaded.
.
+.RE
+.
.IP \fBdec_ttl\fR
.IQ \fBdec_ttl\fB[\fR(\fIid1,id2\fI)\fR]\fR
Decrement TTL of IPv4 packet or hop limit of IPv6 packet. If the