Skip to content

Commit

Permalink
openflow: Allow CT flush to match on mark and labels.
Browse files Browse the repository at this point in the history
Extend the current NX_CT_FLUSH with four additional fields,
that allow to match on CT entry "mark" or "labels". This
is encoded as separate TLV values which is backward compatible.
Versions that do not support them will fail the command.

Extend also the ovs-dpctl and ovs-ofctl command line tools with
option to specify those two matching parameters for the "ct-flush"
command.

Reported-at: https://issues.redhat.com/browse/FDP-55
Signed-off-by: Ales Musil <amusil@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
  • Loading branch information
almusil authored and igsilya committed Dec 14, 2023
1 parent 386deb3 commit a095794
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 58 deletions.
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Post-v3.2.0
from older version is supported but it may trigger more leader elections
during the process, and error logs complaining unrecognized fields may
be observed on old nodes.
- OpenFlow:
* NXT_CT_FLUSH extension is updated to support flushing connections
based on mark and labels. 'ct-flush' command of ovs-ofctl updated
to support these new arguments accordingly.
- ovs-appctl:
* 'ofproto/trace' now reports OpenFlow rules that make up a conjunctive
flow match.
Expand All @@ -17,6 +21,8 @@ Post-v3.2.0
Reported names adjusted accordingly.
* Added support for removal of default CT zone limit, e.g.
"ovs-appctl dpctl/ct-del-limits default".
* 'dpctl/flush-conntrack' is now capable of flushing connections based
on mark and labels.
- ovs-vsctl:
* New commands 'set-zone-limit', 'del-zone-limit' and 'list-zone-limits'
to manage the maximum number of connections in conntrack zones via
Expand Down
4 changes: 4 additions & 0 deletions include/openflow/nicira-ext.h
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,10 @@ enum nx_ct_flush_tlv_type {
* by 'enum nx_ct_flush_tuple_tlv_type'*/
/* Primitive types. */
NXT_CT_ZONE_ID = 2, /* be16 zone id. */
NXT_CT_MARK = 3, /* be32 mark. */
NXT_CT_MARK_MASK = 4, /* be32 mark mask. */
NXT_CT_LABELS = 5, /* be128 labels. */
NXT_CT_LABELS_MASK = 6, /* be128 labels mask. */
};

/* CT flush nested TLVs. */
Expand Down
9 changes: 7 additions & 2 deletions include/openvswitch/ofp-ct.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ struct ofp_ct_match {

struct ofp_ct_tuple tuple_orig;
struct ofp_ct_tuple tuple_reply;

uint32_t mark;
uint32_t mark_mask;

ovs_u128 labels;
ovs_u128 labels_mask;
};

bool ofp_ct_match_is_zero(const struct ofp_ct_match *);
bool ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *, uint8_t ip_proto);
bool ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple *, uint8_t ip_proto);
bool ofp_ct_match_is_five_tuple(const struct ofp_ct_match *);

void ofp_ct_match_format(struct ds *, const struct ofp_ct_match *);
bool ofp_ct_match_parse(const char **, int argc, struct ds *,
Expand Down
12 changes: 10 additions & 2 deletions lib/ct-dpif.c
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ ct_dpif_entry_cmp(const struct ct_dpif_entry *entry,
return false;
}

if ((match->mark & match->mark_mask) != (entry->mark & match->mark_mask)) {
return false;
}

if (!ovs_u128_equals(ovs_u128_and(match->labels, match->labels_mask),
ovs_u128_and(entry->labels, match->labels_mask))) {
return false;
}

return true;
}

Expand All @@ -300,8 +309,7 @@ ct_dpif_flush_tuple(struct dpif *dpif, const uint16_t *zone,

/* If we have full five tuple in original and empty reply tuple just
* do the flush over original tuple directly. */
if (ofp_ct_tuple_is_five_tuple(&match->tuple_orig, match->ip_proto) &&
ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto)) {
if (ofp_ct_match_is_five_tuple(match)) {
struct ct_dpif_tuple tuple;

ct_dpif_tuple_from_ofp_ct_tuple(&match->tuple_orig, &tuple,
Expand Down
5 changes: 3 additions & 2 deletions lib/dpctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3005,8 +3005,9 @@ static const struct dpctl_command all_commands[] = {
0, 4, dpctl_dump_conntrack, DP_RO },
{ "dump-conntrack-exp", "[dp] [zone=N]",
0, 2, dpctl_dump_conntrack_exp, DP_RO },
{ "flush-conntrack", "[dp] [zone=N] [ct-orig-tuple] [ct-reply-tuple]",
0, 4, dpctl_flush_conntrack, DP_RW },
{ "flush-conntrack", "[dp] [zone=N] [mark=X[/M]] [labels=Y[/N]] "
"[ct-orig-tuple [ct-reply-tuple]]",
0, 6, dpctl_flush_conntrack, DP_RW },
{ "cache-get-size", "[dp]", 0, 1, dpctl_cache_get_size, DP_RO },
{ "cache-set-size", "dp cache <size>", 3, 3, dpctl_cache_set_size, DP_RW },
{ "ct-stats-show", "[dp] [zone=N]",
Expand Down
135 changes: 132 additions & 3 deletions lib/ofp-ct.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ ofp_ct_tuple_format(struct ds *ds, const struct ofp_ct_tuple *tuple,
}
}

bool
static bool
ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
{
bool is_zero = ipv6_is_zero(&tuple->src) && ipv6_is_zero(&tuple->dst);
Expand All @@ -65,7 +65,7 @@ ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
return is_zero;
}

bool
static bool
ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
{
/* First check if we have address. */
Expand All @@ -78,17 +78,48 @@ ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
return five_tuple;
}

bool
ofp_ct_match_is_five_tuple(const struct ofp_ct_match *match)
{
return ofp_ct_tuple_is_five_tuple(&match->tuple_orig, match->ip_proto) &&
ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto) &&
!match->mark_mask && ovs_u128_is_zero(match->labels_mask);
}

bool
ofp_ct_match_is_zero(const struct ofp_ct_match *match)
{
return !match->ip_proto && !match->l3_type &&
ofp_ct_tuple_is_zero(&match->tuple_orig, match->ip_proto) &&
ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto);
ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto) &&
!match->mark_mask && ovs_u128_is_zero(match->labels_mask);
}

void
ofp_ct_match_format(struct ds *ds, const struct ofp_ct_match *match)
{
if (match->mark_mask) {
ds_put_format(ds, "mark=%#"PRIx32, match->mark);
if (match->mark_mask != UINT32_MAX) {
ds_put_format(ds, "/%#"PRIx32, match->mark_mask);
}
ds_put_char(ds, ' ');
}

if (!ovs_u128_is_zero(match->labels_mask)) {
ovs_be128 be_value = hton128(match->labels);
ovs_be128 be_mask = hton128(match->labels_mask);

ds_put_cstr(ds, "labels=");
ds_put_hex(ds, &be_value, sizeof be_value);

if (!ovs_u128_is_ones(match->labels_mask)) {
ds_put_char(ds, '/');
ds_put_hex(ds, &be_mask, sizeof be_mask);
}
ds_put_char(ds, ' ');
}

ds_put_cstr(ds, "'");
ofp_ct_tuple_format(ds, &match->tuple_orig, match->ip_proto,
match->l3_type);
Expand All @@ -98,6 +129,23 @@ ofp_ct_match_format(struct ds *ds, const struct ofp_ct_match *match)
ds_put_cstr(ds, "'");
}

static inline bool
ofp_ct_masked_parse(const char *s, uint8_t *val, size_t val_len,
uint8_t *mask, size_t mask_len)
{
char *tail;
if (!parse_int_string(s, val, val_len, &tail)) {
if (*tail != '/' || parse_int_string(tail + 1, mask,
mask_len, &tail)) {
memset(mask, UINT8_MAX, mask_len);
}

return true;
}

return false;
}

/* Parses a specification of a conntrack 5-tuple from 's' into 'tuple'.
* Returns true on success. Otherwise, returns false and puts the error
* message in 'ds'. */
Expand Down Expand Up @@ -239,6 +287,40 @@ ofp_ct_match_parse(const char **argv, int argc, struct ds *ds,
args--;
}

/* Parse mark. */
if (args && !strncmp(argv[argc - args], "mark=", 5)) {
const char *s = argv[argc - args] + 5;
ovs_be32 mark_be;
ovs_be32 mask_be;

if (ofp_ct_masked_parse(s, (uint8_t *) &mark_be, sizeof mark_be,
(uint8_t *) &mask_be, sizeof mask_be)) {
match->mark = ntohl(mark_be);
match->mark_mask = ntohl(mask_be);
} else {
ds_put_cstr(ds, "failed to parse mark");
return false;
}
args--;
}

/* Parse labels. */
if (args && !strncmp(argv[argc - args], "labels=", 7)) {
const char *s = argv[argc - args] + 7;
ovs_be128 labels_be;
ovs_be128 mask_be;

if (ofp_ct_masked_parse(s, (uint8_t *) &labels_be, sizeof labels_be,
(uint8_t *) &mask_be, sizeof mask_be)) {
match->labels = ntoh128(labels_be);
match->labels_mask = ntoh128(mask_be);
} else {
ds_put_cstr(ds, "failed to parse labels");
return false;
}
args--;
}

/* Parse ct tuples. */
for (int i = 0; i < 2; i++) {
if (!args) {
Expand Down Expand Up @@ -389,6 +471,7 @@ enum ofperr
ofp_ct_match_decode(struct ofp_ct_match *match, bool *with_zone,
uint16_t *zone_id, const struct ofp_header *oh)
{
uint32_t tlv_flags = 0;
struct ofpbuf msg = ofpbuf_const_initializer(oh, ntohs(oh->length));
ofpraw_pull_assert(&msg);

Expand Down Expand Up @@ -430,6 +513,22 @@ ofp_ct_match_decode(struct ofp_ct_match *match, bool *with_zone,
error = ofpprop_parse_u16(&property, zone_id);
break;

case NXT_CT_MARK:
error = ofpprop_parse_u32(&property, &match->mark);
break;

case NXT_CT_MARK_MASK:
error = ofpprop_parse_u32(&property, &match->mark_mask);
break;

case NXT_CT_LABELS:
error = ofpprop_parse_u128(&property, &match->labels);
break;

case NXT_CT_LABELS_MASK:
error = ofpprop_parse_u128(&property, &match->labels_mask);
break;

default:
error = OFPPROP_UNKNOWN(false, "NXT_CT_FLUSH", type);
break;
Expand All @@ -438,6 +537,22 @@ ofp_ct_match_decode(struct ofp_ct_match *match, bool *with_zone,
if (error) {
return error;
}

if (type < (sizeof tlv_flags * CHAR_BIT)) {
tlv_flags |= (UINT32_C(1) << type);
}
}

/* Consider the mask being all ones if it's not present but the value
* is specified. */
if (tlv_flags & (UINT32_C(1) << NXT_CT_MARK) &&
!(tlv_flags & (UINT32_C(1) << NXT_CT_MARK_MASK))) {
match->mark_mask = UINT32_MAX;
}

if (tlv_flags & (UINT32_C(1) << NXT_CT_LABELS) &&
!(tlv_flags & (UINT32_C(1) << NXT_CT_LABELS_MASK))) {
match->labels_mask = OVS_U128_MAX;
}

return 0;
Expand All @@ -461,5 +576,19 @@ ofp_ct_match_encode(const struct ofp_ct_match *match, uint16_t *zone_id,
ofpprop_put_u16(msg, NXT_CT_ZONE_ID, *zone_id);
}

if (match->mark_mask) {
ofpprop_put_u32(msg, NXT_CT_MARK, match->mark);
if (match->mark_mask != UINT32_MAX) {
ofpprop_put_u32(msg, NXT_CT_MARK_MASK, match->mark_mask);
}
}

if (!ovs_u128_is_zero(match->labels_mask)) {
ofpprop_put_u128(msg, NXT_CT_LABELS, match->labels);
if (!ovs_u128_is_ones(match->labels_mask)) {
ofpprop_put_u128(msg, NXT_CT_LABELS_MASK, match->labels_mask);
}
}

return msg;
}
84 changes: 84 additions & 0 deletions tests/ofp-print.at
Original file line number Diff line number Diff line change
Expand Up @@ -4093,6 +4093,84 @@ AT_CHECK([ovs-ofctl ofp-print "\
NXT_CT_FLUSH (xid=0x3): zone=13 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 20 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 03 00 08 00 00 00 ab \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 mark=0xab 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 20 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 04 00 08 00 00 00 cd \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 mark=0/0xcd 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 28 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 03 00 08 00 00 00 ab \
00 04 00 08 00 00 00 cd \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 mark=0xab/0xcd 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 30 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 05 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ab 00 00 00 00 00 \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 labels=0xffab00 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 30 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 06 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff cd 00 00 00 00 00 \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 labels=0/0xffcd00 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 48 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 05 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ab 00 00 00 00 00 \
00 06 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff cd 00 00 00 00 00 \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 labels=0xffab00/0xffcd00 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 38 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 03 00 08 00 00 00 ab \
00 05 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ab 00 00 00 00 00 \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 mark=0xab labels=0xffab00 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 58 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 03 00 08 00 00 00 ab \
00 04 00 08 00 00 00 cd \
00 05 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ab 00 00 00 00 00 \
00 06 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff cd 00 00 00 00 00 \
"], [0], [dnl
NXT_CT_FLUSH (xid=0x3): zone=0 mark=0xab/0xcd labels=0xffab00/0xffcd00 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 68 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
Expand Down Expand Up @@ -4198,4 +4276,10 @@ AT_CHECK([ovs-ofctl ofp-print "\
"| grep -q OFPBPC_BAD_TYPE], [0], [ignore], [stderr])
AT_CHECK([grep -q "unknown NXT_CT_TUPLE property type 128" stderr], [0])

AT_CHECK([ovs-ofctl ofp-print "\
01 04 00 30 00 00 00 03 00 00 23 20 00 00 00 20 \
06 \
00 00 00 00 00 00 00 \
00 06 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 ff cd 00 00 00 00 00 \
" | grep -q OFPBPC_BAD_LEN], [0])
AT_CLEANUP
Loading

0 comments on commit a095794

Please sign in to comment.