On 8/6/24 20:47, Mark Michelson wrote:
> On 8/6/24 05:44, Dumitru Ceara wrote:
>> From: Ales Musil <[email protected]>
>>
>> Currently, OVN would generate up to 2 flows per sample, depending
>> on the configuration. Add optimization that can reduce the number
>> of flows added into the ACL pipeline down to 3 per collector. This
>> optimization can be achieved only when the sample action with
>> registers is supported in OvS and the sample has only single
>> collector. The single collector per sample should be the case
>> in most configurations, usually even the same collector
>> for all samples which greatly reduces the number of flows per
>> ACL with sampling.
>>
>> If there are more collectors per sample or the OvS feature is not
>> supported, the implementation will fall back to flows per sample.
>>
>> Reported-at: https://issues.redhat.com/browse/FDP-709
>> Signed-off-by: Ales Musil <[email protected]>
>> ---
>> V6:
>> - Rebased.
>> - Removed Dumitru's ack.
>> - Store (newly created) Sample_Collector.id in ct state - instead of the
>> actual set-id to avoid ambiguity when multiple probabilities are used
>> with the same collector set id.
>> - Fixed bug with stateful to-lport ACLs on router ports.
>> - Reduced number of ct mark bits used for storing the collector id to 4.
>> V5:
>> - Address Ilya's comments:
>> - Explicitly set acl_observation_stage enum values.
>> - Added Dumitru's ack
>> ---
>> include/ovn/logical-fields.h | 2 +
>> lib/logical-fields.c | 8 +
>> northd/northd.c | 273 ++++++++++++++++++------
>> tests/ovn-northd.at | 388 +++++++++++++++++++++++++++++------
>> tests/ovn.at | 2 +
>> tests/system-ovn.at | 10 +-
>> 6 files changed, 553 insertions(+), 130 deletions(-)
>>
>> diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
>> index ce79b501cf..d6c4a9b6b3 100644
>> --- a/include/ovn/logical-fields.h
>> +++ b/include/ovn/logical-fields.h
>> @@ -197,6 +197,8 @@ const struct ovn_field *ovn_field_from_name(const
>> char *name);
>> #define OVN_CT_NATTED_BIT 1
>> #define OVN_CT_LB_SKIP_SNAT_BIT 2
>> #define OVN_CT_LB_FORCE_SNAT_BIT 3
>> +#define OVN_CT_OBS_STAGE_1ST_BIT 4
>> +#define OVN_CT_OBS_STAGE_END_BIT 5
>> #define OVN_CT_BLOCKED 1
>> #define OVN_CT_NATTED 2
>> diff --git a/lib/logical-fields.c b/lib/logical-fields.c
>> index 0c187e1c84..00bbc4a1c4 100644
>> --- a/lib/logical-fields.c
>> +++ b/lib/logical-fields.c
>> @@ -165,6 +165,14 @@ ovn_init_symtab(struct shash *symtab)
>>
>> OVN_CT_STR(OVN_CT_LB_FORCE_SNAT_BIT)
>> "]",
>> WR_CT_COMMIT);
>> + expr_symtab_add_subfield_scoped(symtab, "ct_mark.obs_stage", NULL,
>> + "ct_mark["
>> +
>> OVN_CT_STR(OVN_CT_OBS_STAGE_1ST_BIT) ".."
>> + OVN_CT_STR(OVN_CT_OBS_STAGE_END_BIT)
>> + "]",
>> + WR_CT_COMMIT);
>> + expr_symtab_add_subfield_scoped(symtab,
>> "ct_mark.obs_collector_id", NULL,
>> + "ct_mark[16..19]", WR_CT_COMMIT);
>> expr_symtab_add_field_scoped(symtab, "ct_label", MFF_CT_LABEL,
>> NULL,
>> false, WR_CT_COMMIT);
>> diff --git a/northd/northd.c b/northd/northd.c
>> index 13f9faba31..b1201bcc86 100644
>> --- a/northd/northd.c
>> +++ b/northd/northd.c
>> @@ -144,8 +144,20 @@ static bool vxlan_mode;
>> #define REGBIT_ACL_VERDICT_ALLOW "reg8[16]"
>> #define REGBIT_ACL_VERDICT_DROP "reg8[17]"
>> #define REGBIT_ACL_VERDICT_REJECT "reg8[18]"
>> +#define REGBIT_ACL_OBS_STAGE "reg8[19..20]"
>> #define REG_ACL_TIER "reg8[30..31]"
>> +enum acl_observation_stage {
>> + ACL_OBS_FROM_LPORT = 0,
>> + ACL_OBS_FROM_LPORT_AFTER_LB = 1,
>> + ACL_OBS_TO_LPORT = 2,
>> + ACL_OBS_STAGE_MAX
>> +};
>> +
>> +/* enum acl_observation_stage_t values must fit in the 2 bits of
>> + * REGBIT_ACL_OBS_STAGE .*/
>> +BUILD_ASSERT_DECL(ACL_OBS_STAGE_MAX < (1 << 2));
>> +
>> /* Indicate that this packet has been recirculated using egress
>> * loopback. This allows certain checks to be bypassed, such as a
>> * logical router dropping packets with source IP address equals
>> @@ -189,6 +201,8 @@ static bool vxlan_mode;
>> * domain and point ID. */
>> #define REG_OBS_POINT_ID_NEW "reg3"
>> #define REG_OBS_POINT_ID_EST "reg9"
>> +#define REG_OBS_COLLECTOR_ID_NEW "reg8[0..3]"
>> +#define REG_OBS_COLLECTOR_ID_EST "reg8[4..7]"
>> /* Register used for temporarily store ECMP eth.src to avoid
>> masked ct_label
>> * access. It doesn't really occupy registers because the content of
>> the
>> @@ -228,12 +242,13 @@ static bool vxlan_mode;
>> * +----+----------------------------------------------+ G
>> | |
>> * | R7 | UNUSED | 1
>> | |
>> *
>> +----+----------------------------------------------+---+-----------------------------------+
>> - * | | LB_AFF_MATCH_PORT |
>> - * | | (>= IN_LB_AFF_CHECK && <= IN_LB_AFF_LEARN) |
>> - * +----+----------------------------------------------+
>> - * | R9 | OBS_POINT_ID_EST |
>> - * | | (>= ACL_EVAL* && <= ACL_ACTION*) |
>> - * +----+----------------------------------------------+
>> + * | R8 | LB_AFF_MATCH_PORT | X |
>> REG_OBS_COLLECTOR_ID_NEW |
>> + * | | (>= IN_LB_AFF_CHECK && <= IN_LB_AFF_LEARN) | R |
>> REG_OBS_COLLECTOR_ID_EST |
>> + * | | | E | (>=
>> ACL_EVAL* && <= ACL_ACTION*) |
>> + * +----+----------------------------------------------+ G
>> +-----------------------------------+
>> + * | R9 | OBS_POINT_ID_EST | 4
>> | |
>> + * | | (>= ACL_EVAL* && <= ACL_ACTION*) |
>> | |
>> + *
>> +----+----------------------------------------------+---+-----------------------------------+
>> *
>> * Logical Router pipeline:
>> *
>> +-----+---------------------------+---+-----------------+---+------------------------------------+
>> @@ -6532,7 +6547,8 @@ build_acl_sample_action(struct ds *actions,
>> const struct nbrec_acl *acl,
>> static void
>> build_acl_sample_label_action(struct ds *actions, const struct
>> nbrec_acl *acl,
>> const struct nbrec_sample *sample_new,
>> - const struct nbrec_sample *sample_est)
>> + const struct nbrec_sample *sample_est,
>> + enum acl_observation_stage obs_stage)
>> {
>> if (!acl->label && !sample_new && !sample_est) {
>> return;
>> @@ -6540,6 +6556,8 @@ build_acl_sample_label_action(struct ds
>> *actions, const struct nbrec_acl *acl,
>> uint32_t point_id_new = 0;
>> uint32_t point_id_est = 0;
>> + uint8_t collector_id_new = 0;
>> + uint8_t collector_id_est = 0;
>> if (acl->label) {
>> point_id_new = acl->label;
>> @@ -6547,16 +6565,27 @@ build_acl_sample_label_action(struct ds
>> *actions, const struct nbrec_acl *acl,
>> } else {
>> if (sample_new) {
>> point_id_new = sample_new->metadata;
>> + if (sample_new->n_collectors == 1) {
>> + collector_id_new = sample_new->collectors[0]->id;
>> + }
>> }
>> if (sample_est) {
>> point_id_est = sample_est->metadata;
>> + if (sample_est->n_collectors == 1) {
>> + collector_id_est = sample_est->collectors[0]->id;
>> + }
>> }
>> }
>> ds_put_format(actions, REGBIT_ACL_LABEL" = 1; "
>> REG_OBS_POINT_ID_NEW " = %"PRIu32"; "
>> - REG_OBS_POINT_ID_EST " = %"PRIu32"; ",
>> - point_id_new, point_id_est);
>> + REG_OBS_POINT_ID_EST " = %"PRIu32"; "
>> + REG_OBS_COLLECTOR_ID_NEW " = %"PRIu8"; "
>> + REG_OBS_COLLECTOR_ID_EST " = %"PRIu8"; "
>> + REGBIT_ACL_OBS_STAGE " = %"PRIu8"; ",
>> + point_id_new, point_id_est,
>> + collector_id_new, collector_id_est,
>> + (uint8_t) obs_stage);
>> }
>> /* This builds an ACL logical flow specific match that selects
>> traffic
>> @@ -6604,46 +6633,16 @@ build_acl_sample_label_match(struct ds *match,
>> const struct nbrec_acl *acl,
>> }
>> /* This builds a logical flow that samples and forwards/drops traffic
>> - * that hit a stateless ACL ("pass" or "allow-stateless") that has
>> sampling
>> - * enabled.
>> + * that hit a stateless/stateful ACL that has sampling enabled.
>> */
>> static void
>> -build_acl_sample_new_stateless_flows(const struct ovn_datapath *od,
>> - struct lflow_table *lflows,
>> - enum ovn_stage stage,
>> - struct ds *match, struct ds
>> *actions,
>> - const struct nbrec_acl *acl,
>> - uint8_t sample_domain_id,
>> - struct lflow_ref *lflow_ref)
>> -{
>> - if (!acl->sample_new) {
>> - return;
>> - }
>> -
>> - ds_clear(actions);
>> - ds_clear(match);
>> -
>> - ds_put_cstr(match, "ip && ");
>> - build_acl_sample_register_match(match, acl, acl->sample_new);
>> -
>> - build_acl_sample_action(actions, acl, acl->sample_new,
>> sample_domain_id);
>> -
>> - ovn_lflow_add(lflows, od, stage, 1100, ds_cstr(match),
>> - ds_cstr(actions), lflow_ref);
>> -}
>> -
>> -/* This builds a logical flow that samples and forwards/drops traffic
>> - * that created a new conntrack entry and hit a stateful ACL that has
>> sampling
>> - * enabled.
>> - */
>> -static void
>> -build_acl_sample_new_stateful_flows(const struct ovn_datapath *od,
>> - struct lflow_table *lflows,
>> - enum ovn_stage stage,
>> - struct ds *match, struct ds
>> *actions,
>> - const struct nbrec_acl *acl,
>> - uint8_t sample_domain_id,
>> - struct lflow_ref *lflow_ref)
>> +build_acl_sample_new_flows(const struct ovn_datapath *od,
>> + struct lflow_table *lflows,
>> + enum ovn_stage stage,
>> + struct ds *match, struct ds *actions,
>> + const struct nbrec_acl *acl,
>> + uint8_t sample_domain_id, bool stateful,
>> + struct lflow_ref *lflow_ref)
>> {
>> if (!acl->sample_new) {
>> return;
>> @@ -6652,12 +6651,16 @@ build_acl_sample_new_stateful_flows(const
>> struct ovn_datapath *od,
>> ds_clear(actions);
>> ds_clear(match);
>> - /* Match on new connections. However, for to-lport ACLs, due to
>> + /* Match on new connections. However, for stateful to-lport
>> ACLs, due to
>> * skip_port_from_conntrack() conntrack state might be cleared, so
>> * take that into account too. */
>> - ds_put_format(match, "ip && %s && ",
>> - stage != S_SWITCH_OUT_ACL_SAMPLE
>> - ? "ct.new" : "(ct.new || !ct.trk)");
>> + if (!stateful) {
>> + ds_put_format(match, "ip && ");
>> + } else if (stage != S_SWITCH_OUT_ACL_SAMPLE) {
>> + ds_put_format(match, "ip && ct.new && ");
>> + } else {
>> + ds_put_format(match, "ip && (ct.new || !ct.trk) && ");
>> + }
>> build_acl_sample_register_match(match, acl, acl->sample_new);
>> build_acl_sample_action(actions, acl, acl->sample_new,
>> sample_domain_id);
>> @@ -6758,6 +6761,112 @@ build_acl_sample_est_stateful_flows(const
>> struct ovn_datapath *od,
>> static void build_acl_reject_action(struct ds *actions, bool
>> is_ingress);
>> +/* This builds a generic logical flow that samples traffic
>> + * that hit a stateless/stateful ACL that has sampling enabled with
>> + * single collector and all chassis supporting the sample with match
>> action.
>> + */
>> +static void
>> +build_acl_sample_generic_new_flows(const struct ovn_datapath *od,
>> + struct lflow_table *lflows,
>> + enum ovn_stage stage,
>> + enum acl_observation_stage obs_stage,
>> + struct ds *match, struct ds *actions,
>> + const struct
>> nbrec_sample_collector *coll,
>> + uint8_t sample_domain_id, bool
>> stateful,
>> + struct lflow_ref *lflow_ref)
>> +{
>> + ds_clear(match);
>> + ds_clear(actions);
>> +
>> + /* Match on new connections. However, for stateful to-lport
>> ACLs, due to
>> + * skip_port_from_conntrack() conntrack state might be cleared, so
>> + * take that into account too. */
>> + const char *new_conn_match = "";
>> + if (stateful) {
>> + if (stage != S_SWITCH_OUT_ACL_SAMPLE) {
>> + new_conn_match = "&& ct.new ";
>> + } else {
>> + new_conn_match = "&& (ct.new || !ct.trk) ";
>> + }
>> + }
>> +
>> + ds_put_format(match, "ip %s&& "REG_OBS_COLLECTOR_ID_NEW" ==
>> %"PRIu8" && "
>
> To me, it seems really strange to include "&& " and the trailing space
> as part of new_conn_match. I think formatting the match string looks
> more natural like this:
>
> ds_put_format(match, "ip && %s && "REG_OBS_COLLECTOR_ID_NEW" == ...
>
Thanks, Mark, for the review.
You're right, "%s&&" is quite ugly. We do need to account for the fact
that if "!stateful" new_conn_match is "".
What if I change this to:
const char *new_conn_match = "ip";
if (stateful) {
if (stage != S_SWITCH_OUT_ACL_SAMPLE) {
new_conn_match = "ip && ct.new";
} else {
new_conn_match = "ip && (ct.new || !ct.trk)";
}
}
ds_put_format(match, "%s && "REG_OBS_COLLECTOR_ID_NEW" == %"PRIu8" && "
REGBIT_ACL_OBS_STAGE " == %"PRIu8, new_conn_match,
(uint8_t) coll->id,
(uint8_t) obs_stage);
Does this look better? If so, I can include it in v7.
Regards,
Dumitru
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev