Great thanks for the help, guys! I just saw that it was merged. On Wed, Feb 18, 2026 at 8:09 AM Ales Musil <[email protected]> wrote:
> > > On Wed, Feb 18, 2026 at 8:46 AM Dumitru Ceara <[email protected]> wrote: > >> On 2/17/26 4:12 PM, Erlon R. Cruz wrote: >> > At the current state, OVN can not handle fragmented traffic for ACLs >> > in the userspace datapath (DPDK). Just like in the case of LB >> > (commit 20a96b9), the kernel DP will try to reassemble the fragments >> > during CT lookup, however userspace won't reassemble them. >> > >> > This patch allows OVN to handle fragmented traffic by defining a >> > translation table on southbound that leverages OpenFlow connection >> > tracking capabilities. When a stateful flow is created on NB, we add >> > a hint in the flow. This hint will be read in SB and if the >> > connection tracking is set to be used, SB will use the alternative >> > translation table that will use the connection tracking information. >> > >> > This approach should not change the current behavior and it's only >> > enabled if acl_ct_translation is set: >> > >> > ovn-nbctl set NB_Global . options:acl_ct_translation=true >> > >> > Signed-off-by: Erlon R. Cruz <[email protected]> >> > --- >> > v2: Rebased on current upstream main, removed external python >> > code traffic generators, added scenario tests for: dhcp, negative >> > udp and ovn rule generation, documentation and many code clean ups. >> > DHCP traffic is being dropped for some reason. Still need to figure >> > that out for v3. >> > v3: Rebased on current upstream main, clean ups. Fix DHCP/broadcast >> > bug >> > v4: Rebase, code cleanup >> > v5: Rebase, code cleanup, make ovn_lflow_find acl_ct_translation aware >> > --- >> >> Hi Erlon, >> >> This version looks good to me. I only have a few very minor comments. >> I know Ales wanted to review the patch too and, if he doesn't have any >> major comments either, I hope he can fix up the small issues I found >> below. >> >> With that: >> Acked-by: Dumitru Ceara <[email protected]> >> >> Thanks, >> Dumitru >> > > Thank you Erlon and Dumitru, > I have just one extra nit below. > > >> >> > NEWS | 5 + >> > controller/lflow.c | 23 +- >> > include/ovn/logical-fields.h | 2 + >> > lib/logical-fields.c | 67 ++++++ >> > lib/ovn-util.c | 10 +- >> > lib/ovn-util.h | 3 +- >> > northd/en-global-config.c | 5 + >> > northd/lflow-mgr.c | 76 ++++-- >> > northd/lflow-mgr.h | 4 +- >> > northd/northd.c | 48 +++- >> > ovn-nb.xml | 23 ++ >> > tests/ovn.at | 75 ++++++ >> > tests/system-ovn.at | 440 +++++++++++++++++++++++++++++++++-- >> > 13 files changed, 718 insertions(+), 63 deletions(-) >> > >> > diff --git a/NEWS b/NEWS >> > index c566ebfc8..6eb77b0e0 100644 >> > --- a/NEWS >> > +++ b/NEWS >> > @@ -104,6 +104,11 @@ Post v25.09.0 >> > - Add support for special port_security prefix "VRRPv3". This >> prefix allows >> > CMS to allow all required traffic for a VRRPv3 virtual router >> behind LSP. >> > See ovn-nb(5) man page for more details. >> > + - Fixed support for fragmented traffic in the userspace datapath. >> Added the >> > + "acl_ct_translation" NB_Global option to enable connection >> tracking >> > + based L4 field translation for stateful ACLs. When enabled allows >> proper >> > + handling of IP fragmentation in userspace datapaths. This option >> breaks >> >> Nit: I'd rephrase this as "this option may break..." >> >> > + hardware offloading and is disabled by default. >> > >> > OVN v25.09.0 - xxx xx xxxx >> > -------------------------- >> > diff --git a/controller/lflow.c b/controller/lflow.c >> > index a12627b0a..35ed6d30b 100644 >> > --- a/controller/lflow.c >> > +++ b/controller/lflow.c >> > @@ -51,10 +51,19 @@ COVERAGE_DEFINE(consider_logical_flow); >> > /* Contains "struct expr_symbol"s for fields supported by OVN lflows. >> */ >> > static struct shash symtab; >> > >> > +/* Alternative symbol table for ACL CT translation. >> > + * This symbol table maps L4 port fields (tcp/udp/sctp) to their >> connection >> > + * tracking equivalents (ct_tp_src/ct_tp_dst with ct_proto predicates). >> > + * This allows matching on all IP fragments (not just the first >> fragment) >> > + * so that all fragments can be matched based on the connection >> tracking state. >> > + */ >> > +static struct shash acl_ct_symtab; >> > + >> > void >> > lflow_init(void) >> > { >> > ovn_init_symtab(&symtab); >> > + ovn_init_acl_ct_symtab(&acl_ct_symtab); >> > } >> > >> > struct lookup_port_aux { >> > @@ -1001,7 +1010,15 @@ convert_match_to_expr(const struct >> sbrec_logical_flow *lflow, >> > lflow->match); >> > return NULL; >> > } >> > - struct expr *e = expr_parse_string(lex_str_get(&match_s), &symtab, >> > + >> > + /* Check if this logical flow requires ACL CT translation. >> > + * If the tags contains "acl_ct_translation"="true", we use the >> > + * alternative symbol table that maps L4 fields (tcp/udp/sctp >> ports) >> > + * to their CT equivalents. */ >> > + bool ct_trans = smap_get_bool(&lflow->tags, "acl_ct_translation", >> false); >> > + struct shash *symtab_to_use = ct_trans ? &acl_ct_symtab : &symtab; >> > + >> > + struct expr *e = expr_parse_string(lex_str_get(&match_s), >> symtab_to_use, >> > addr_sets, port_groups, >> &addr_sets_ref, >> > &port_groups_ref, >> > ldp->datapath->tunnel_key, >> > @@ -1033,7 +1050,7 @@ convert_match_to_expr(const struct >> sbrec_logical_flow *lflow, >> > e = expr_combine(EXPR_T_AND, e, *prereqs); >> > *prereqs = NULL; >> > } >> > - e = expr_annotate(e, &symtab, &error); >> > + e = expr_annotate(e, symtab_to_use, &error); >> > } >> > if (error) { >> > static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); >> > @@ -2062,6 +2079,8 @@ lflow_destroy(void) >> > { >> > expr_symtab_destroy(&symtab); >> > shash_destroy(&symtab); >> > + expr_symtab_destroy(&acl_ct_symtab); >> > + shash_destroy(&acl_ct_symtab); >> > } >> > >> > bool >> > diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h >> > index a22e4cfb7..3c0fb22e7 100644 >> > --- a/include/ovn/logical-fields.h >> > +++ b/include/ovn/logical-fields.h >> > @@ -278,6 +278,8 @@ const char *event_to_string(enum >> ovn_controller_event event); >> > int string_to_event(const char *s); >> > const struct ovn_field *ovn_field_from_name(const char *name); >> > >> > +void ovn_init_acl_ct_symtab(struct shash *symtab); >> > + >> > /* OVN CT label values >> > * =================== >> > * These are specific ct.label bit values OVN uses to track different >> types >> > diff --git a/lib/logical-fields.c b/lib/logical-fields.c >> > index 601208cb9..9b04762a1 100644 >> > --- a/lib/logical-fields.c >> > +++ b/lib/logical-fields.c >> > @@ -451,3 +451,70 @@ ovn_field_from_name(const char *name) >> > >> > return shash_find_data(&ovnfield_by_name, name); >> > } >> > + >> > +/* Removes the symbol with 'name' from 'symtab', freeing its memory. */ >> > +static void >> > +expr_symtab_remove(struct shash *symtab, const char *name) >> > +{ >> > + struct expr_symbol *symbol = shash_find_and_delete(symtab, name); >> > + if (symbol) { >> > + free(symbol->name); >> > + free(symbol->prereqs); >> > + free(symbol->predicate); >> > + free(symbol); >> > + } >> > +} >> > + >> > +/* Initialize a symbol table for ACL CT translation. >> > + * This creates an alternative symbol table that maps L4 port fields >> > + * (tcp/udp/sctp) to their connection tracking equivalents. This allows >> > + * matching on all IP fragments (not just the first) by using CT state >> > + * which is available for all fragments. */ >> > +void >> > +ovn_init_acl_ct_symtab(struct shash *acl_symtab) >> > +{ >> > + /* Initialize with the standard symbol table. */ >> > + ovn_init_symtab(acl_symtab); >> > + >> > + /* Remove the original tcp/udp/sctp symbols that we will override. >> */ >> > + expr_symtab_remove(acl_symtab, "tcp.src"); >> > + expr_symtab_remove(acl_symtab, "tcp.dst"); >> > + expr_symtab_remove(acl_symtab, "tcp"); >> > + expr_symtab_remove(acl_symtab, "udp.src"); >> > + expr_symtab_remove(acl_symtab, "udp.dst"); >> > + expr_symtab_remove(acl_symtab, "udp"); >> > + expr_symtab_remove(acl_symtab, "sctp.src"); >> > + expr_symtab_remove(acl_symtab, "sctp.dst"); >> > + expr_symtab_remove(acl_symtab, "sctp"); >> > + >> > + /* Add ct_proto field - CT original direction protocol. Used in the >> > + * tcp/udp/sctp predicate expansions below. */ >> > + expr_symtab_add_field(acl_symtab, "ct_proto", MFF_CT_NW_PROTO, >> > + "ct.trk", false); >> > + >> > + /* Override TCP protocol and port fields to use CT equivalents. >> > + * When "tcp" is used as a predicate, it expands to "ct_proto == 6" >> > + * instead of "ip.proto == 6". */ >> > + expr_symtab_add_predicate(acl_symtab, "tcp", >> > + "ct.trk && !ct.inv && ct_proto == 6"); >> > + expr_symtab_add_field(acl_symtab, "tcp.src", MFF_CT_TP_SRC, >> > + "tcp", false); >> > + expr_symtab_add_field(acl_symtab, "tcp.dst", MFF_CT_TP_DST, >> > + "tcp", false); >> > + >> > + /* Override UDP protocol and port fields */ >> > + expr_symtab_add_predicate(acl_symtab, "udp", >> > + "ct.trk && !ct.inv && ct_proto == 17"); >> > + expr_symtab_add_field(acl_symtab, "udp.src", MFF_CT_TP_SRC, >> > + "udp", false); >> > + expr_symtab_add_field(acl_symtab, "udp.dst", MFF_CT_TP_DST, >> > + "udp", false); >> > + >> > + /* Override SCTP protocol and port fields */ >> > + expr_symtab_add_predicate(acl_symtab, "sctp", >> > + "ct.trk && !ct.inv && ct_proto == 132"); >> > + expr_symtab_add_field(acl_symtab, "sctp.src", MFF_CT_TP_SRC, >> > + "sctp", false); >> > + expr_symtab_add_field(acl_symtab, "sctp.dst", MFF_CT_TP_DST, >> > + "sctp", false); >> > +} >> > diff --git a/lib/ovn-util.c b/lib/ovn-util.c >> > index f34c37a79..63a68df68 100644 >> > --- a/lib/ovn-util.c >> > +++ b/lib/ovn-util.c >> > @@ -698,20 +698,24 @@ ovn_pipeline_from_name(const char *pipeline) >> > uint32_t >> > sbrec_logical_flow_hash(const struct sbrec_logical_flow *lf) >> > { >> > + bool acl_ct_translation = smap_get_bool(&lf->tags, >> > + "acl_ct_translation", >> false); >> > return ovn_logical_flow_hash(lf->table_id, >> > ovn_pipeline_from_name(lf->pipeline), >> > lf->priority, lf->match, >> > - lf->actions); >> > + lf->actions, acl_ct_translation); >> > } >> > >> > uint32_t >> > ovn_logical_flow_hash(uint8_t table_id, enum ovn_pipeline pipeline, >> > uint16_t priority, >> > - const char *match, const char *actions) >> > + const char *match, const char *actions, >> > + bool acl_ct_translation) >> > { >> > size_t hash = hash_2words((table_id << 16) | priority, pipeline); >> > hash = hash_string(match, hash); >> > - return hash_string(actions, hash); >> > + hash = hash_string(actions, hash); >> > + return hash_boolean(acl_ct_translation, hash); >> > > nit: hash_boolean(...) doesn't call hash_finish, so we need > to swap the order with hash_string(...). > > >> > } >> > >> > >> > diff --git a/lib/ovn-util.h b/lib/ovn-util.h >> > index 0efeb217c..b882b8533 100644 >> > --- a/lib/ovn-util.h >> > +++ b/lib/ovn-util.h >> > @@ -155,7 +155,8 @@ enum ovn_pipeline { >> > uint32_t sbrec_logical_flow_hash(const struct sbrec_logical_flow *); >> > uint32_t ovn_logical_flow_hash(uint8_t table_id, enum ovn_pipeline >> pipeline, >> > uint16_t priority, >> > - const char *match, const char *actions); >> > + const char *match, const char *actions, >> > + bool acl_ct_translation); >> > void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED, >> > const char *argv[] OVS_UNUSED, void *idl_); >> > >> > diff --git a/northd/en-global-config.c b/northd/en-global-config.c >> > index 2556b2888..a6f37b0c3 100644 >> > --- a/northd/en-global-config.c >> > +++ b/northd/en-global-config.c >> > @@ -717,6 +717,11 @@ check_nb_options_out_of_sync( >> > return true; >> > } >> > >> > + if (config_out_of_sync(&nb->options, &config_data->nb_options, >> > + "acl_ct_translation", false)) { >> > + return true; >> > + } >> > + >> > return false; >> > } >> > >> > diff --git a/northd/lflow-mgr.c b/northd/lflow-mgr.c >> > index f21024903..a9811601f 100644 >> > --- a/northd/lflow-mgr.c >> > +++ b/northd/lflow-mgr.c >> > @@ -40,12 +40,14 @@ static void ovn_lflow_init(struct ovn_lflow *, >> > char *actions, char *io_port, >> > char *ctrl_meter, char *stage_hint, >> > const char *where, const char *flow_desc, >> > - struct uuid sbuuid); >> > + struct uuid sbuuid, bool >> acl_ct_translation); >> > static struct ovn_lflow *ovn_lflow_find(const struct hmap *lflows, >> > const struct ovn_stage *stage, >> > uint16_t priority, const char >> *match, >> > const char *actions, >> > - const char *ctrl_meter, >> uint32_t hash); >> > + const char *ctrl_meter, >> > + bool acl_ct_translation, >> > + uint32_t hash); >> > static void ovn_lflow_destroy(struct lflow_table *lflow_table, >> > struct ovn_lflow *lflow); >> > static char *ovn_lflow_hint(const struct ovsdb_idl_row *row); >> > @@ -56,7 +58,8 @@ static struct ovn_lflow *do_ovn_lflow_add( >> > const char *actions, const char *io_port, >> > const char *ctrl_meter, >> > const struct ovsdb_idl_row *stage_hint, >> > - const char *where, const char *flow_desc); >> > + const char *where, const char *flow_desc, >> > + bool acl_ct_translation); >> > >> > >> > static struct ovs_mutex *lflow_hash_lock(const struct hmap >> *lflow_table, >> > @@ -184,6 +187,7 @@ struct ovn_lflow { >> > struct ovn_dp_group *dpg; /* Link to unique Sb datapath group. >> */ >> > const char *where; >> > const char *flow_desc; >> > + bool acl_ct_translation; /* Use CT-based L4 field translation. >> */ >> > >> > struct uuid sb_uuid; /* SB DB row uuid, specified by >> northd. */ >> > struct ovs_list referenced_by; /* List of struct lflow_ref_node. >> */ >> > @@ -362,10 +366,12 @@ lflow_table_sync_to_sb(struct lflow_table >> *lflow_table, >> > struct ovn_stage stage = ovn_stage_build(dp_type, pipeline, >> > sbflow->table_id); >> > >> > + bool acl_ct_translation = smap_get_bool(&sbflow->tags, >> > + "acl_ct_translation", >> false); >> > lflow = ovn_lflow_find( >> > lflows, &stage, >> > sbflow->priority, sbflow->match, sbflow->actions, >> > - sbflow->controller_meter, sbflow->hash); >> > + sbflow->controller_meter, acl_ct_translation, >> sbflow->hash); >> > if (lflow) { >> > const struct ovn_synced_datapaths *datapaths; >> > struct hmap *dp_groups; >> > @@ -727,14 +733,15 @@ lflow_ref_sync_lflows(struct lflow_ref *lflow_ref, >> > */ >> > static void >> > lflow_table_add_lflow__(struct lflow_table *lflow_table, >> > - const struct ovn_synced_datapath *sdp, >> > - const unsigned long *dp_bitmap, size_t >> dp_bitmap_len, >> > - const struct ovn_stage *stage, uint16_t >> priority, >> > - const char *match, const char *actions, >> > - const char *io_port, const char *ctrl_meter, >> > - const struct ovsdb_idl_row *stage_hint, >> > - const char *where, const char *flow_desc, >> > - struct lflow_ref *lflow_ref) >> > + const struct ovn_synced_datapath *sdp, >> > + const unsigned long *dp_bitmap, size_t >> dp_bitmap_len, >> > + const struct ovn_stage *stage, uint16_t >> priority, >> > + const char *match, const char *actions, >> > + const char *io_port, const char *ctrl_meter, >> > + const struct ovsdb_idl_row *stage_hint, >> > + const char *where, const char *flow_desc, >> > + bool acl_ct_translation, >> >> Nit: to be more aligned with the order of arguments of other functions, >> I'd move 'acl_ct_translation' just after 'ctrl_meter'. >> >> > + struct lflow_ref *lflow_ref) >> > OVS_EXCLUDED(fake_hash_mutex) >> > { >> > struct ovs_mutex *hash_lock; >> > @@ -747,7 +754,7 @@ lflow_table_add_lflow__(struct lflow_table >> *lflow_table, >> > hash = ovn_logical_flow_hash(ovn_stage_get_table(stage), >> > ovn_stage_get_pipeline(stage), >> > priority, match, >> > - actions); >> > + actions, acl_ct_translation); >> > >> > hash_lock = lflow_hash_lock(&lflow_table->entries, hash); >> > struct ovn_lflow *lflow = >> > @@ -755,7 +762,8 @@ lflow_table_add_lflow__(struct lflow_table >> *lflow_table, >> > sdp ? sparse_array_len(&sdp->dps->dps_array) >> > : dp_bitmap_len, >> > hash, stage, priority, match, actions, >> > - io_port, ctrl_meter, stage_hint, where, >> flow_desc); >> > + io_port, ctrl_meter, stage_hint, where, >> flow_desc, >> > + acl_ct_translation); >> > >> > if (lflow_ref) { >> > struct lflow_ref_node *lrn = >> > @@ -815,7 +823,8 @@ lflow_table_add_lflow(struct lflow_table_add_args >> *args) >> > args->dp_bitmap_len, args->stage, >> args->priority, >> > args->match, args->actions, args->io_port, >> > args->ctrl_meter, args->stage_hint, >> args->where, >> > - args->flow_desc, args->lflow_ref); >> > + args->flow_desc, args->acl_ct_translation, >> > + args->lflow_ref); >> > } >> > >> > struct ovn_dp_group * >> > @@ -933,7 +942,8 @@ ovn_lflow_init(struct ovn_lflow *lflow, >> > size_t dp_bitmap_len, const struct ovn_stage *stage, >> > uint16_t priority, char *match, char *actions, char >> *io_port, >> > char *ctrl_meter, char *stage_hint, const char *where, >> > - const char *flow_desc, struct uuid sbuuid) >> > + const char *flow_desc, struct uuid sbuuid, >> > + bool acl_ct_translation) >> >> Nit: to be more aligned with the order of arguments of other functions, >> I'd move 'acl_ct_translation' just after 'stage_hint'. >> >> > { >> > dynamic_bitmap_alloc(&lflow->dpg_bitmap, dp_bitmap_len); >> > lflow->dp = dp; >> > @@ -949,6 +959,7 @@ ovn_lflow_init(struct ovn_lflow *lflow, >> > lflow->where = where; >> > lflow->sb_uuid = sbuuid; >> > lflow->sync_state = LFLOW_TO_SYNC; >> > + lflow->acl_ct_translation = acl_ct_translation; >> > hmap_init(&lflow->dp_refcnts_map); >> > ovs_list_init(&lflow->referenced_by); >> > } >> > @@ -981,25 +992,28 @@ lflow_hash_unlock(struct ovs_mutex *hash_lock) >> > static bool >> > ovn_lflow_equal(const struct ovn_lflow *a, const struct ovn_stage >> *stage, >> > uint16_t priority, const char *match, >> > - const char *actions, const char *ctrl_meter) >> > + const char *actions, const char *ctrl_meter, >> > + bool acl_ct_translation) >> > { >> > return (ovn_stage_equal(a->stage, stage) >> > && a->priority == priority >> > && !strcmp(a->match, match) >> > && !strcmp(a->actions, actions) >> > - && nullable_string_is_equal(a->ctrl_meter, ctrl_meter)); >> > + && nullable_string_is_equal(a->ctrl_meter, ctrl_meter) >> > + && a->acl_ct_translation == acl_ct_translation); >> > } >> > >> > static struct ovn_lflow * >> > ovn_lflow_find(const struct hmap *lflows, >> > const struct ovn_stage *stage, uint16_t priority, >> > const char *match, const char *actions, >> > - const char *ctrl_meter, uint32_t hash) >> > + const char *ctrl_meter, bool acl_ct_translation, >> > + uint32_t hash) >> > { >> > struct ovn_lflow *lflow; >> > HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) { >> > if (ovn_lflow_equal(lflow, stage, priority, match, actions, >> > - ctrl_meter)) { >> > + ctrl_meter, acl_ct_translation)) { >> > return lflow; >> > } >> > } >> > @@ -1039,7 +1053,8 @@ do_ovn_lflow_add(struct lflow_table *lflow_table, >> size_t dp_bitmap_len, >> > uint16_t priority, const char *match, const char >> *actions, >> > const char *io_port, const char *ctrl_meter, >> > const struct ovsdb_idl_row *stage_hint, >> > - const char *where, const char *flow_desc) >> > + const char *where, const char *flow_desc, >> > + bool acl_ct_translation) >> > OVS_REQUIRES(fake_hash_mutex) >> > { >> > struct ovn_lflow *old_lflow; >> > @@ -1049,7 +1064,8 @@ do_ovn_lflow_add(struct lflow_table *lflow_table, >> size_t dp_bitmap_len, >> > ovs_assert(dp_bitmap_len); >> > >> > old_lflow = ovn_lflow_find(&lflow_table->entries, stage, >> > - priority, match, actions, ctrl_meter, >> hash); >> > + priority, match, actions, ctrl_meter, >> > + acl_ct_translation, hash); >> > if (old_lflow) { >> > dynamic_bitmap_realloc(&old_lflow->dpg_bitmap, dp_bitmap_len); >> > if (old_lflow->sync_state != LFLOW_STALE) { >> > @@ -1068,7 +1084,7 @@ do_ovn_lflow_add(struct lflow_table *lflow_table, >> size_t dp_bitmap_len, >> > io_port ? xstrdup(io_port) : NULL, >> > nullable_xstrdup(ctrl_meter), >> > ovn_lflow_hint(stage_hint), where, >> > - flow_desc, sbuuid); >> > + flow_desc, sbuuid, acl_ct_translation); >> > >> > if (parallelization_state != STATE_USE_PARALLELIZATION) { >> > hmap_insert(&lflow_table->entries, &lflow->hmap_node, hash); >> > @@ -1122,12 +1138,22 @@ sync_lflow_to_sb(struct ovn_lflow *lflow, >> > sbrec_logical_flow_set_match(sbflow, lflow->match); >> > sbrec_logical_flow_set_actions(sbflow, lflow->actions); >> > sbrec_logical_flow_set_flow_desc(sbflow, lflow->flow_desc); >> > + >> > + struct smap tags = SMAP_INITIALIZER(&tags); >> > if (lflow->io_port) { >> > - struct smap tags = SMAP_INITIALIZER(&tags); >> > smap_add(&tags, "in_out_port", lflow->io_port); >> > + } >> > + >> > + if (lflow->acl_ct_translation) { >> > + smap_add(&tags, "acl_ct_translation", "true"); >> > + } >> > + >> > + if (!smap_is_empty(&tags)) { >> > sbrec_logical_flow_set_tags(sbflow, &tags); >> > - smap_destroy(&tags); >> > } >> > + >> > + smap_destroy(&tags); >> > + >> > sbrec_logical_flow_set_controller_meter(sbflow, >> lflow->ctrl_meter); >> > >> > /* Trim the source locator lflow->where, which looks something >> like >> > diff --git a/northd/lflow-mgr.h b/northd/lflow-mgr.h >> > index 4a1655c35..84d0b3e67 100644 >> > --- a/northd/lflow-mgr.h >> > +++ b/northd/lflow-mgr.h >> > @@ -24,6 +24,7 @@ >> > struct ovsdb_idl_txn; >> > struct ovn_datapath; >> > struct ovsdb_idl_row; >> > +struct ovn_lflow; >> > >> > /* lflow map which stores the logical flows. */ >> > struct lflow_table { >> > @@ -87,12 +88,13 @@ struct lflow_table_add_args { >> > const char *flow_desc; >> > struct lflow_ref *lflow_ref; >> > const char *where; >> > + bool acl_ct_translation; >> > }; >> > >> > void lflow_table_add_lflow(struct lflow_table_add_args *args); >> > >> > - >> > #define WITH_HINT(HINT) .stage_hint = HINT >> > +#define WITH_CT_TRANSLATION(CT_TRANS) .acl_ct_translation = (CT_TRANS) >> > /* The IN_OUT_PORT argument tells the lport name that appears in the >> MATCH, >> > * which helps ovn-controller to bypass lflows parsing when the lport >> is >> > * not local to the chassis. The critiera of the lport to be added >> using this >> > diff --git a/northd/northd.c b/northd/northd.c >> > index 7fe36b850..937383e4b 100644 >> > --- a/northd/northd.c >> > +++ b/northd/northd.c >> > @@ -89,6 +89,12 @@ static bool use_common_zone = false; >> > * Otherwise, it will avoid using it. The default is true. */ >> > static bool use_ct_inv_match = true; >> > >> > +/* If this option is 'true' northd will flag the related ACL flows to >> use >> > + * connection tracking fields to properly handle IP fragments. By >> default this >> > + * option is set to 'false'. >> > + */ >> > +static bool acl_ct_translation = false; >> > + >> > /* If this option is 'true' northd will implicitly add a >> lowest-priority >> > * drop rule in the ACL stage of logical switches that have at least >> one >> > * ACL. >> > @@ -6306,11 +6312,19 @@ build_ls_stateful_rec_pre_acls( >> > "(udp && udp.src == 546 && udp.dst == 547)", >> "next;", >> > lflow_ref); >> > >> > - /* Do not send multicast packets to conntrack. */ >> > - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, >> "eth.mcast", >> > - "next;", lflow_ref); >> > - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, >> "eth.mcast", >> > - "next;", lflow_ref); >> > + /* Do not send multicast packets to conntrack unless ACL CT >> > + * translation is enabled. When translation is active, L4 port >> > + * fields are rewritten to their CT equivalents (e.g. udp.dst >> -> >> > + * ct_udp.dst), which requires ct.trk to be set. Skipping CT >> > + * for multicast would leave ct.trk unset and cause all >> > + * CT-translated ACL matches to fail for multicast traffic >> > + * (including DHCP). */ >> > + if (!acl_ct_translation) { >> > + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, >> "eth.mcast", >> > + "next;", lflow_ref); >> > + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, >> "eth.mcast", >> > + "next;", lflow_ref); >> > + } >> > >> > /* Ingress and Egress Pre-ACL Table (Priority 100). >> > * >> > @@ -7286,6 +7300,11 @@ consider_acl(struct lflow_table *lflows, const >> struct ovn_datapath *od, >> > match_tier_len = match->length; >> > } >> > >> > + /* Check if this ACL needs CT translation for fragment handling. >> > + * All stateful ACLs are marked when the option is enabled; the >> actual >> > + * translation only affects L4 port fields in ovn-controller. */ >> > + bool needs_ct_trans = has_stateful && acl_ct_translation; >> > + >> > if (!has_stateful >> > || !strcmp(acl->action, "pass") >> > || !strcmp(acl->action, "allow-stateless")) { >> > @@ -7303,6 +7322,7 @@ consider_acl(struct lflow_table *lflows, const >> struct ovn_datapath *od, >> > >> > ds_put_cstr(actions, "next;"); >> > ds_put_format(match, "(%s)", acl->match); >> > + /* Stateless ACLs don't need CT translation. */ >> > ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), >> > ds_cstr(actions), lflow_ref, >> WITH_HINT(&acl->header_)); >> > return; >> > @@ -7372,7 +7392,9 @@ consider_acl(struct lflow_table *lflows, const >> struct ovn_datapath *od, >> > } >> > ds_put_cstr(actions, "next;"); >> > ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), >> > - ds_cstr(actions), lflow_ref, >> WITH_HINT(&acl->header_)); >> > + ds_cstr(actions), lflow_ref, >> > + WITH_HINT(&acl->header_), >> > + WITH_CT_TRANSLATION(needs_ct_trans)); >> > >> > /* Match on traffic in the request direction for an established >> > * connection tracking entry that has not been marked for >> > @@ -7402,7 +7424,9 @@ consider_acl(struct lflow_table *lflows, const >> struct ovn_datapath *od, >> > } >> > ds_put_cstr(actions, "next;"); >> > ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), >> > - ds_cstr(actions), lflow_ref, >> WITH_HINT(&acl->header_)); >> > + ds_cstr(actions), lflow_ref, >> > + WITH_HINT(&acl->header_), >> > + WITH_CT_TRANSLATION(needs_ct_trans)); >> > } else if (!strcmp(acl->action, "drop") >> > || !strcmp(acl->action, "reject")) { >> > if (acl->network_function_group) { >> > @@ -7428,7 +7452,9 @@ consider_acl(struct lflow_table *lflows, const >> struct ovn_datapath *od, >> > obs_stage); >> > ds_put_cstr(actions, "next;"); >> > ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), >> > - ds_cstr(actions), lflow_ref, >> WITH_HINT(&acl->header_)); >> > + ds_cstr(actions), lflow_ref, >> > + WITH_HINT(&acl->header_), >> > + WITH_CT_TRANSLATION(needs_ct_trans)); >> > /* For an existing connection without ct_mark.blocked set, >> we've >> > * encountered a policy change. ACLs previously allowed >> > * this connection and we committed the connection tracking >> > @@ -7454,7 +7480,9 @@ consider_acl(struct lflow_table *lflows, const >> struct ovn_datapath *od, >> > "ct_commit { ct_mark.blocked = 1; " >> > "ct_label.obs_point_id = %"PRIu32"; }; next;", >> obs_pid); >> > ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), >> > - ds_cstr(actions), lflow_ref, >> WITH_HINT(&acl->header_)); >> > + ds_cstr(actions), lflow_ref, >> > + WITH_HINT(&acl->header_), >> > + WITH_CT_TRANSLATION(needs_ct_trans)); >> > } >> > } >> > >> > @@ -20758,6 +20786,8 @@ ovnnb_db_run(struct northd_input *input_data, >> > >> > use_ct_inv_match = smap_get_bool(input_data->nb_options, >> > "use_ct_inv_match", true); >> > + acl_ct_translation = smap_get_bool(input_data->nb_options, >> > + "acl_ct_translation", false); >> > >> > /* deprecated, use --event instead */ >> > controller_event_en = smap_get_bool(input_data->nb_options, >> > diff --git a/ovn-nb.xml b/ovn-nb.xml >> > index ae37d03d1..9bb49c2ea 100644 >> > --- a/ovn-nb.xml >> > +++ b/ovn-nb.xml >> > @@ -327,6 +327,29 @@ >> > </p> >> > </column> >> > >> > + <column name="options" key="acl_ct_translation"> >> > + <p> >> > + If set to <code>true</code>, <code>ovn-northd</code> will >> enable >> > + connection tracking based L4 field translation for stateful >> ACLs. >> > + When enabled, ACL matches on L4 port fields (tcp/udp/sctp) >> use >> > + connection tracking state instead of packet headers. This >> allows >> > + proper handling of IP fragmentation in userspace datapaths >> (e.g., >> > + DPDK) where fragments are not automatically reassembled >> during >> > + connection tracking lookup. >> > + </p> >> > + <p> >> > + <em>Important:</em> Enabling this option breaks hardware >> offloading >> >> Nit: s/breaks/may break/ >> >> > + of flows. It also disables the multicast conntrack bypass, >> which >> > + means multicast traffic (including DHCP) will go through >> connection >> > + tracking. This may have a performance impact on >> multicast-heavy >> > + workloads. Only enable this option if you need to handle >> fragmented >> > + traffic with stateful ACLs in userspace datapaths. >> > + </p> >> > + <p> >> > + The default value is <code>false</code>. >> > + </p> >> > + </column> >> > + >> > <column name="options" key="default_acl_drop"> >> > <p> >> > If set to <code>true</code>., <code>ovn-northd</code> will >> > diff --git a/tests/ovn.at b/tests/ovn.at >> > index 3a3b05ab7..ff3dbd6bc 100644 >> > --- a/tests/ovn.at >> > +++ b/tests/ovn.at >> > @@ -45700,3 +45700,78 @@ AT_CHECK([grep -q 'name="eth-unknown"' >> hv1/ovn-controller.log], [1]) >> > OVN_CLEANUP([hv1]) >> > AT_CLEANUP >> > ]) >> > + >> > +OVN_FOR_EACH_NORTHD([ >> > +AT_SETUP([ACL CT translation - Rules]) >> > +AT_KEYWORDS([acl_ct_translation_rules]) >> > +ovn_start >> > + >> > +check ovn-nbctl ls-add ls1 >> > + >> > +check ovn-nbctl lsp-add ls1 lp1 \ >> > + -- lsp-set-addresses lp1 "f0:00:00:00:00:01 10.0.0.1" >> > +check ovn-nbctl lsp-add ls1 lp2 \ >> > + -- lsp-set-addresses lp2 "f0:00:00:00:00:02 10.0.0.2" >> > + >> > +net_add n1 >> > +sim_add hv1 >> > + >> > +as hv1 >> > +check ovs-vsctl add-br br-phys >> > +ovn_attach n1 br-phys 192.168.0.1 >> > +check ovs-vsctl -- add-port br-int hv1-vif1 -- \ >> > + set interface hv1-vif1 external-ids:iface-id=lp1 >> > +check ovs-vsctl -- add-port br-int hv1-vif2 -- \ >> > + set interface hv1-vif2 external-ids:iface-id=lp2 >> > + >> > +# Get the OF table numbers for ACL evaluation stages. >> > +acl_in_eval=$(ovn-debug lflow-stage-to-oftable ls_in_acl_eval) >> > +acl_out_eval=$(ovn-debug lflow-stage-to-oftable ls_out_acl_eval) >> > + >> > +# Create port group and add ACLs with TCP/UDP matches. >> > +lp1_uuid=$(fetch_column nb:logical_switch_port _uuid name=lp1) >> > +lp2_uuid=$(fetch_column nb:logical_switch_port _uuid name=lp2) >> > +check ovn-nbctl pg-add pg1 $lp1_uuid $lp2_uuid >> > + >> > +check ovn-nbctl acl-add pg1 from-lport 1002 \ >> > + "inport == @pg1 && ip4 && tcp && tcp.dst == 80" allow-related >> > +check ovn-nbctl acl-add pg1 from-lport 1002 \ >> > + "inport == @pg1 && ip4 && udp && udp.dst == 53" allow-related >> > +check ovn-nbctl acl-add pg1 to-lport 1002 \ >> > + "outport == @pg1 && ip4 && tcp && tcp.src == 80" allow-related >> > +check ovn-nbctl acl-add pg1 to-lport 1002 \ >> > + "outport == @pg1 && ip4 && udp && udp.src == 53" allow-related >> > +check ovn-nbctl --wait=hv --log acl-add pg1 to-lport 100 "outport == >> @pg1" drop >> > + >> > +# Verify lflows do NOT have acl_ct_translation tag when option is >> disabled (default). >> > +check_row_count Logical_Flow 0 'tags:acl_ct_translation="true"' >> > + >> > +# Verify OpenFlows use packet header fields (tcp.dst, udp.dst) not CT >> fields. >> > +# When CT translation is OFF, we should see tp_dst matches in ACL >> tables. >> > +as hv1 >> > +AT_CHECK([ovs-ofctl dump-flows br-int table=$acl_in_eval | grep -q >> "tp_dst=80"], [0]) >> > + >> > +# Now enable ACL CT translation. >> > +check ovn-nbctl --wait=hv set NB_Global . >> options:acl_ct_translation=true >> > + >> > +# Verify lflows now have acl_ct_translation tag in the tags column. >> > +wait_row_count Logical_Flow 10 'tags:acl_ct_translation="true"' >> > + >> > +# Verify OpenFlows now use CT fields (ct_tp_dst) instead of packet >> headers. >> > +# When CT translation is ON, we should see ct_tp_dst matches in ACL >> tables. >> > +as hv1 >> > +AT_CHECK([ovs-ofctl dump-flows br-int table=$acl_in_eval | grep -q >> "ct_tp_dst=80"], [0]) >> > + >> > +# Now disable ACL CT translation again. >> > +check ovn-nbctl --wait=hv set NB_Global . >> options:acl_ct_translation=false >> > + >> > +# Verify lflows no longer have acl_ct_translation tag. >> > +wait_row_count Logical_Flow 0 'tags:acl_ct_translation="true"' >> > + >> > +# Verify OpenFlows reverted to packet header fields in ACL tables. >> > +as hv1 >> > +AT_CHECK([ovs-ofctl dump-flows br-int table=$acl_in_eval | grep -q >> "tp_dst=80"], [0]) >> > + >> > +OVN_CLEANUP([hv1]) >> > +AT_CLEANUP >> > +]) >> > diff --git a/tests/system-ovn.at b/tests/system-ovn.at >> > index a478dd709..f2c8cdb65 100644 >> > --- a/tests/system-ovn.at >> > +++ b/tests/system-ovn.at >> > @@ -18,7 +18,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -190,7 +190,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -363,7 +363,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -468,7 +468,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -573,7 +573,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -788,7 +788,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -1007,7 +1007,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -1314,7 +1314,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -1588,7 +1588,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -2044,7 +2044,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -2145,7 +2145,7 @@ ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > >> > -# Start ovn-controller >> > +# Start ovn-controller. >> > start_daemon ovn-controller >> > >> > # Logical network: >> > @@ -7296,7 +7296,7 @@ ADD_BR([br-int]) >> > ADD_BR([br-ext]) >> > >> > check ovs-ofctl add-flow br-ext action=normal >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -7838,7 +7838,7 @@ ADD_BR([br-int]) >> > ADD_BR([br-ext]) >> > >> > check ovs-ofctl add-flow br-ext action=normal >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -10463,7 +10463,7 @@ ovn_start >> > OVS_TRAFFIC_VSWITCHD_START() >> > ADD_BR([br-int]) >> > >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -11718,7 +11718,7 @@ ovn_start >> > OVS_TRAFFIC_VSWITCHD_START() >> > ADD_BR([br-int]) >> > >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -12570,7 +12570,7 @@ ADD_BR([br-int]) >> > ADD_BR([br-ext]) >> > >> > check ovs-ofctl add-flow br-ext action=normal >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -12793,7 +12793,7 @@ ADD_BR([br-int]) >> > ADD_BR([br-ext]) >> > >> > check ovs-ofctl add-flow br-ext action=normal >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -12919,7 +12919,7 @@ ADD_BR([br-int]) >> > ADD_BR([br-ext]) >> > >> > check ovs-ofctl add-flow br-ext action=normal >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -13946,7 +13946,7 @@ ADD_BR([br-int]) >> > ADD_BR([br-ext]) >> > >> > check ovs-ofctl add-flow br-ext action=normal >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -14085,7 +14085,7 @@ ADD_BR([br-int]) >> > ADD_BR([br-ext]) >> > >> > check ovs-ofctl add-flow br-ext action=normal >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -14986,7 +14986,7 @@ ovn_start >> > OVS_TRAFFIC_VSWITCHD_START() >> > ADD_BR([br-int]) >> > >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -17750,7 +17750,7 @@ ovn_start >> > OVS_TRAFFIC_VSWITCHD_START() >> > ADD_BR([br-int]) >> > >> > -# Set external-ids in br-int needed for ovn-controller >> > +# Set external-ids in br-int needed for ovn-controller. >> > check ovs-vsctl \ >> > -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > @@ -20997,3 +20997,399 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query >> port patch-.*/d >> > /connection dropped.*/d"]) >> > AT_CLEANUP >> > ]) >> >> While I appreciate the comment fixes above, they should be in a separate >> patch as they're not related to the ACL patch and would make backporting >> it even harder than it might be. >> >> > + >> > +OVN_FOR_EACH_NORTHD([ >> > +AT_SETUP([ACL CT translation - UDP fragmentation]) >> > +AT_KEYWORDS([acl_ct_translation_udp_fragmentation]) >> > +AT_SKIP_IF([test $HAVE_TCPDUMP = no]) >> > + >> > +CHECK_CONNTRACK() >> > + >> > +ovn_start >> > +OVS_TRAFFIC_VSWITCHD_START() >> > +ADD_BR([br-int]) >> > + >> > +# Set external-ids in br-int needed for ovn-controller >> > +check ovs-vsctl \ >> > + -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > + -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > + -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > + >> > +# Start ovn-controller. >> > +start_daemon ovn-controller >> > + >> > +# Set the minimal fragment size for userspace DP. >> > +ovs-appctl dpctl/ipf-set-min-frag v4 500 >> > + >> > +check ovn-nbctl ls-add internal >> > +check ovn-nbctl lsp-add internal client \ >> > + -- lsp-set-addresses client "f0:00:00:01:02:03 172.16.1.3" >> > +check ovn-nbctl lsp-add internal server \ >> > + -- lsp-set-addresses server "f0:00:0f:01:02:03 172.16.1.2" >> > + >> > +ADD_NAMESPACES(client) >> > +ADD_VETH(client, client, br-int, "172.16.1.3/24", >> "f0:00:00:01:02:03", \ >> > + "172.16.1.1") >> > +NS_EXEC([client], [ip l set dev client mtu 900]) >> > + >> > +ADD_NAMESPACES(server) >> > +ADD_VETH(server, server, br-int, "172.16.1.2/24", >> "f0:00:0f:01:02:03", \ >> > + "172.16.1.1") >> > +NS_EXEC([server], [ip l set dev server mtu 900]) >> > + >> > +# Create data file for traffic testing (8000 bytes will be fragmented >> with MTU 900). >> > +printf %8000s > datafile >> > + >> > +# Create port group with both client and server. >> > +client_uuid=$(fetch_column nb:logical_switch_port _uuid name=client) >> > +server_uuid=$(fetch_column nb:logical_switch_port _uuid name=server) >> > +check ovn-nbctl pg-add internal_vms $client_uuid $server_uuid >> > + >> > +# ACL rules - allow outgoing traffic. >> > +check ovn-nbctl acl-add internal_vms from-lport 1002 "inport == >> @internal_vms && ip4 && udp" allow-related >> > +check ovn-nbctl acl-add internal_vms from-lport 1002 "inport == >> @internal_vms && ip4" allow-related >> > + >> > +# ACL rules - allow incoming UDP on specific ports. >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && ip4 && udp && udp.dst == 5060" allow-related >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && ip4 && udp && udp.dst == 5061" allow-related >> > + >> > +# Allow ARP. >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && arp" allow-related >> > + >> > +# Drop rule. >> > +check ovn-nbctl --log --severity=info acl-add internal_vms to-lport >> 100 "outport == @internal_vms" drop >> > + >> > +# Enable ACL CT translation for fragmentation handling. >> > +check ovn-nbctl --wait=hv set NB_Global . >> options:acl_ct_translation=true >> > + >> > +check ovn-nbctl --wait=hv sync >> > +check ovs-appctl dpctl/flush-conntrack >> > + >> > +# Start tcpdump to capture IP fragments on both sides. >> > +NETNS_START_TCPDUMP([server], [-U -i server -Q in -nn ip and >> '(ip[[6:2]] & 0x3fff != 0)'], [tcpdump-udp-server]) >> > +NETNS_START_TCPDUMP([client], [-U -i client -Q in -nn ip and >> '(ip[[6:2]] & 0x3fff != 0)'], [tcpdump-udp-client]) >> > + >> > +# Start UDP listeners on both sides. >> > +NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 5060 > >> udp_server.rcvd], [server.pid]) >> > +NETNS_DAEMONIZE([client], [nc -l -u 172.16.1.3 5061 > >> udp_client.rcvd], [client.pid]) >> > + >> > +# Client sends to server (will be fragmented due to MTU 900). >> > +NS_CHECK_EXEC([client], [cat datafile | nc -w 1 -u 172.16.1.2 5060], >> [0], [ignore], [ignore]) >> > + >> > +# Server sends to client (will be fragmented due to MTU 900). >> > +NS_CHECK_EXEC([server], [cat datafile | nc -w 1 -u 172.16.1.3 5061], >> [0], [ignore], [ignore]) >> > + >> > +OVS_WAIT_UNTIL([test -s udp_server.rcvd]) >> > +OVS_WAIT_UNTIL([test -s udp_client.rcvd]) >> > + >> > +# Verify both sides received data. >> > +check cmp datafile udp_server.rcvd >> > +check cmp datafile udp_client.rcvd >> > + >> > +# Verify IP fragments were received on both sides (at least 5 >> fragments confirms fragmentation). >> > +OVS_WAIT_UNTIL([test $(cat tcpdump-udp-server.tcpdump | wc -l) -ge 5]) >> > +OVS_WAIT_UNTIL([test $(cat tcpdump-udp-client.tcpdump | wc -l) -ge 5]) >> > + >> > +OVN_CLEANUP_CONTROLLER([hv1]) >> > +OVN_CLEANUP_NORTHD >> > + >> > +as >> > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d >> > +/connection dropped.*/d >> > +/WARN|netdev@ovs-netdev: execute.*/d >> > +/dpif|WARN|system@ovs-system: execute.*/d >> > +"]) >> > +AT_CLEANUP >> > +]) >> > + >> > +OVN_FOR_EACH_NORTHD([ >> > +AT_SETUP([ACL CT translation - TCP traffic]) >> > +AT_KEYWORDS([acl_ct_translation_tcp_fragmentation]) >> > + >> > +CHECK_CONNTRACK() >> > + >> > +ovn_start >> > +OVS_TRAFFIC_VSWITCHD_START() >> > +ADD_BR([br-int]) >> > + >> > +# Set external-ids in br-int needed for ovn-controller >> > +check ovs-vsctl \ >> > + -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > + -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > + -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > + >> > +# Start ovn-controller >> > +start_daemon ovn-controller >> > + >> > +check ovn-nbctl ls-add internal >> > +check ovn-nbctl lsp-add internal client \ >> > + -- lsp-set-addresses client "f0:00:00:01:02:03 172.16.1.3" >> > +check ovn-nbctl lsp-add internal server \ >> > + -- lsp-set-addresses server "f0:00:0f:01:02:03 172.16.1.2" >> > + >> > +ADD_NAMESPACES(client) >> > +ADD_VETH(client, client, br-int, "172.16.1.3/24", >> "f0:00:00:01:02:03", \ >> > + "172.16.1.1") >> > + >> > +ADD_NAMESPACES(server) >> > +ADD_VETH(server, server, br-int, "172.16.1.2/24", >> "f0:00:0f:01:02:03", \ >> > + "172.16.1.1") >> > + >> > +# Create data file for traffic testing. >> > +printf %8000s > datafile >> > + >> > +# Create port group with both client and server. >> > +client_uuid=$(fetch_column nb:logical_switch_port _uuid name=client) >> > +server_uuid=$(fetch_column nb:logical_switch_port _uuid name=server) >> > +check ovn-nbctl pg-add internal_vms $client_uuid $server_uuid >> > + >> > +# ACL rules - allow outgoing traffic. >> > +check ovn-nbctl acl-add internal_vms from-lport 1002 "inport == >> @internal_vms && ip4 && tcp" allow-related >> > +check ovn-nbctl acl-add internal_vms from-lport 1002 "inport == >> @internal_vms && ip4" allow-related >> > + >> > +# ACL rules - allow incoming TCP on specific ports. >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && ip4 && tcp && tcp.dst == 8080" allow-related >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && ip4 && tcp && tcp.dst == 8081" allow-related >> > + >> > +# Allow ARP. >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && arp" allow-related >> > + >> > +# Drop rule. >> > +check ovn-nbctl --log --severity=info acl-add internal_vms to-lport >> 100 "outport == @internal_vms" drop >> > + >> > +# Enable ACL CT translation. >> > +check ovn-nbctl --wait=hv set NB_Global . >> options:acl_ct_translation=true >> > + >> > +check ovn-nbctl --wait=hv sync >> > +check ovs-appctl dpctl/flush-conntrack >> > + >> > +# Test client -> server direction. >> > +NETNS_DAEMONIZE([server], [nc -l 172.16.1.2 8080 > tcp_server.rcvd], >> [server.pid]) >> > +NS_CHECK_EXEC([client], [nc -w 1 172.16.1.2 8080 < datafile], [0], >> [ignore], [ignore]) >> > + >> > +OVS_WAIT_UNTIL([test -s tcp_server.rcvd]) >> > +check cmp datafile tcp_server.rcvd >> > + >> > +# Clean up first direction. >> > +kill $(cat server.pid) 2>/dev/null || true >> > + >> > +# Test server -> client direction. >> > +NETNS_DAEMONIZE([client], [nc -l 172.16.1.3 8081 > tcp_client.rcvd], >> [client.pid]) >> > +NS_CHECK_EXEC([server], [nc -w 1 172.16.1.3 8081 < datafile], [0], >> [ignore], [ignore]) >> > + >> > +OVS_WAIT_UNTIL([test -s tcp_client.rcvd]) >> > +check cmp datafile tcp_client.rcvd >> > + >> > +OVN_CLEANUP_CONTROLLER([hv1]) >> > +OVN_CLEANUP_NORTHD >> > + >> > +as >> > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d >> > +/connection dropped.*/d >> > +"]) >> > +AT_CLEANUP >> > +]) >> > + >> > +OVN_FOR_EACH_NORTHD([ >> > +AT_SETUP([ACL CT translation - negative test]) >> > +AT_KEYWORDS([acl_ct_translation_udp_fragmentation_negative]) >> > +AT_SKIP_IF([test $HAVE_TCPDUMP = no]) >> > + >> > +CHECK_CONNTRACK() >> > + >> > +ovn_start >> > +OVS_TRAFFIC_VSWITCHD_START() >> > +ADD_BR([br-int]) >> > + >> > +# Set external-ids in br-int needed for ovn-controller >> > +check ovs-vsctl \ >> > + -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > + -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > + -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > + >> > +# Start ovn-controller >> > +start_daemon ovn-controller >> > + >> > +check ovn-nbctl ls-add internal >> > +check ovn-nbctl lsp-add internal client \ >> > + -- lsp-set-addresses client "f0:00:00:01:02:03 172.16.1.3" >> > +check ovn-nbctl lsp-add internal server \ >> > + -- lsp-set-addresses server "f0:00:0f:01:02:03 172.16.1.2" >> > + >> > +ADD_NAMESPACES(client) >> > +ADD_VETH(client, client, br-int, "172.16.1.3/24", >> "f0:00:00:01:02:03", \ >> > + "172.16.1.1") >> > + >> > +ADD_NAMESPACES(server) >> > +ADD_VETH(server, server, br-int, "172.16.1.2/24", >> "f0:00:0f:01:02:03", \ >> > + "172.16.1.1") >> > + >> > +# Create port group with both client and server. >> > +client_uuid=$(fetch_column nb:logical_switch_port _uuid name=client) >> > +server_uuid=$(fetch_column nb:logical_switch_port _uuid name=server) >> > +check ovn-nbctl pg-add internal_vms $client_uuid $server_uuid >> > + >> > +# ACL rules - allow outgoing traffic. >> > +check ovn-nbctl acl-add internal_vms from-lport 1002 "inport == >> @internal_vms && ip4 && udp" allow-related >> > +check ovn-nbctl acl-add internal_vms from-lport 1002 "inport == >> @internal_vms && ip4" allow-related >> > + >> > +# ACL rules - allow incoming UDP ONLY on port 5060 (NOT 4000). >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && ip4 && udp && udp.dst == 5060" allow-related >> > + >> > +# Allow ARP. >> > +check ovn-nbctl acl-add internal_vms to-lport 1002 "outport == >> @internal_vms && arp" allow-related >> > + >> > +# Drop rule - this should drop traffic on port 4000. >> > +check ovn-nbctl --log --severity=info acl-add internal_vms to-lport >> 100 "outport == @internal_vms" drop >> > + >> > +# Enable ACL CT translation. >> > +check ovn-nbctl --wait=hv set NB_Global . >> options:acl_ct_translation=true >> > + >> > +check ovn-nbctl --wait=hv sync >> > +check ovs-appctl dpctl/flush-conntrack >> > + >> > +# Start tcpdump to capture any incoming packets on server. >> > +NETNS_START_TCPDUMP([server], [-U -i server -Q in -nn udp port 4000], >> [tcpdump-drop-server]) >> > + >> > +# Start UDP listener on server (on port 4000 which is NOT allowed by >> ACLs). >> > +NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 4000 > udp_drop.rcvd], >> [drop_server.pid]) >> > + >> > +# Client sends to server on disallowed port. >> > +NS_CHECK_EXEC([client], [echo "test" | nc -w 1 -u 172.16.1.2 4000], >> [0], [ignore], [ignore]) >> > + >> > +# Wait a bit for any packets to arrive. >> > +sleep 2 >> > + >> > +# Verify server did NOT receive any data (file should be empty or not >> exist). >> > +AT_CHECK([test ! -s udp_drop.rcvd], [0]) >> > + >> > +# Verify tcpdump captured no packets (traffic was dropped by ACL). >> > +AT_CHECK([test $(cat tcpdump-drop-server.tcpdump | wc -l) -eq 0], [0]) >> > + >> > +OVN_CLEANUP_CONTROLLER([hv1]) >> > +OVN_CLEANUP_NORTHD >> > + >> > +as >> > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d >> > +/connection dropped.*/d >> > +"]) >> > +AT_CLEANUP >> > +]) >> > + >> > +OVN_FOR_EACH_NORTHD([ >> > +AT_SETUP([ACL CT translation with DHCP traffic]) >> > +AT_KEYWORDS([acl_ct_translation_dhcp]) >> > +AT_SKIP_IF([test $HAVE_DHCPD = no]) >> > +AT_SKIP_IF([test $HAVE_DHCLIENT = no]) >> > + >> > +CHECK_CONNTRACK() >> > + >> > +ovn_start >> > +OVS_TRAFFIC_VSWITCHD_START() >> > +ADD_BR([br-int]) >> > + >> > +# Set external-ids in br-int needed for ovn-controller >> > +check ovs-vsctl \ >> > + -- set Open_vSwitch . external-ids:system-id=hv1 \ >> > + -- set Open_vSwitch . >> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ >> > + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ >> > + -- set bridge br-int fail-mode=secure >> other-config:disable-in-band=true >> > + >> > +# Start ovn-controller >> > +start_daemon ovn-controller >> > + >> > +# Create logical switch. >> > +check ovn-nbctl ls-add ls1 >> > + >> > +# Create DHCP server port. >> > +check ovn-nbctl lsp-add ls1 dhcp-server \ >> > + -- lsp-set-addresses dhcp-server "00:00:00:00:02:00 192.168.1.2" >> > + >> > +# Create DHCP client port. >> > +check ovn-nbctl lsp-add ls1 dhcp-client \ >> > + -- lsp-set-addresses dhcp-client "00:00:00:00:02:01" >> > + >> > +# Add OVS ports. >> > +ADD_NAMESPACES(server, client) >> > +ADD_VETH(server, server, br-int, "192.168.1.2/24", >> "00:00:00:00:02:00") >> > +ADD_VETH(client, client, br-int, "0.0.0.0/0", "00:00:00:00:02:01") >> > + >> > +# Bind logical ports to OVS ports. >> > +check ovs-vsctl set Interface ovs-server >> external_ids:iface-id=dhcp-server >> > +check ovs-vsctl set Interface ovs-client >> external_ids:iface-id=dhcp-client >> > + >> > +# Create port group with both ports. >> > +server_uuid=$(fetch_column nb:logical_switch_port _uuid >> name=dhcp-server) >> > +client_uuid=$(fetch_column nb:logical_switch_port _uuid >> name=dhcp-client) >> > +check ovn-nbctl pg-add dhcp_vms $server_uuid $client_uuid >> > + >> > +# Add ACL rules - allow outgoing traffic from both ports. >> > +check ovn-nbctl acl-add dhcp_vms from-lport 1002 "inport == @dhcp_vms >> && ip4" allow-related >> > +check ovn-nbctl acl-add dhcp_vms from-lport 1002 "inport == @dhcp_vms >> && udp" allow-related >> > + >> > +# Add ACL rules for DHCP traffic (ports 67 and 68). >> > +check ovn-nbctl acl-add dhcp_vms to-lport 1002 "outport == @dhcp_vms >> && ip4 && udp && udp.dst == 67" allow-related >> > +check ovn-nbctl acl-add dhcp_vms to-lport 1002 "outport == @dhcp_vms >> && ip4 && udp && udp.dst == 68" allow-related >> > + >> > +# Allow ARP and broadcast. >> > +check ovn-nbctl acl-add dhcp_vms to-lport 1002 "outport == @dhcp_vms >> && arp" allow-related >> > + >> > +# Add drop rule. >> > +check ovn-nbctl --log --severity=info acl-add dhcp_vms to-lport 100 >> "outport == @dhcp_vms" drop >> > + >> > +# Enable ACL CT translation. >> > +check ovn-nbctl --wait=hv set NB_Global . >> options:acl_ct_translation=true >> > + >> > +# Wait for flows to be installed. >> > +check ovn-nbctl --wait=hv sync >> > + >> > +# Setup DHCP server configuration. >> > +DHCP_TEST_DIR="$ovs_base/dhcp-test" >> > +mkdir -p $DHCP_TEST_DIR >> > + >> > +cat > $DHCP_TEST_DIR/dhcpd.conf <<EOF >> > +subnet 192.168.1.0 netmask 255.255.255.0 { >> > + range 192.168.1.100 192.168.1.100; >> > + option routers 192.168.1.2; >> > + option broadcast-address 192.168.1.255; >> > + default-lease-time 60; >> > + max-lease-time 120; >> > +} >> > +EOF >> > + >> > +touch $DHCP_TEST_DIR/dhcpd.leases >> > +chown root:dhcpd $DHCP_TEST_DIR $DHCP_TEST_DIR/dhcpd.leases >> > +chmod 775 $DHCP_TEST_DIR >> > +chmod 664 $DHCP_TEST_DIR/dhcpd.leases >> > + >> > +# Start dhcpd as DHCP server in the server namespace. >> > +NETNS_DAEMONIZE([server], [dhcpd -4 -f -cf $DHCP_TEST_DIR/dhcpd.conf >> server > $DHCP_TEST_DIR/dhcpd.log 2>&1], [dhcpd.pid]) >> > + >> > +# Give dhcpd time to start. >> > +sleep 1 >> > + >> > +# Request IP via DHCP using dhclient. >> > +NS_CHECK_EXEC([client], [dhclient -1 -v -lf >> $DHCP_TEST_DIR/dhclient.lease -pf $DHCP_TEST_DIR/dhclient.pid client], [0], >> [ignore], [ignore]) >> > +# Register cleanup handler to kill dhclient when test exits. >> > +on_exit 'kill $(cat $DHCP_TEST_DIR/dhclient.pid) 2>/dev/null || true' >> > + >> > +# Verify client got an IP address from DHCP. >> > +NS_CHECK_EXEC([client], [ip addr show client | grep -q >> "192.168.1.100"], [0]) >> > + >> > +OVN_CLEANUP_CONTROLLER([hv1]) >> > + >> > +OVN_CLEANUP_NORTHD >> > + >> > +as >> > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d >> > +/connection dropped.*/d >> > +"]) >> > +AT_CLEANUP >> > +]) >> >> > I addressed the nits, added "Reported-at: > https://issues.redhat.com/browse/FDP-1992" for our internal tracking and > merged this into main. > > Regards, > Ales > _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
