v3: Rebased on current upstream main, clean ups. Fix DHCP/broadcast bug. 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]> --- NEWS | 6 + controller/lflow.c | 23 +- include/ovn/logical-fields.h | 3 + lib/logical-fields.c | 67 ++++++ northd/en-global-config.c | 5 + northd/lflow-mgr.c | 50 ++++- northd/lflow-mgr.h | 6 +- northd/northd.c | 78 +++++-- ovn-nb.xml | 23 ++ tests/atlocal.in | 3 + tests/ovn.at | 75 +++++++ tests/system-ovn.at | 396 +++++++++++++++++++++++++++++++++++ 12 files changed, 708 insertions(+), 27 deletions(-) diff --git a/NEWS b/NEWS index bb550fe59..4a92d0881 100644 --- a/NEWS +++ b/NEWS @@ -101,6 +101,12 @@ Post v25.09.0 - Add "distributed" option for load balancer, that forces traffic to be routed only to backend instances running locally on the same chassis it arrives on. + - 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 + 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 b6be5c630..95da1095c 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 f0d34196a..8f23249a0 100644 --- a/include/ovn/logical-fields.h +++ b/include/ovn/logical-fields.h @@ -236,6 +236,9 @@ 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); +void expr_symtab_remove(struct shash *symtab, const char *name); + /* 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 c8bddcdc5..334a7e244 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. */ +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/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..8f6121ac7 100644 --- a/northd/lflow-mgr.c +++ b/northd/lflow-mgr.c @@ -184,6 +184,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. */ @@ -725,7 +726,7 @@ lflow_ref_sync_lflows(struct lflow_ref *lflow_ref, * then it may corrupt the hmap. Caller should ensure thread safety * for such scenarios. */ -static void +static struct ovn_lflow * 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, @@ -798,9 +799,10 @@ lflow_table_add_lflow__(struct lflow_table *lflow_table, ovn_dp_group_add_with_reference(lflow, sdp, dp_bitmap, dp_bitmap_len); lflow_hash_unlock(hash_lock); + return lflow; } -void +struct ovn_lflow * lflow_table_add_lflow(struct lflow_table_add_args *args) { /* It is invalid for both args->dp_bitmap and args->sdp to be @@ -811,11 +813,17 @@ lflow_table_add_lflow(struct lflow_table_add_args *args) args->sdp = NULL; } - lflow_table_add_lflow__(args->table, args->sdp, args->dp_bitmap, - 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); + struct ovn_lflow *lflow = lflow_table_add_lflow__(args->table, args->sdp, + args->dp_bitmap, 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); + if (lflow) { + lflow->acl_ct_translation = args->acl_ct_translation; + } + return lflow; } struct ovn_dp_group * @@ -945,6 +953,7 @@ ovn_lflow_init(struct ovn_lflow *lflow, lflow->stage_hint = stage_hint; lflow->ctrl_meter = ctrl_meter; lflow->flow_desc = flow_desc; + lflow->acl_ct_translation = false; lflow->dpg = NULL; lflow->where = where; lflow->sb_uuid = sbuuid; @@ -1122,12 +1131,20 @@ 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); - if (lflow->io_port) { + + /* Set tags for io_port and/or acl_ct_translation if needed. */ + if (lflow->io_port || lflow->acl_ct_translation) { struct smap tags = SMAP_INITIALIZER(&tags); - smap_add(&tags, "in_out_port", lflow->io_port); + if (lflow->io_port) { + smap_add(&tags, "in_out_port", lflow->io_port); + } + if (lflow->acl_ct_translation) { + smap_add(&tags, "acl_ct_translation", "true"); + } sbrec_logical_flow_set_tags(sbflow, &tags); smap_destroy(&tags); } + sbrec_logical_flow_set_controller_meter(sbflow, lflow->ctrl_meter); /* Trim the source locator lflow->where, which looks something like @@ -1193,6 +1210,21 @@ sync_lflow_to_sb(struct ovn_lflow *lflow, } } } + + /* Update acl_ct_translation marker in tags if needed. + * This must be outside ovn_internal_version_changed check because + * the option can be enabled/disabled at runtime. */ + bool cur_has_ct_trans = smap_get_bool(&sbflow->tags, + "acl_ct_translation", false); + if (lflow->acl_ct_translation != cur_has_ct_trans) { + if (lflow->acl_ct_translation) { + sbrec_logical_flow_update_tags_setkey( + sbflow, "acl_ct_translation", "true"); + } else { + sbrec_logical_flow_update_tags_delkey( + sbflow, "acl_ct_translation"); + } + } } if (lflow->dp) { diff --git a/northd/lflow-mgr.h b/northd/lflow-mgr.h index 4a1655c35..f7f4bd538 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); - +struct ovn_lflow *lflow_table_add_lflow(struct lflow_table_add_args *args); #define WITH_HINT(HINT) .stage_hint = HINT +#define WITH_CT_TRANSLATION .acl_ct_translation = true /* 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 2df205ec2..d5d0af8ae 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -83,12 +83,18 @@ static bool check_lsp_is_up; static bool install_ls_lb_from_router; /* Use common zone for SNAT and DNAT if this option is set to "true". */ -static bool use_common_zone = false; +static bool use_common_zone; /* If this option is 'true' northd will make use of ct.inv match fields. * 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. @@ -6298,11 +6304,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). * @@ -7278,6 +7292,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")) { @@ -7295,6 +7314,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; @@ -7363,8 +7383,15 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od, (uint8_t) acl->network_function_group->id); } ds_put_cstr(actions, "next;"); - ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), - ds_cstr(actions), lflow_ref, WITH_HINT(&acl->header_)); + if (needs_ct_trans) { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_), WITH_CT_TRANSLATION); + } else { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_)); + } /* Match on traffic in the request direction for an established * connection tracking entry that has not been marked for @@ -7393,8 +7420,15 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od, (uint8_t) acl->network_function_group->id); } ds_put_cstr(actions, "next;"); - ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), - ds_cstr(actions), lflow_ref, WITH_HINT(&acl->header_)); + if (needs_ct_trans) { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_), WITH_CT_TRANSLATION); + } else { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_)); + } } else if (!strcmp(acl->action, "drop") || !strcmp(acl->action, "reject")) { if (acl->network_function_group) { @@ -7419,8 +7453,15 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od, build_acl_sample_label_action(actions, acl, acl->sample_new, NULL, 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_)); + if (needs_ct_trans) { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_), WITH_CT_TRANSLATION); + } else { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_)); + } /* 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 @@ -7445,8 +7486,15 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od, ds_put_format(actions, "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_)); + if (needs_ct_trans) { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_), WITH_CT_TRANSLATION); + } else { + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match), + ds_cstr(actions), lflow_ref, + WITH_HINT(&acl->header_)); + } } } @@ -20655,6 +20703,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 aab091883..8ea0e0267 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 + 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/atlocal.in b/tests/atlocal.in index 477d56a0f..5ee8abc20 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -192,6 +192,9 @@ find_command dhcpd # Set HAVE_DHCLIENT find_command dhclient +# Set HAVE_DNSMASQ +find_command dnsmasq + # Set HAVE_BFDD_BEACON find_command bfdd-beacon diff --git a/tests/ovn.at b/tests/ovn.at index 802e6d0da..2f83d6751 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -44594,3 +44594,78 @@ check ovn-nbctl --wait=hv lsp-set-type down_ext localnet OVN_CLEANUP([hv1],[hv2]) 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 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=lp1 +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=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port name=lp1) +lp2_uuid=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port 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 82b7e7db5..375fa73a5 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -20993,3 +20993,399 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d /connection dropped.*/d"]) AT_CLEANUP ]) + +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=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port name=client) +server_uuid=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port 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 -N -u 172.16.1.2 5060 > udp_server.rcvd], [server.pid]) +NETNS_DAEMONIZE([client], [nc -l -N -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=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port name=client) +server_uuid=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port 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 -N 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 -N 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=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port name=client) +server_uuid=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port 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 -N -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=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port name=dhcp-server) +client_uuid=$(ovn-nbctl --bare --columns=_uuid find logical_switch_port 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 +]) -- 2.43.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
