On Mon, Nov 24, 2025 at 9:45 AM Ales Musil <[email protected]> wrote:
> > > On Mon, Nov 17, 2025 at 6:15 AM Han Zhou <[email protected]> wrote: > >> This patch introduces flow-based tunnels as an alternative to >> traditional port-based tunnels, significantly reducing tunnel port count >> in large deployments. >> >> Flow-based tunnels use shared ports (one per tunnel type) with >> options:local_ip=flow and options:remote_ip=flow. OpenFlow flows >> dynamically set tunnel endpoints using set_field actions, reducing port >> count to O(T) where T is the number of tunnel types. >> >> The feature is experimental, and controlled by >> external_ids:ovn-enable-flow-based-tunnels (default: false). >> >> Some known limitations: >> - IPsec is not supported >> - BFD between tunnel endpoints is not supported, thus HA chassis not >> supported. >> >> Assisted-by: Cursor, with model: Claude Sonnet 4.5 >> Signed-off-by: Han Zhou <[email protected]> >> --- >> > > Hi Han, > > thank you for the v3. Seems like ovn-k jobs are failing for this > patch. Let's try to re-run them again. Other than that it looks good. > > Recheck-request: github-robot-_ovn-kubernetes > > v2: >> - Rebase on latest main. >> - Revise for comments from Mark and Dumitru. >> - Added a prepare patch for refactoring chassis_tunnel_type enum. >> - Removed the patch2 from v1 because it is merged. >> v3: >> - Revise for comments from Ales and Mark. >> - Removed the patch2 from v2 because it is merged. >> >> NEWS | 10 +- >> controller/bfd.c | 16 +- >> controller/bfd.h | 3 +- >> controller/encaps.c | 240 +++++++++++++++---- >> controller/encaps.h | 15 ++ >> controller/local_data.c | 188 ++++++++++++++- >> controller/local_data.h | 32 ++- >> controller/ovn-controller.8.xml | 30 +++ >> controller/ovn-controller.c | 26 ++- >> controller/physical.c | 403 +++++++++++++++++++++++++++----- >> controller/physical.h | 3 + >> lib/ovn-util.h | 3 +- >> tests/ovn-macros.at | 31 ++- >> tests/ovn.at | 135 +++++++++-- >> tests/ovs-macros.at | 4 +- >> 15 files changed, 993 insertions(+), 146 deletions(-) >> >> diff --git a/NEWS b/NEWS >> index 754934b6b9d3..59943130814e 100644 >> --- a/NEWS >> +++ b/NEWS >> @@ -60,9 +60,13 @@ Post v25.09.0 >> specified LS. >> - Add ovn-nbctl lsp-add-localnet-port which will create localnet port >> on >> specified LS. >> - - Add a new experimental service - ovn-br-controller to program and >> - manage OVS bridges (not managed by ovn-controller) using OVN logical >> flows. >> - For more details see man ovn-br(5). >> + - Add a new experimental service - ovn-br-controller to program and >> + manage OVS bridges (not managed by ovn-controller) using OVN logical >> + flows. For more details see man ovn-br(5). >> + - Added experimental flow-based tunnel support. Enable via >> + external_ids:ovn-enable-flow-based-tunnels=true to use shared tunnel >> + ports instead of per-chassis ports, reducing port count for large >> scale >> + environments. Default is disabled. >> >> OVN v25.09.0 - xxx xx xxxx >> -------------------------- >> diff --git a/controller/bfd.c b/controller/bfd.c >> index 9889b7e7a4cb..3b0c3f6dae0f 100644 >> --- a/controller/bfd.c >> +++ b/controller/bfd.c >> @@ -70,7 +70,7 @@ bfd_calculate_active_tunnels(const struct ovsrec_bridge >> *br_int, >> "state"); >> if (status && !strcmp(status, "up")) { >> const char *id = >> smap_get(&port_rec->external_ids, >> - "ovn-chassis-id"); >> + OVN_TUNNEL_ID); >> if (id) { >> char *chassis_name = NULL; >> >> @@ -185,7 +185,8 @@ bfd_run(const struct ovsrec_interface_table >> *interface_table, >> const struct ovsrec_bridge *br_int, >> const struct sset *bfd_chassis, >> const struct sbrec_chassis *chassis_rec, >> - const struct sbrec_sb_global_table *sb_global_table) >> + const struct sbrec_sb_global_table *sb_global_table, >> + const struct ovsrec_open_vswitch_table *ovs_table) >> { >> if (!chassis_rec) { >> return; >> @@ -196,7 +197,7 @@ bfd_run(const struct ovsrec_interface_table >> *interface_table, >> struct sset bfd_ifaces = SSET_INITIALIZER(&bfd_ifaces); >> for (size_t k = 0; k < br_int->n_ports; k++) { >> const char *tunnel_id = smap_get(&br_int->ports[k]->external_ids, >> - "ovn-chassis-id"); >> + OVN_TUNNEL_ID); >> if (tunnel_id) { >> char *chassis_name = NULL; >> char *port_name = br_int->ports[k]->name; >> @@ -212,6 +213,15 @@ bfd_run(const struct ovsrec_interface_table >> *interface_table, >> } >> } >> >> + /* Warn if BFD is needed but flow-based tunnels are enabled */ >> + if (is_flow_based_tunnels_enabled(ovs_table, chassis_rec) >> + && !sset_is_empty(bfd_chassis)) { >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); >> + VLOG_WARN_RL(&rl, "Flow-based tunnels are enabled but BFD is >> required " >> + "for HA chassis groups. BFD will not function >> correctly " >> + "with flow-based tunnels."); >> + } >> + >> const struct sbrec_sb_global *sb >> = sbrec_sb_global_table_first(sb_global_table); >> struct smap bfd = SMAP_INITIALIZER(&bfd); >> diff --git a/controller/bfd.h b/controller/bfd.h >> index be0d599b7cee..f8fece5a580b 100644 >> --- a/controller/bfd.h >> +++ b/controller/bfd.h >> @@ -33,7 +33,8 @@ void bfd_run(const struct ovsrec_interface_table *, >> const struct ovsrec_bridge *, >> const struct sset *, >> const struct sbrec_chassis *, >> - const struct sbrec_sb_global_table *); >> + const struct sbrec_sb_global_table *, >> + const struct ovsrec_open_vswitch_table *); >> >> void bfd_calculate_chassis( >> const struct sbrec_chassis *, >> diff --git a/controller/encaps.c b/controller/encaps.c >> index 3ed42534b36a..61f41bf3acb5 100644 >> --- a/controller/encaps.c >> +++ b/controller/encaps.c >> @@ -29,14 +29,6 @@ >> >> VLOG_DEFINE_THIS_MODULE(encaps); >> >> -/* >> - * Given there could be multiple tunnels with different IPs to the same >> - * chassis we annotate the external_ids:ovn-chassis-id in tunnel port >> with >> - * <chassis_name>@<remote IP>%<local IP>. The external_id key >> - * "ovn-chassis-id" is kept for backward compatibility. >> - */ >> -#define OVN_TUNNEL_ID "ovn-chassis-id" >> - >> static char *current_br_int_name = NULL; >> >> void >> @@ -550,6 +542,172 @@ create_evpn_tunnels(struct tunnel_ctx *tc) >> } >> >> >> +bool >> +is_flow_based_tunnels_enabled( >> + const struct ovsrec_open_vswitch_table *ovs_table, >> + const struct sbrec_chassis *chassis) >> +{ >> + const struct ovsrec_open_vswitch *cfg = >> + ovsrec_open_vswitch_table_first(ovs_table); >> + >> + return cfg ? get_chassis_external_id_value_bool( >> + &cfg->external_ids, chassis->name, >> + "ovn-enable-flow-based-tunnels", false) >> + : false; >> +} >> + >> +static char * >> +flow_based_tunnel_name(const char *tunnel_type, const char *chassis_idx) >> +{ >> + return xasprintf("ovn%s-%s", chassis_idx, tunnel_type); >> +} >> + >> +static void >> +flow_based_tunnel_ensure(struct tunnel_ctx *tc, const char *tunnel_type, >> + const char *port_name, >> + const struct sbrec_sb_global *sbg, >> + const struct ovsrec_open_vswitch_table >> *ovs_table) >> +{ >> + /* Check if flow-based tunnel already exists. */ >> + const struct ovsrec_port *existing_port = NULL; >> + for (size_t i = 0; i < tc->br_int->n_ports; i++) { >> + const struct ovsrec_port *port = tc->br_int->ports[i]; >> + if (!strcmp(port->name, port_name)) { >> + existing_port = port; >> + break; >> + } >> + } >> + >> + if (existing_port) { >> + return; >> + } >> + >> + /* Create flow-based tunnel port. */ >> + struct smap options = SMAP_INITIALIZER(&options); >> + smap_add(&options, "remote_ip", "flow"); >> + smap_add(&options, "local_ip", "flow"); >> + smap_add(&options, "key", "flow"); >> + >> + if (sbg->ipsec) { >> + /* For flow-based tunnels, we can't specify remote_name since >> + * remote chassis varies. IPsec will need to handle this >> differently. >> + */ >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); >> + VLOG_WARN_RL(&rl, "IPsec is not supported for flow-based >> tunnels. " >> + "Ignoring IPsec settings."); >> + } >> + >> + /* Add other tunnel options from OVS config. */ >> + const struct ovsrec_open_vswitch *cfg = >> + ovsrec_open_vswitch_table_first(ovs_table); >> + if (cfg) { >> + const char *encap_tos = >> + get_chassis_external_id_value(&cfg->external_ids, >> + tc->this_chassis->name, >> + "ovn-encap-tos", "none"); >> + if (encap_tos && strcmp(encap_tos, "none")) { >> + smap_add(&options, "tos", encap_tos); >> + } >> + >> + const char *encap_df = >> + get_chassis_external_id_value(&cfg->external_ids, >> + tc->this_chassis->name, >> + "ovn-encap-df_default", NULL); >> + if (encap_df) { >> + smap_add(&options, "df_default", encap_df); >> + } >> + } >> + >> + /* Create interface. */ >> + struct ovsrec_interface *iface = >> ovsrec_interface_insert(tc->ovs_txn); >> + ovsrec_interface_set_name(iface, port_name); >> + ovsrec_interface_set_type(iface, tunnel_type); >> + ovsrec_interface_set_options(iface, &options); >> + >> + /* Create port. */ >> + struct ovsrec_port *port = ovsrec_port_insert(tc->ovs_txn); >> + ovsrec_port_set_name(port, port_name); >> + ovsrec_port_set_interfaces(port, &iface, 1); >> + >> + /* Set external IDs to mark as flow-based tunnel using unified >> + * OVN_TUNNEL_ID. */ >> + const struct smap external_ids = SMAP_CONST2(&external_ids, >> + OVN_TUNNEL_ID, "flow", >> + "ovn-tunnel-type", >> + tunnel_type); >> + ovsrec_port_set_external_ids(port, &external_ids); >> + >> + /* Add to bridge. */ >> + ovsrec_bridge_update_ports_addvalue(tc->br_int, port); >> + >> + VLOG_INFO("Created flow-based %s tunnel port: %s", tunnel_type, >> port_name); >> + >> + smap_destroy(&options); >> +} >> + >> +static void >> +create_flow_based_tunnels(struct tunnel_ctx *tc, >> + const struct sbrec_sb_global *sbg) >> +{ >> + struct sset tunnel_types = SSET_INITIALIZER(&tunnel_types); >> + >> + for (size_t i = 0; i < tc->this_chassis->n_encaps; i++) { >> + sset_add(&tunnel_types, tc->this_chassis->encaps[i]->type); >> + } >> + >> + const char *tunnel_type; >> + SSET_FOR_EACH (tunnel_type, &tunnel_types) { >> + char *port_name = flow_based_tunnel_name(tunnel_type, >> + >> get_chassis_idx(tc->ovs_table)); >> + flow_based_tunnel_ensure(tc, tunnel_type, port_name, sbg, >> + tc->ovs_table); >> + /* Remove any existing tunnel with this name from tracking so it >> + * doesn't get deleted. */ >> + struct tunnel_node *exist_tun = >> shash_find_and_delete(&tc->tunnel, >> + port_name); >> + free(exist_tun); >> + free(port_name); >> + } >> + >> + sset_destroy(&tunnel_types); >> +} >> + >> +static void >> +create_port_based_tunnels(struct tunnel_ctx *tc, >> + const struct sbrec_chassis_table >> *chassis_table, >> + const struct sbrec_sb_global *sbg, >> + const struct sset *transport_zones) >> +{ >> + const struct sbrec_chassis *chassis_rec; >> + SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) { >> + if (strcmp(chassis_rec->name, tc->this_chassis->name)) { >> + /* Create tunnels to the other Chassis belonging to the >> + * same transport zone */ >> + if (!chassis_tzones_overlap(transport_zones, chassis_rec)) { >> + VLOG_DBG("Skipping encap creation for Chassis '%s' >> because " >> + "it belongs to different transport zones", >> + chassis_rec->name); >> + continue; >> + } >> + >> + if (smap_get_bool(&chassis_rec->other_config, "is-remote", >> false) >> + && !smap_get_bool(&tc->this_chassis->other_config, >> + "is-interconn", false)) { >> + VLOG_DBG("Skipping encap creation for Chassis '%s' >> because " >> + "it is remote but this chassis is not >> interconn.", >> + chassis_rec->name); >> + continue; >> + } >> + >> + if (chassis_tunnel_add(chassis_rec, sbg, tc->ovs_table, tc, >> + tc->this_chassis) == 0) { >> + VLOG_INFO("Creating encap for '%s' failed", >> chassis_rec->name); >> + continue; >> + } >> + } >> + } >> +} >> + >> void >> encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> struct ovsdb_idl_txn *ovnsb_idl_txn, >> @@ -565,6 +723,11 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> return; >> } >> >> + bool use_flow_based = is_flow_based_tunnels_enabled(ovs_table, >> + this_chassis); >> + VLOG_DBG("Using %s tunnels for this chassis.", >> + use_flow_based ? "flow-based" : "port-based"); >> + >> if (!current_br_int_name) { >> /* The controller has just started, we need to look through all >> * bridges for old tunnel ports. */ >> @@ -594,8 +757,6 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> current_br_int_name = xstrdup(br_int->name); >> } >> >> - const struct sbrec_chassis *chassis_rec; >> - >> struct tunnel_ctx tc = { >> .tunnel = SHASH_INITIALIZER(&tc.tunnel), >> .port_names = SSET_INITIALIZER(&tc.port_names), >> @@ -610,24 +771,30 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> "ovn-controller: modifying OVS tunnels >> '%s'", >> this_chassis->name); >> >> - /* Collect all port names into tc.port_names. >> - * >> - * Collect all the OVN-created tunnels into tc.tunnel_hmap. */ >> + /* Collect existing port names and tunnel ports for cleanup. */ >> for (size_t i = 0; i < br_int->n_ports; i++) { >> const struct ovsrec_port *port = br_int->ports[i]; >> sset_add(&tc.port_names, port->name); >> >> const char *id = smap_get(&port->external_ids, OVN_TUNNEL_ID); >> if (id) { >> - if (!shash_find(&tc.tunnel, id)) { >> - struct tunnel_node *tunnel = xzalloc(sizeof *tunnel); >> - tunnel->bridge = br_int; >> - tunnel->port = port; >> - shash_add_assert(&tc.tunnel, id, tunnel); >> + struct tunnel_node *tunnel = xzalloc(sizeof *tunnel); >> + tunnel->bridge = br_int; >> + tunnel->port = port; >> + >> + if (use_flow_based) { >> + /* Flow-based: track by port name */ >> + shash_add(&tc.tunnel, port->name, tunnel); >> } else { >> - /* Duplicate port for tunnel-id. Arbitrarily choose >> - * to delete this one. */ >> - ovsrec_bridge_update_ports_delvalue(br_int, port); >> + /* Port-based: track by tunnel ID, handle duplicates */ >> + if (!shash_find(&tc.tunnel, id)) { >> + shash_add_assert(&tc.tunnel, id, tunnel); >> + } else { >> + /* Duplicate port for tunnel-id. Arbitrarily choose >> + * to delete this one. */ >> + ovsrec_bridge_update_ports_delvalue(br_int, port); >> + free(tunnel); >> + } >> } >> } >> >> @@ -636,32 +803,11 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> } >> } >> >> - SBREC_CHASSIS_TABLE_FOR_EACH (chassis_rec, chassis_table) { >> - if (strcmp(chassis_rec->name, this_chassis->name)) { >> - /* Create tunnels to the other Chassis belonging to the >> - * same transport zone */ >> - if (!chassis_tzones_overlap(transport_zones, chassis_rec)) { >> - VLOG_DBG("Skipping encap creation for Chassis '%s' >> because " >> - "it belongs to different transport zones", >> - chassis_rec->name); >> - continue; >> - } >> - >> - if (smap_get_bool(&chassis_rec->other_config, "is-remote", >> false) >> - && !smap_get_bool(&this_chassis->other_config, >> "is-interconn", >> - false)) { >> - VLOG_DBG("Skipping encap creation for Chassis '%s' >> because " >> - "it is remote but this chassis is not >> interconn.", >> - chassis_rec->name); >> - continue; >> - } >> - >> - if (chassis_tunnel_add(chassis_rec, sbg, ovs_table, &tc, >> - this_chassis) == 0) { >> - VLOG_INFO("Creating encap for '%s' failed", >> chassis_rec->name); >> - continue; >> - } >> - } >> + /* Create OVN tunnels (mode-specific). */ >> + if (use_flow_based) { >> + create_flow_based_tunnels(&tc, sbg); >> + } else { >> + create_port_based_tunnels(&tc, chassis_table, sbg, >> transport_zones); >> } >> >> create_evpn_tunnels(&tc); >> diff --git a/controller/encaps.h b/controller/encaps.h >> index 3f4a82d683f2..fa5dc17e5fdb 100644 >> --- a/controller/encaps.h >> +++ b/controller/encaps.h >> @@ -18,6 +18,17 @@ >> >> #include <stdbool.h> >> >> +/* >> + * Given there could be multiple tunnels with different IPs to the same >> + * chassis we annotate the external_ids:ovn-chassis-id in tunnel port >> with >> + * <chassis_name>@<remote IP>%<local IP>. The external_id key >> + * "ovn-chassis-id" is kept for backward compatibility. >> + * >> + * For flow-based tunnels, we use the special value "flow" to identify >> + * shared tunnel ports that handle dynamic endpoint resolution. >> + */ >> +#define OVN_TUNNEL_ID "ovn-chassis-id" >> + >> struct ovsdb_idl; >> struct ovsdb_idl_txn; >> struct ovsrec_bridge; >> @@ -39,6 +50,10 @@ void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> const struct sset *transport_zones, >> const struct ovsrec_bridge_table *bridge_table); >> >> +bool is_flow_based_tunnels_enabled( >> + const struct ovsrec_open_vswitch_table *ovs_table, >> + const struct sbrec_chassis *chassis); >> + >> bool encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn, >> const struct ovsrec_bridge *br_int); >> >> diff --git a/controller/local_data.c b/controller/local_data.c >> index a35d3fa5ae37..dda746d73ef4 100644 >> --- a/controller/local_data.c >> +++ b/controller/local_data.c >> @@ -51,6 +51,10 @@ static struct tracked_datapath >> *tracked_datapath_create( >> enum en_tracked_resource_type tracked_type, >> struct hmap *tracked_datapaths); >> >> +static void track_flow_based_tunnel( >> + const struct ovsrec_port *, const struct sbrec_chassis *, >> + struct flow_based_tunnel *flow_tunnels); >> + >> static bool datapath_is_switch(const struct sbrec_datapath_binding *); >> static bool datapath_is_transit_switch(const struct >> sbrec_datapath_binding *); >> >> @@ -454,7 +458,8 @@ void >> local_nonvif_data_run(const struct ovsrec_bridge *br_int, >> const struct sbrec_chassis *chassis_rec, >> struct simap *patch_ofports, >> - struct hmap *chassis_tunnels) >> + struct hmap *chassis_tunnels, >> + struct flow_based_tunnel *flow_tunnels) >> { >> for (int i = 0; i < br_int->n_ports; i++) { >> const struct ovsrec_port *port_rec = br_int->ports[i]; >> @@ -463,13 +468,15 @@ local_nonvif_data_run(const struct ovsrec_bridge >> *br_int, >> } >> >> const char *tunnel_id = smap_get(&port_rec->external_ids, >> - "ovn-chassis-id"); >> + OVN_TUNNEL_ID); >> if (tunnel_id && encaps_tunnel_id_match(tunnel_id, >> chassis_rec->name, >> NULL, NULL)) { >> continue; >> } >> >> + track_flow_based_tunnel(port_rec, chassis_rec, flow_tunnels); >> + >> const char *localnet = smap_get(&port_rec->external_ids, >> "ovn-localnet-port"); >> const char *l2gateway = smap_get(&port_rec->external_ids, >> @@ -757,3 +764,180 @@ lb_is_local(const struct sbrec_load_balancer >> *sbrec_lb, >> >> return false; >> } >> + >> +/* Flow-based tunnel management functions. */ >> + >> +void >> +flow_based_tunnels_init(struct flow_based_tunnel *flow_tunnels) >> +{ >> + for (size_t i = 0; i < TUNNEL_TYPE_MAX; i++) { >> + flow_tunnels[i] = (struct flow_based_tunnel) { >> + .ofport = 0, >> + .is_ipv6 = false, >> + .port_name = NULL, >> + }; >> + } >> +} >> + >> +void >> +flow_based_tunnels_destroy(struct flow_based_tunnel *flow_tunnels) >> +{ >> + for (size_t i = 0; i < TUNNEL_TYPE_MAX; i++) { >> + free(flow_tunnels[i].port_name); >> + flow_tunnels[i].port_name = NULL; >> + flow_tunnels[i].ofport = 0; >> + } >> +} >> + >> +ofp_port_t >> +get_flow_based_tunnel_port(enum chassis_tunnel_type type, >> + const struct flow_based_tunnel *flow_tunnels) >> +{ >> + if (type < 0 || type >= TUNNEL_TYPE_MAX) { >> + return 0; >> + } >> + return flow_tunnels[type].ofport; >> +} >> + >> +/* Direct tunnel endpoint selection utilities. */ >> + >> +enum chassis_tunnel_type >> +select_preferred_tunnel_type(const struct sbrec_chassis *local_chassis, >> + const struct sbrec_chassis *remote_chassis) >> +{ >> + /* Determine what tunnel types both chassis support */ >> + bool local_supports_geneve = false; >> + bool local_supports_vxlan = false; >> + bool remote_supports_geneve = false; >> + bool remote_supports_vxlan = false; >> + >> + for (size_t i = 0; i < local_chassis->n_encaps; i++) { >> + const char *type = local_chassis->encaps[i]->type; >> + if (!strcmp(type, "geneve")) { >> + local_supports_geneve = true; >> + } else if (!strcmp(type, "vxlan")) { >> + local_supports_vxlan = true; >> + } >> + } >> + >> + for (size_t i = 0; i < remote_chassis->n_encaps; i++) { >> + const char *type = remote_chassis->encaps[i]->type; >> + if (!strcmp(type, "geneve")) { >> + remote_supports_geneve = true; >> + } else if (!strcmp(type, "vxlan")) { >> + remote_supports_vxlan = true; >> + } >> + } >> + >> + /* Return preferred common tunnel type: geneve > vxlan */ >> + if (local_supports_geneve && remote_supports_geneve) { >> + return GENEVE; >> + } else if (local_supports_vxlan && remote_supports_vxlan) { >> + return VXLAN; >> + } else { >> + return TUNNEL_TYPE_INVALID; /* No common tunnel type */ >> + } >> +} >> + >> +const char * >> +select_default_encap_ip(const struct sbrec_chassis *chassis, >> + enum chassis_tunnel_type tunnel_type) >> +{ >> + const char *default_ip = NULL; >> + const char *tunnel_type_str = tunnel_type == GENEVE ? "geneve" : >> "vxlan"; >> + >> + for (size_t i = 0; i < chassis->n_encaps; i++) { >> + const struct sbrec_encap *encap = chassis->encaps[i]; >> + >> + if (strcmp(encap->type, tunnel_type_str)) { >> + continue; >> + } >> + >> + if (!default_ip) { >> + default_ip = encap->ip; >> + } >> + >> + if (smap_get_bool(&encap->options, "default-encap-ip", false)) { >> + default_ip = encap->ip; >> + break; /* Found explicit default */ >> + } >> + } >> + >> + return default_ip; >> +} >> + >> +const char * >> +select_port_encap_ip(const struct sbrec_port_binding *binding, >> + enum chassis_tunnel_type tunnel_type) >> +{ >> + const char *tunnel_type_str = tunnel_type == GENEVE ? "geneve" : >> "vxlan"; >> + >> + if (binding->encap && !strcmp(binding->encap->type, >> tunnel_type_str)) { >> + VLOG_DBG("Using port-specific encap IP %s for binding %s", >> + binding->encap->ip, binding->logical_port); >> + return binding->encap->ip; >> + } >> + >> + /* Fall back to chassis default encap IP */ >> + return select_default_encap_ip(binding->chassis, tunnel_type); >> +} >> + >> +static void >> +track_flow_based_tunnel(const struct ovsrec_port *port_rec, >> + const struct sbrec_chassis *chassis_rec, >> + struct flow_based_tunnel *flow_tunnels) >> +{ >> + if (port_rec->n_interfaces != 1) { >> + return; >> + } >> + >> + const struct ovsrec_interface *iface_rec = port_rec->interfaces[0]; >> + >> + /* Check if this is a flow-based tunnel port using unified >> + * OVN_TUNNEL_ID. */ >> + const char *tunnel_id = smap_get(&port_rec->external_ids, >> OVN_TUNNEL_ID); >> + const char *tunnel_type_str = smap_get(&port_rec->external_ids, >> + "ovn-tunnel-type"); >> + >> + if (!tunnel_id || !tunnel_type_str || strcmp(tunnel_id, "flow")) { >> + return; >> + } >> + >> + /* Get tunnel type. */ >> + enum chassis_tunnel_type tunnel_type = >> get_tunnel_type(tunnel_type_str); >> + if (tunnel_type == TUNNEL_TYPE_INVALID) { >> + return; >> + } >> + >> + /* Check if we already track this tunnel type. */ >> + if (flow_tunnels[tunnel_type].ofport != 0) { >> + return; >> + } >> + >> + int64_t ofport = iface_rec->ofport ? *iface_rec->ofport : 0; >> + if (ofport <= 0 || ofport > UINT16_MAX) { >> + VLOG_INFO("Invalid ofport %"PRId64" for flow-based tunnel %s", >> + ofport, port_rec->name); >> + return; >> + } >> + >> + /* Detect if this tunnel will use IPv6 by checking chassis encap >> IPs. */ >> + bool is_ipv6 = false; >> + for (size_t i = 0; i < chassis_rec->n_encaps; i++) { >> + const struct sbrec_encap *encap = chassis_rec->encaps[i]; >> + if (!strcmp(encap->type, tunnel_type_str) && >> + addr_is_ipv6(encap->ip)) { >> + is_ipv6 = true; >> + break; >> + } >> + } >> + >> + /* Store in array using tunnel_type as index. */ >> + flow_tunnels[tunnel_type].ofport = u16_to_ofp(ofport); >> + flow_tunnels[tunnel_type].is_ipv6 = is_ipv6; >> + flow_tunnels[tunnel_type].port_name = xstrdup(port_rec->name); >> + >> + VLOG_DBG("Tracking flow-based tunnel: port=%s, type=%s, >> ofport=%"PRId64 >> + ", is_ipv6=%s", port_rec->name, tunnel_type_str, ofport, >> + is_ipv6 ? "true" : "false"); >> +} >> diff --git a/controller/local_data.h b/controller/local_data.h >> index 841829f2e071..948c1a935e0c 100644 >> --- a/controller/local_data.h >> +++ b/controller/local_data.h >> @@ -28,6 +28,7 @@ >> struct sbrec_datapath_binding; >> struct sbrec_port_binding; >> struct sbrec_chassis; >> +struct sbrec_chassis_table; >> struct ovsdb_idl_index; >> struct ovsrec_bridge; >> struct ovsrec_interface_table; >> @@ -147,10 +148,21 @@ struct chassis_tunnel { >> bool is_ipv6; >> }; >> >> +/* Flow-based tunnel that consolidates multiple endpoints into a single >> + * port. Array is indexed by tunnel type (VXLAN=0, GENEVE=1). >> + * The type is implicit from the array index, so not stored. */ >> +struct flow_based_tunnel { >> + ofp_port_t ofport; /* Single port for all endpoints */ >> + bool is_ipv6; >> + char *port_name; /* e.g., "ovn-geneve" */ >> +}; >> + >> + >> void local_nonvif_data_run(const struct ovsrec_bridge *br_int, >> - const struct sbrec_chassis *, >> + const struct sbrec_chassis *chassis, >> struct simap *patch_ofports, >> - struct hmap *chassis_tunnels); >> + struct hmap *chassis_tunnels, >> + struct flow_based_tunnel *flow_tunnels); >> >> bool local_nonvif_data_handle_ovs_iface_changes( >> const struct ovsrec_interface_table *); >> @@ -165,6 +177,22 @@ bool get_chassis_tunnel_ofport(const struct hmap >> *chassis_tunnels, >> ofp_port_t *ofport); >> >> void chassis_tunnels_destroy(struct hmap *chassis_tunnels); >> + >> +/* Flow-based tunnel management functions. */ >> +void flow_based_tunnels_init(struct flow_based_tunnel *); >> +void flow_based_tunnels_destroy(struct flow_based_tunnel *); >> +ofp_port_t get_flow_based_tunnel_port( >> + enum chassis_tunnel_type, const struct flow_based_tunnel *); >> + >> +/* Direct tunnel endpoint selection utilities. */ >> +enum chassis_tunnel_type select_preferred_tunnel_type( >> + const struct sbrec_chassis *local_chassis, >> + const struct sbrec_chassis *remote_chassis); >> +const char *select_default_encap_ip(const struct sbrec_chassis *, >> + enum chassis_tunnel_type); >> +const char *select_port_encap_ip(const struct sbrec_port_binding *, >> + enum chassis_tunnel_type); >> + >> void local_datapath_memory_usage(struct simap *usage); >> void add_local_datapath_external_port(struct local_datapath *ld, >> char *logical_port, const void >> *data); >> diff --git a/controller/ovn-controller.8.xml >> b/controller/ovn-controller.8.xml >> index 32a1f7a6f658..dfc7cc2179ea 100644 >> --- a/controller/ovn-controller.8.xml >> +++ b/controller/ovn-controller.8.xml >> @@ -219,6 +219,36 @@ >> <code>false</code> to clear the DF flag. >> </dd> >> >> + <dt><code>external_ids:ovn-enable-flow-based-tunnels</code></dt> >> + <dd> >> + <p> >> + The boolean flag indicates if <code>ovn-controller</code> >> should >> + use flow-based tunnels instead of port-based tunnels for >> overlay >> + network connectivity. Default value is <code>false</code>. >> + </p> >> + <p> >> + <em>Port-based tunnels</em> (default mode) create a dedicated >> tunnel >> + port for each combination of remote chassis and local encap IP, >> + while <em>Flow-based tunnels</em> create a single shared >> tunnel port >> + for each tunnel type, and use OpenFlow <code>set_field</code> >> actions >> + to dynamically set tunnel source and destination IP addresses >> per >> + packet. >> + </p> >> + <p> >> + This feature is experimental and is disabled by default. There >> are >> + some known limitations to the feature: >> + <ul> >> + <li> >> + IPSec is not supported. >> + </li> >> + <li> >> + BFD between tunnel endpoints is not supported, thus HA >> chassis >> + (e.g. for Distributed Gateway Port redundancy) is not >> supported. >> + </li> >> + </ul> >> + </p> >> + </dd> >> + >> <dt><code>external_ids:ovn-bridge-mappings</code></dt> >> <dd> >> A list of key-value pairs that map a physical network name to a >> local >> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c >> index 14b9b9e28301..4b4ffb2bb851 100644 >> --- a/controller/ovn-controller.c >> +++ b/controller/ovn-controller.c >> @@ -3516,6 +3516,10 @@ struct ed_type_non_vif_data { >> struct simap patch_ofports; /* simap of patch ovs ports. */ >> struct hmap chassis_tunnels; /* hmap of 'struct chassis_tunnel' from >> the >> * tunnel OVS ports. */ >> + struct flow_based_tunnel flow_tunnels[TUNNEL_TYPE_MAX]; >> + /* Array of flow-based tunnels indexed >> by >> + * tunnel type. */ >> + bool use_flow_based_tunnels; /* Enable flow-based tunnels. */ >> }; >> >> static void * >> @@ -3525,6 +3529,8 @@ en_non_vif_data_init(struct engine_node *node >> OVS_UNUSED, >> struct ed_type_non_vif_data *data = xzalloc(sizeof *data); >> simap_init(&data->patch_ofports); >> hmap_init(&data->chassis_tunnels); >> + flow_based_tunnels_init(data->flow_tunnels); >> + data->use_flow_based_tunnels = false; >> return data; >> } >> >> @@ -3534,6 +3540,7 @@ en_non_vif_data_cleanup(void *data OVS_UNUSED) >> struct ed_type_non_vif_data *ed_non_vif_data = data; >> simap_destroy(&ed_non_vif_data->patch_ofports); >> chassis_tunnels_destroy(&ed_non_vif_data->chassis_tunnels); >> + flow_based_tunnels_destroy(ed_non_vif_data->flow_tunnels); >> } >> >> static enum engine_node_state >> @@ -3542,8 +3549,11 @@ en_non_vif_data_run(struct engine_node *node, void >> *data) >> struct ed_type_non_vif_data *ed_non_vif_data = data; >> simap_destroy(&ed_non_vif_data->patch_ofports); >> chassis_tunnels_destroy(&ed_non_vif_data->chassis_tunnels); >> + flow_based_tunnels_destroy(ed_non_vif_data->flow_tunnels); >> + >> simap_init(&ed_non_vif_data->patch_ofports); >> hmap_init(&ed_non_vif_data->chassis_tunnels); >> + flow_based_tunnels_init(ed_non_vif_data->flow_tunnels); >> >> const struct ovsrec_open_vswitch_table *ovs_table = >> EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node)); >> @@ -3563,8 +3573,14 @@ en_non_vif_data_run(struct engine_node *node, void >> *data) >> = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); >> ovs_assert(chassis); >> >> - local_nonvif_data_run(br_int, chassis, >> &ed_non_vif_data->patch_ofports, >> - &ed_non_vif_data->chassis_tunnels); >> + ed_non_vif_data->use_flow_based_tunnels = >> + is_flow_based_tunnels_enabled(ovs_table, chassis); >> + >> + local_nonvif_data_run(br_int, chassis, >> + &ed_non_vif_data->patch_ofports, >> + &ed_non_vif_data->chassis_tunnels, >> + ed_non_vif_data->flow_tunnels); >> + >> return EN_UPDATED; >> } >> >> @@ -4673,6 +4689,7 @@ static void init_physical_ctx(struct engine_node >> *node, >> parse_encap_ips(ovs_table, &p_ctx->n_encap_ips, &p_ctx->encap_ips); >> p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name; >> p_ctx->sbrec_port_binding_by_datapath = >> sbrec_port_binding_by_datapath; >> + p_ctx->sbrec_chassis_by_name = sbrec_chassis_by_name; >> p_ctx->port_binding_table = port_binding_table; >> p_ctx->ovs_interface_table = ovs_interface_table; >> p_ctx->mc_group_table = multicast_group_table; >> @@ -4686,6 +4703,8 @@ static void init_physical_ctx(struct engine_node >> *node, >> p_ctx->local_bindings = &rt_data->lbinding_data.bindings; >> p_ctx->patch_ofports = &non_vif_data->patch_ofports; >> p_ctx->chassis_tunnels = &non_vif_data->chassis_tunnels; >> + p_ctx->flow_tunnels = non_vif_data->flow_tunnels; >> + p_ctx->use_flow_based_tunnels = non_vif_data->use_flow_based_tunnels; >> p_ctx->always_tunnel = n_opts->always_tunnel; >> p_ctx->evpn_bindings = &eb_data->bindings; >> p_ctx->evpn_multicast_groups = &eb_data->multicast_groups; >> @@ -7586,7 +7605,8 @@ main(int argc, char *argv[]) >> >> ovsrec_interface_table_get(ovs_idl_loop.idl), >> br_int, &bfd_chassis_data->bfd_chassis, >> chassis, sbrec_sb_global_table_get( >> - ovnsb_idl_loop.idl) >> + ovnsb_idl_loop.idl), >> + ovs_table >> ); >> stopwatch_stop( >> BFD_RUN_STOPWATCH_NAME, time_msec()); >> diff --git a/controller/physical.c b/controller/physical.c >> index 6ac5dcd3fb28..f6de774307d5 100644 >> --- a/controller/physical.c >> +++ b/controller/physical.c >> @@ -51,6 +51,7 @@ >> #include "openvswitch/shash.h" >> #include "simap.h" >> #include "smap.h" >> +#include "socket-util.h" >> #include "sset.h" >> #include "util.h" >> #include "vswitch-idl.h" >> @@ -225,6 +226,179 @@ match_set_chassis_flood_outport(struct match *match, >> } >> } >> >> +/* Flow-based tunnel helper function to set tunnel source or destination >> IP. */ >> + >> +static void >> +put_set_tunnel_ip(const char *ip, bool is_src, struct ofpbuf *ofpacts) >> +{ >> + struct in6_addr ip_addr; >> + if (!ip46_parse(ip, &ip_addr)) { >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); >> + VLOG_WARN_RL(&rl, "Invalid tunnel IP address: %s", ip); >> + return; >> + } >> + >> + if (IN6_IS_ADDR_V4MAPPED(&ip_addr)) { >> + /* IPv4 */ >> + ovs_be32 ipv4 = in6_addr_get_mapped_ipv4(&ip_addr); >> + put_load_bytes(&ipv4, sizeof ipv4, >> + is_src ? MFF_TUN_SRC : MFF_TUN_DST, >> + 0, 32, ofpacts); >> + } else { >> + /* IPv6 */ >> + put_load_bytes(&ip_addr, sizeof ip_addr, >> + is_src ? MFF_TUN_IPV6_SRC : MFF_TUN_IPV6_DST, >> + 0, 128, ofpacts); >> + } >> +} >> + >> +/* Flow-based encapsulation that sets tunnel metadata and endpoint IPs. >> */ >> +static void >> +put_flow_based_encapsulation(enum mf_field_id mff_ovn_geneve, >> + enum chassis_tunnel_type tunnel_type, >> + const char *local_ip, const char *remote_ip, >> + const struct sbrec_datapath_binding >> *datapath, >> + uint16_t outport, bool is_ramp_switch, >> + struct ofpbuf *ofpacts) >> +{ >> + struct chassis_tunnel temp_tun = { >> + .type = tunnel_type, >> + }; >> + put_encapsulation(mff_ovn_geneve, &temp_tun, datapath, >> + outport, is_ramp_switch, ofpacts); >> + >> + /* Set tunnel source and destination IPs (flow-based specific) */ >> + put_set_tunnel_ip(local_ip, true, ofpacts); >> + put_set_tunnel_ip(remote_ip, false, ofpacts); >> +} >> + >> + >> +/* Generate flows for flow-based tunnel to a specific chassis. */ >> +static void >> +put_flow_based_remote_port_redirect_overlay( >> + const struct sbrec_port_binding *binding, >> + const enum en_lport_type type, >> + const struct physical_ctx *ctx, >> + uint32_t port_key, >> + struct match *match, >> + struct ofpbuf *ofpacts_p, >> + struct ovn_desired_flow_table *flow_table) >> +{ >> + if (!binding->chassis || binding->chassis == ctx->chassis) { >> + return; >> + } >> + >> + enum chassis_tunnel_type tunnel_type = >> + select_preferred_tunnel_type(ctx->chassis, binding->chassis); >> + if (tunnel_type == TUNNEL_TYPE_INVALID) { >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); >> + VLOG_WARN_RL(&rl, "No common tunnel type with chassis %s", >> + binding->chassis->name); >> + return; >> + } >> + >> + const char *tunnel_type_str = tunnel_type == GENEVE ? "geneve" : >> "vxlan"; >> + const char *remote_ip = select_port_encap_ip(binding, tunnel_type); >> + if (!remote_ip) { >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); >> + VLOG_WARN_RL(&rl, "No compatible encap IP for port %s on chassis >> %s " >> + "with type %s", binding->logical_port, >> + binding->chassis->name, tunnel_type_str); >> + return; >> + } >> + >> + ofp_port_t flow_port = get_flow_based_tunnel_port(tunnel_type, >> + ctx->flow_tunnels); >> + if (flow_port == 0) { >> + VLOG_DBG("No flow-based tunnel port found for type %s", >> + tunnel_type_str); >> + return; >> + } >> + >> + VLOG_DBG("Using flow-based tunnel: chassis=%s, tunnel_type=%s, " >> + "remote_ip=%s, flow_port=%d", binding->chassis->name, >> + tunnel_type_str, remote_ip, flow_port); >> + >> + /* Generate flows for each local encap IP. */ >> + for (size_t i = 0; i < ctx->n_encap_ips; i++) { >> + const char *local_encap_ip = ctx->encap_ips[i]; >> + >> + struct ofpbuf *ofpacts_clone = ofpbuf_clone(ofpacts_p); >> + >> + /* Set encap ID for this local IP. */ >> + match_set_reg_masked(match, MFF_LOG_ENCAP_ID - MFF_REG0, i << 16, >> + (uint32_t) 0xFFFF << 16); >> + >> + bool is_vtep_port = type == LP_VTEP; >> + if (is_vtep_port) { >> + put_load(ofp_to_u16(OFPP_NONE), MFF_IN_PORT, 0, 16, >> ofpacts_clone); >> + } >> + >> + /* Set flow-based tunnel encapsulation. */ >> + put_flow_based_encapsulation(ctx->mff_ovn_geneve, tunnel_type, >> + local_encap_ip, remote_ip, >> + binding->datapath, port_key, >> + is_vtep_port, ofpacts_clone); >> + >> + ofpact_put_OUTPUT(ofpacts_clone)->port = flow_port; >> + put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_clone); >> + >> + ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, >> + binding->header_.uuid.parts[0], match, >> + ofpacts_clone, &binding->header_.uuid); >> + >> + ofpbuf_delete(ofpacts_clone); >> + } >> +} >> + >> +static void >> +add_tunnel_ingress_flows(const struct chassis_tunnel *tun, >> + enum mf_field_id mff_ovn_geneve, >> + struct ovn_desired_flow_table *flow_table, >> + struct ofpbuf *ofpacts) >> +{ >> + /* Main ingress flow (priority 100) */ >> + struct match match = MATCH_CATCHALL_INITIALIZER; >> + match_set_in_port(&match, tun->ofport); >> + >> + ofpbuf_clear(ofpacts); >> + put_decapsulation(mff_ovn_geneve, tun, ofpacts); >> + put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts); >> + >> + ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match, >> + ofpacts, hc_uuid); >> + >> + /* Set allow rx from tunnel bit */ >> + ofpbuf_clear(ofpacts); >> + put_load(1, MFF_LOG_FLAGS, MLF_RX_FROM_TUNNEL_BIT, 1, ofpacts); >> + put_resubmit(OFTABLE_CT_ZONE_LOOKUP, ofpacts); >> + >> + /* Add specific flows for E/W ICMPv{4,6} packets if tunnelled packets >> + * do not fit path MTU. */ >> + >> + /* IPv4 ICMP flow (priority 120) */ >> + match_init_catchall(&match); >> + match_set_in_port(&match, tun->ofport); >> + match_set_dl_type(&match, htons(ETH_TYPE_IP)); >> + match_set_nw_proto(&match, IPPROTO_ICMP); >> + match_set_icmp_type(&match, 3); >> + match_set_icmp_code(&match, 4); >> + >> + ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 120, 0, &match, >> + ofpacts, hc_uuid); >> + >> + /* IPv6 ICMP flow (priority 120) */ >> + match_init_catchall(&match); >> + match_set_in_port(&match, tun->ofport); >> + match_set_dl_type(&match, htons(ETH_TYPE_IPV6)); >> + match_set_nw_proto(&match, IPPROTO_ICMPV6); >> + match_set_icmp_type(&match, 2); >> + match_set_icmp_code(&match, 0); >> + >> + ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 120, 0, &match, >> + ofpacts, hc_uuid); >> +} >> + >> static void >> put_stack(enum mf_field_id field, struct ofpact_stack *stack) >> { >> @@ -425,17 +599,18 @@ get_remote_tunnels(const struct sbrec_port_binding >> *binding, >> return tunnels; >> } >> >> +/* Generate flows for port-based tunnel to a specific chassis. */ >> static void >> -put_remote_port_redirect_overlay(const struct sbrec_port_binding >> *binding, >> - const enum en_lport_type type, >> - const struct physical_ctx *ctx, >> - uint32_t port_key, >> - struct match *match, >> - struct ofpbuf *ofpacts_p, >> - struct ovn_desired_flow_table >> *flow_table, >> - bool allow_hairpin) >> +put_port_based_remote_port_redirect_overlay( >> + const struct sbrec_port_binding *binding, >> + const enum en_lport_type type, >> + const struct physical_ctx *ctx, >> + uint32_t port_key, >> + struct match *match, >> + struct ofpbuf *ofpacts_p, >> + struct ovn_desired_flow_table *flow_table, >> + bool allow_hairpin) >> { >> - /* Setup encapsulation */ >> for (size_t i = 0; i < ctx->n_encap_ips; i++) { >> const char *encap_ip = ctx->encap_ips[i]; >> struct ofpbuf *ofpacts_clone = ofpbuf_clone(ofpacts_p); >> @@ -481,6 +656,28 @@ put_remote_port_redirect_overlay(const struct >> sbrec_port_binding *binding, >> } >> } >> >> +static void >> +put_remote_port_redirect_overlay(const struct sbrec_port_binding >> *binding, >> + const enum en_lport_type type, >> + const struct physical_ctx *ctx, >> + uint32_t port_key, >> + struct match *match, >> + struct ofpbuf *ofpacts_p, >> + struct ovn_desired_flow_table >> *flow_table, >> + bool allow_hairpin) >> +{ >> + if (ctx->use_flow_based_tunnels) { >> + put_flow_based_remote_port_redirect_overlay(binding, type, ctx, >> + port_key, match, >> + ofpacts_p, >> flow_table); >> + } else { >> + put_port_based_remote_port_redirect_overlay(binding, type, ctx, >> + port_key, match, >> + ofpacts_p, >> flow_table, >> + allow_hairpin); >> + } >> +} >> + >> static const struct sbrec_port_binding * >> get_binding_network_function_linked_port( >> struct ovsdb_idl_index *sbrec_port_binding_by_name, >> @@ -1903,6 +2100,7 @@ handle_pkt_too_big(struct ovn_desired_flow_table >> *flow_table, >> handle_pkt_too_big_for_ip_version(flow_table, binding, mcp, mtu, >> true); >> } >> >> +/* XXX: Need to support flow-based tunnel for this function. */ >> static void >> enforce_tunneling_for_multichassis_ports( >> struct local_datapath *ld, >> @@ -2516,14 +2714,101 @@ tunnel_to_chassis(enum mf_field_id >> mff_ovn_geneve, >> ofpact_put_OUTPUT(remote_ofpacts)->port = tun->ofport; >> } >> >> -/* Encapsulate and send to a set of remote chassis. */ >> +/* Flow-based tunnel version of fanout_to_chassis for >> multicast/broadcast. */ >> static void >> -fanout_to_chassis(enum mf_field_id mff_ovn_geneve, >> - struct sset *remote_chassis, >> - const struct hmap *chassis_tunnels, >> - const struct sbrec_datapath_binding *datapath, >> - uint16_t outport, bool is_ramp_switch, >> - struct ofpbuf *remote_ofpacts) >> +fanout_to_chassis_flow_based(const struct physical_ctx *ctx, >> + struct sset *remote_chassis, >> + const struct sbrec_datapath_binding >> *datapath, >> + uint16_t outport, bool is_ramp_switch, >> + struct ofpbuf *remote_ofpacts) >> +{ >> + VLOG_DBG("fanout_to_chassis_flow_based called with %"PRIuSIZE >> + " remote chassis", sset_count(remote_chassis)); >> + >> + if (!ctx->flow_tunnels) { >> + VLOG_DBG("fanout_to_chassis_flow_based: Missing flow_tunnels"); >> + return; >> + } >> + >> + if (!remote_chassis || sset_is_empty(remote_chassis)) { >> + VLOG_DBG("fanout_to_chassis_flow_based: No remote chassis " >> + "to send to"); >> + return; >> + } >> + >> + const char *local_encap_ip = NULL; >> + if (ctx->n_encap_ips <= 0) { >> + return; >> + } >> + local_encap_ip = ctx->encap_ips[0]; /* Use first/default local IP */ >> + >> + const char *chassis_name; >> + enum chassis_tunnel_type prev_type = TUNNEL_TYPE_INVALID; >> + >> + SSET_FOR_EACH (chassis_name, remote_chassis) { >> + const struct sbrec_chassis *remote_chassis_rec = >> + chassis_lookup_by_name(ctx->sbrec_chassis_by_name, chassis_name); >> + if (!remote_chassis_rec) { >> + VLOG_DBG("Chassis %s not found in SB", chassis_name); >> + continue; >> + } >> + >> + enum chassis_tunnel_type tunnel_type = >> + select_preferred_tunnel_type(ctx->chassis, >> remote_chassis_rec); >> + if (tunnel_type == TUNNEL_TYPE_INVALID) { >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, >> 1); >> + VLOG_WARN_RL(&rl, "No common tunnel type with chassis %s", >> + chassis_name); >> + continue; >> + } >> + const char *tunnel_type_str = tunnel_type == GENEVE ? "geneve" >> + : "vxlan"; >> + >> + ofp_port_t flow_port = get_flow_based_tunnel_port(tunnel_type, >> + >> ctx->flow_tunnels); >> + if (flow_port == 0) { >> + VLOG_DBG("No flow-based tunnel port found for type %s", >> + tunnel_type_str); >> + continue; >> + } >> + >> + const char *remote_ip = >> select_default_encap_ip(remote_chassis_rec, >> + tunnel_type); >> + if (!remote_ip) { >> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, >> 1); >> + VLOG_WARN_RL(&rl, "No compatible encap IP for chassis %s >> with type %s", >> + chassis_name, tunnel_type_str); >> + continue; >> + } >> + >> + /* Add encapsulation if tunnel type changed or this is the first >> + * chassis. */ >> + if (tunnel_type != prev_type) { >> + struct chassis_tunnel temp_tun = { >> + .chassis_id = CONST_CAST(char *, chassis_name), >> + .ofport = flow_port, >> + .type = tunnel_type >> + }; >> + put_encapsulation(ctx->mff_ovn_geneve, &temp_tun, datapath, >> + outport, is_ramp_switch, remote_ofpacts); >> + prev_type = tunnel_type; >> + } >> + >> + /* Set tunnel source and destination IPs for flow-based tunnels. >> */ >> + put_set_tunnel_ip(local_encap_ip, true, remote_ofpacts); >> + put_set_tunnel_ip(remote_ip, false, remote_ofpacts); >> + ofpact_put_OUTPUT(remote_ofpacts)->port = flow_port; >> + } >> +} >> + >> +/* Encapsulate and send to a set of remote chassis (port-based tunnels). >> */ >> +static void >> +fanout_to_chassis_port_based(enum mf_field_id mff_ovn_geneve, >> + struct sset *remote_chassis, >> + const struct hmap *chassis_tunnels, >> + const struct sbrec_datapath_binding >> *datapath, >> + uint16_t outport, bool is_ramp_switch, >> + struct ofpbuf *remote_ofpacts) >> { >> const char *chassis_name; >> const struct chassis_tunnel *prev = NULL; >> @@ -2757,12 +3042,29 @@ consider_mc_group(const struct physical_ctx *ctx, >> if (remote_ports) { >> put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, >> &remote_ctx->ofpacts); >> } >> - fanout_to_chassis(ctx->mff_ovn_geneve, &remote_chassis, >> - ctx->chassis_tunnels, mc->datapath, mc->tunnel_key, >> - false, &remote_ctx->ofpacts); >> - fanout_to_chassis(ctx->mff_ovn_geneve, &vtep_chassis, >> - ctx->chassis_tunnels, mc->datapath, mc->tunnel_key, >> - true, &remote_ctx->ofpacts); >> + if (ctx->use_flow_based_tunnels) { >> + VLOG_DBG("Using flow-based tunnels for multicast group %s " >> + "(tunnel_key=%"PRId64") with %"PRIuSIZE" remote >> chassis", >> + mc->name, mc->tunnel_key, sset_count(&remote_chassis)); >> + fanout_to_chassis_flow_based(ctx, &remote_chassis, >> + mc->datapath, mc->tunnel_key, >> + false, &remote_ctx->ofpacts); >> + fanout_to_chassis_flow_based(ctx, &vtep_chassis, >> + mc->datapath, mc->tunnel_key, >> + true, &remote_ctx->ofpacts); >> + } else { >> + VLOG_DBG("Using port-based tunnels for multicast group %s " >> + "(tunnel_key=%"PRId64") with %"PRIuSIZE" remote >> chassis", >> + mc->name, mc->tunnel_key, sset_count(&remote_chassis)); >> + fanout_to_chassis_port_based(ctx->mff_ovn_geneve, >> &remote_chassis, >> + ctx->chassis_tunnels, mc->datapath, >> + mc->tunnel_key, false, >> + &remote_ctx->ofpacts); >> + fanout_to_chassis_port_based(ctx->mff_ovn_geneve, &vtep_chassis, >> + ctx->chassis_tunnels, mc->datapath, >> + mc->tunnel_key, true, >> + &remote_ctx->ofpacts); >> + } >> >> remote_ports = remote_ctx->ofpacts.size > 0; >> if (remote_ports) { >> @@ -3408,44 +3710,33 @@ physical_run(struct physical_ctx *p_ctx, >> * packets to the local hypervisor. */ >> struct chassis_tunnel *tun; >> HMAP_FOR_EACH (tun, hmap_node, p_ctx->chassis_tunnels) { >> - struct match match = MATCH_CATCHALL_INITIALIZER; >> - match_set_in_port(&match, tun->ofport); >> - >> - ofpbuf_clear(&ofpacts); >> - put_decapsulation(p_ctx->mff_ovn_geneve, tun, &ofpacts); >> + add_tunnel_ingress_flows(tun, p_ctx->mff_ovn_geneve, flow_table, >> + &ofpacts); >> + } >> >> - put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts); >> - ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match, >> - &ofpacts, hc_uuid); >> + /* Process packets that arrive from flow-based tunnels. */ >> + if (p_ctx->use_flow_based_tunnels && p_ctx->flow_tunnels) { >> + for (size_t i = 0; i < TUNNEL_TYPE_MAX; i++) { >> + if (p_ctx->flow_tunnels[i].ofport == 0) { >> + continue; /* Tunnel not configured */ >> + } >> >> - /* Set allow rx from tunnel bit. */ >> - put_load(1, MFF_LOG_FLAGS, MLF_RX_FROM_TUNNEL_BIT, 1, &ofpacts); >> + /* Flow-based tunnels use the same ingress flow logic as >> + * port-based. Create a temporary chassis_tunnel structure >> + * for compatibility. */ >> + struct chassis_tunnel temp_tunnel = { >> + .type = i, /* Array index is the tunnel type */ >> + .ofport = p_ctx->flow_tunnels[i].ofport, >> + .chassis_id = NULL /* Not needed for decapsulation */ >> + }; >> >> - /* Add specif flows for E/W ICMPv{4,6} packets if tunnelled >> packets >> - * do not fit path MTU. >> - */ >> - put_resubmit(OFTABLE_CT_ZONE_LOOKUP, &ofpacts); >> + VLOG_DBG("Adding flow-based tunnel ingress flow: in_port=%d, >> " >> + "type=%s", p_ctx->flow_tunnels[i].ofport, >> + i == GENEVE ? "geneve" : "vxlan"); >> >> - /* IPv4 */ >> - match_init_catchall(&match); >> - match_set_in_port(&match, tun->ofport); >> - match_set_dl_type(&match, htons(ETH_TYPE_IP)); >> - match_set_nw_proto(&match, IPPROTO_ICMP); >> - match_set_icmp_type(&match, 3); >> - match_set_icmp_code(&match, 4); >> - >> - ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 120, 0, &match, >> - &ofpacts, hc_uuid); >> - /* IPv6 */ >> - match_init_catchall(&match); >> - match_set_in_port(&match, tun->ofport); >> - match_set_dl_type(&match, htons(ETH_TYPE_IPV6)); >> - match_set_nw_proto(&match, IPPROTO_ICMPV6); >> - match_set_icmp_type(&match, 2); >> - match_set_icmp_code(&match, 0); >> - >> - ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 120, 0, &match, >> - &ofpacts, hc_uuid); >> + add_tunnel_ingress_flows(&temp_tunnel, p_ctx->mff_ovn_geneve, >> + flow_table, &ofpacts); >> + } >> } >> >> /* Add VXLAN specific rules to transform port keys >> diff --git a/controller/physical.h b/controller/physical.h >> index 0dc544823ad5..c7a33bd028a0 100644 >> --- a/controller/physical.h >> +++ b/controller/physical.h >> @@ -51,6 +51,7 @@ struct physical_debug { >> struct physical_ctx { >> struct ovsdb_idl_index *sbrec_port_binding_by_name; >> struct ovsdb_idl_index *sbrec_port_binding_by_datapath; >> + struct ovsdb_idl_index *sbrec_chassis_by_name; >> const struct sbrec_port_binding_table *port_binding_table; >> const struct ovsrec_interface_table *ovs_interface_table; >> const struct sbrec_multicast_group_table *mc_group_table; >> @@ -65,6 +66,8 @@ struct physical_ctx { >> struct shash *local_bindings; >> struct simap *patch_ofports; >> struct hmap *chassis_tunnels; >> + bool use_flow_based_tunnels; >> + struct flow_based_tunnel *flow_tunnels; >> size_t n_encap_ips; >> const char **encap_ips; >> struct physical_debug debug; >> diff --git a/lib/ovn-util.h b/lib/ovn-util.h >> index 611f80f1e6f6..3055a707ae3f 100644 >> --- a/lib/ovn-util.h >> +++ b/lib/ovn-util.h >> @@ -350,7 +350,8 @@ hash_add_in6_addr(uint32_t hash, const struct >> in6_addr *addr) >> enum chassis_tunnel_type { >> TUNNEL_TYPE_INVALID = -1, >> VXLAN = 0, >> - GENEVE = 1 >> + GENEVE = 1, >> + TUNNEL_TYPE_MAX = 2 /* Number of valid tunnel types */ >> }; >> >> enum chassis_tunnel_type get_tunnel_type(const char *name); >> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at >> index cc16fe0298e6..4055697f4ae4 100644 >> --- a/tests/ovn-macros.at >> +++ b/tests/ovn-macros.at >> @@ -109,7 +109,12 @@ m4_divert_text([PREPARE_TESTS], >> echo "$3: waiting for flows for remote output on $hv1" >> # Wait for a flow outputing to remote output >> OVS_WAIT_UNTIL([ >> - ofport=$(as $hv1 ovs-vsctl --bare --columns ofport find >> Interface name=ovn-${hv2}-0) >> + if test X$OVN_ENABLE_FLOW_BASED_TUNNELS = Xyes; then >> + tun_name=ovn-geneve >> + else >> + tun_name=ovn-${hv2}-0 >> + fi >> + ofport=$(as $hv1 ovs-vsctl --bare --columns ofport find >> Interface name=$tun_name) >> echo "tunnel port=$ofport" >> test -n "$ofport" && test 1 -le $(as $hv1 ovs-ofctl dump-flows >> br-int | grep -c "output:$ofport") >> ]) >> @@ -121,7 +126,12 @@ m4_divert_text([PREPARE_TESTS], >> echo "$3: waiting for flows for remote input on $hv1" >> # Wait for a flow outputing to remote input >> OVS_WAIT_UNTIL([ >> - ofport=$(as $hv1 ovs-vsctl --bare --columns ofport find >> Interface name=ovn-${hv2}-0) >> + if test X$OVN_ENABLE_FLOW_BASED_TUNNELS = Xyes; then >> + tun_name=ovn-geneve >> + else >> + tun_name=ovn-${hv2}-0 >> + fi >> + ofport=$(as $hv1 ovs-vsctl --bare --columns ofport find >> Interface name=$tun_name) >> echo "tunnel port=$ofport" >> test -n "$ofport" && test 1 -le $(as $hv1 ovs-ofctl dump-flows >> br-int | grep -c "in_port=$ofport") >> ]) >> @@ -762,6 +772,13 @@ ovn_az_attach() { >> ovs-vsctl set open . external_ids:ovn-monitor-all=true >> fi >> >> + # Configure flow-based tunnels if the test variable is set >> + if test X$OVN_ENABLE_FLOW_BASED_TUNNELS = Xyes; then >> + ovs-vsctl set open . >> external_ids:ovn-enable-flow-based-tunnels=true >> + elif test X$OVN_ENABLE_FLOW_BASED_TUNNELS = Xno; then >> + ovs-vsctl set open . >> external_ids:ovn-enable-flow-based-tunnels=false >> + fi >> + >> start_daemon ovn-controller --enable-dummy-vif-plug ${cli_args} || >> return 1 >> if test X"$az" = XNONE; then >> ovn_wait_for_encaps $expected_encap_id >> @@ -1441,6 +1458,16 @@ >> m4_define([OVN_FOR_EACH_NORTHD_NO_HV_PARALLELIZATION], >> [m4_foreach([NORTHD_USE_PARALLELIZATION], [yes, no], [$1 >> ])]) >> >> +# Defines versions of the test with all combinations of northd, >> +# parallelization enabled, conditional monitoring on/off, and flow-based >> +# tunnels on/off. Use this for tests that need to verify behavior with >> both >> +# port-based and flow-based tunnel implementations. >> +m4_define([OVN_FOR_EACH_NORTHD_FLOW_TUNNEL], >> + [m4_foreach([NORTHD_USE_PARALLELIZATION], [yes], >> + [m4_foreach([OVN_MONITOR_ALL], [yes, no], >> + [m4_foreach([OVN_ENABLE_FLOW_BASED_TUNNELS], [yes, no], [$1 >> +])])])]) >> + >> # OVN_NBCTL(NBCTL_COMMAND) adds NBCTL_COMMAND to list of commands to be >> run by RUN_OVN_NBCTL(). >> m4_define([OVN_NBCTL], [ >> command="${command} -- $1" >> diff --git a/tests/ovn.at b/tests/ovn.at >> index 0a3671368313..46aa4a16dfaa 100644 >> --- a/tests/ovn.at >> +++ b/tests/ovn.at >> @@ -2508,7 +2508,7 @@ AT_CLEANUP >> ]) >> >> # 3 hypervisors, one logical switch, 3 logical ports per hypervisor >> -OVN_FOR_EACH_NORTHD([ >> +OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([ >> AT_SETUP([3 HVs, 1 LS, 3 lports/HV]) >> AT_KEYWORDS([ovnarp]) >> AT_KEYWORDS([slowtest]) >> @@ -2785,7 +2785,7 @@ AT_CLEANUP >> >> # 2 hypervisors, one logical switch, 2 logical ports per hypervisor >> # logical ports bound to chassis encap-ip. >> -OVN_FOR_EACH_NORTHD([ >> +OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([ >> AT_SETUP([2 HVs, 1 LS, 2 lports/HV]) >> AT_KEYWORDS([ovnarp]) >> ovn_start >> @@ -3188,7 +3188,7 @@ AT_CLEANUP >> # 2 hypervisors, 4 logical ports per HV >> # 2 locally attached networks (one flat, one vlan tagged over same >> device) >> # 2 ports per HV on each network >> -OVN_FOR_EACH_NORTHD([ >> +OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([ >> AT_SETUP([2 HVs, 4 lports/HV, localnet ports]) >> ovn_start >> >> @@ -3522,7 +3522,7 @@ OVN_CLEANUP([hv-1 >> AT_CLEANUP >> ]) >> >> -OVN_FOR_EACH_NORTHD([ >> +OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([ >> AT_SETUP([2 HVs, 2 LS, broadcast traffic with multiple localnet ports >> per switch]) >> ovn_start >> >> @@ -4756,7 +4756,7 @@ AT_CLEANUP >> ]) >> >> # Similar test to "hardware GW" >> -OVN_FOR_EACH_NORTHD([ >> +OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([ >> AT_SETUP([3 HVs, 1 VIFs/HV, 1 software GW, 1 LS]) >> ovn_start >> >> @@ -4915,7 +4915,7 @@ AT_CLEANUP >> ]) >> >> # 3 hypervisors, 3 logical switches with 3 logical ports each, 1 logical >> router >> -OVN_FOR_EACH_NORTHD([ >> +OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([ >> AT_SETUP([3 HVs, 3 LS, 3 lports/LS, 1 LR]) >> AT_KEYWORDS([slowtest]) >> AT_SKIP_IF([test $HAVE_SCAPY = no]) >> @@ -5352,7 +5352,7 @@ OVN_CLEANUP([hv1], [hv2], [hv3]) >> AT_CLEANUP >> ]) >> >> -OVN_FOR_EACH_NORTHD([ >> +OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([ >> AT_SETUP([IP relocation using GARP request]) >> AT_SKIP_IF([test $HAVE_SCAPY = no]) >> ovn_start >> @@ -31371,6 +31371,7 @@ for i in 1 2; do >> ovs-vsctl add-br br-phys-$j >> ovn_attach n1 br-phys-$j 192.168.0.${i}1 >> ovs-vsctl set open . >> external_ids:ovn-encap-ip=192.168.0.${i}1,192.168.0.${i}2 >> + ovs-vsctl set open . external_ids:ovn-enable-flow-based-tunnels=false >> >> for j in 1 2; do >> ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j \ >> @@ -31424,6 +31425,7 @@ vif_to_hv() { >> # tunnel that matches the VIFs' encap_ip configurations. >> check_packet_tunnel() { >> local src=$1 dst=$2 >> + local flow_based_tunnel=$3 >> local src_mac=f0:00:00:00:00:$src >> local dst_mac=f0:00:00:00:00:$dst >> local src_ip=10.0.0.$src >> @@ -31436,8 +31438,17 @@ check_packet_tunnel() { >> hv=`vif_to_hv vif$src` >> as $hv >> echo "vif$src -> vif$dst should go through tunnel $local_encap_ip -> >> $remote_encap_ip" >> - tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface >> options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip) >> + if test x$flow_based_tunnel == xtrue; then >> + tunnel_ofport=$(ovs-vsctl --bare --column=ofport list interface >> ovn-geneve) >> + else >> + tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface >> options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip) >> + fi >> AT_CHECK([test $(ovs-appctl ofproto/trace br-int in_port=vif$src >> $packet | grep "output:" | awk -F ':' '{ print $2 }') == $tunnel_ofport]) >> + if test x$flow_based_tunnel == xtrue; then >> + trace_output=$(ovs-appctl ofproto/trace br-int in_port=vif$src >> $packet) >> + AT_CHECK([echo "$trace_output" | grep -q >> "set_field:$local_encap_ip->tun_src"]) >> + AT_CHECK([echo "$trace_output" | grep -q >> "set_field:$remote_encap_ip->tun_dst"]) >> + fi >> } >> >> for i in 1 2; do >> @@ -31446,6 +31457,20 @@ for i in 1 2; do >> done >> done >> >> +# Test flow-based tunnels >> +for i in 1 2; do >> + as hv$i >> + ovs-vsctl set open . external_ids:ovn-enable-flow-based-tunnels=true >> +done >> +check ovn-nbctl --wait=hv sync >> + >> +for i in 1 2; do >> + for j in 1 2; do >> + check_packet_tunnel 1$i 2$j true >> + done >> +done >> + >> + >> OVN_CLEANUP([hv1],[hv2]) >> AT_CLEANUP >> ]) >> @@ -31467,6 +31492,7 @@ for i in 1 2; do >> ovs-vsctl add-br br-phys-$j >> ovn_attach n1 br-phys-$j 192.168.0.${i}1 >> ovs-vsctl set open . >> external_ids:ovn-encap-ip=192.168.0.${i}1,192.168.0.${i}2 >> + ovs-vsctl set open . external_ids:ovn-enable-flow-based-tunnels=false >> >> check ovn-nbctl ls-add ls$i -- \ >> lrp-add lr lrp-ls$i f0:00:00:00:88:0$i 10.0.$i.88/24 -- \ >> @@ -31503,38 +31529,37 @@ vif_to_ip() { >> # tunnel that matches the VIFs' encap_ip configurations. >> check_packet_tunnel() { >> local src=$1 dst=$2 >> + local local_encap_ip=$3 >> + local remote_encap_ip=$4 >> + local flow_based_tunnel=$5 >> local src_mac=f0:00:00:00:00:$src >> local dst_mac=f0:00:00:00:88:01 # lrp-ls1's MAC >> local src_ip=$(vif_to_ip vif$src) >> local dst_ip=$(vif_to_ip vif$dst) >> >> - local local_encap_ip >> - if test -n "$3"; then >> - local_encap_ip=$3 >> - else >> - local_encap_ip=192.168.0.$src >> - fi >> - >> - local remote_encap_ip >> - if test -n "$4"; then >> - remote_encap_ip=$4 >> - else >> - remote_encap_ip=192.168.0.$dst >> - fi >> - >> local packet=$(fmt_pkt "Ether(dst='${dst_mac}', src='${src_mac}')/ \ >> IP(dst='${dst_ip}', src='${src_ip}')/ \ >> ICMP(type=8)") >> hv=`vif_to_hv vif$src` >> as $hv >> echo "vif$src -> vif$dst should go through tunnel $local_encap_ip -> >> $remote_encap_ip" >> - tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface >> options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip) >> + >> + if test x$flow_based_tunnel == xtrue; then >> + tunnel_ofport=$(ovs-vsctl --bare --column=ofport list interface >> ovn-geneve) >> + else >> + tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface >> options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip) >> + fi >> AT_CHECK([test $(ovs-appctl ofproto/trace br-int in_port=vif$src >> $packet | grep "output:" | awk -F ':' '{ print $2 }') == $tunnel_ofport]) >> + if test x$flow_based_tunnel == xtrue; then >> + trace_output=$(ovs-appctl ofproto/trace br-int in_port=vif$src >> $packet) >> + AT_CHECK([echo "$trace_output" | grep -q >> "set_field:$local_encap_ip->tun_src"]) >> + AT_CHECK([echo "$trace_output" | grep -q >> "set_field:$remote_encap_ip->tun_dst"]) >> + fi >> } >> >> for i in 1 2; do >> for j in 1 2; do >> - check_packet_tunnel 1$i 2$j >> + check_packet_tunnel 1$i 2$j 192.168.0.1$i 192.168.0.2$j >> done >> done >> >> @@ -31558,6 +31583,36 @@ for i in 1 2; do >> done >> done >> >> +# Test flow-based tunnels >> +for i in 1 2; do >> + as hv$i >> + check ovs-vsctl set open . >> external_ids:ovn-enable-flow-based-tunnels=true >> + for j in 1 2; do >> + check ovs-vsctl set Interface vif$i$j >> external_ids:encap-ip=192.168.0.$i$j >> + done >> +done >> +check ovn-nbctl --wait=hv sync >> + >> +for i in 1 2; do >> + for j in 1 2; do >> + check_packet_tunnel 1$i 2$j 192.168.0.1$i 192.168.0.2$j true >> + done >> +done >> + >> +for i in 1 2; do >> + as hv$i >> + for j in 1 2; do >> + check ovs-vsctl remove Interface vif$i$j external_ids encap-ip >> + done >> +done >> +check ovn-nbctl --wait=hv sync >> + >> +for i in 1 2; do >> + for j in 1 2; do >> + check_packet_tunnel 1$i 2$j 192.168.0.12 192.168.0.22 true >> + done >> +done >> + >> OVN_CLEANUP([hv1],[hv2]) >> AT_CLEANUP >> ]) >> @@ -31585,6 +31640,7 @@ for i in 1 2; do >> ovs-vsctl add-br br-phys-$j >> ovn_attach n1 br-phys-$j 192.168.0.${i}1 >> ovs-vsctl set open . >> external_ids:ovn-encap-ip=192.168.0.${i}1,192.168.0.${i}2 >> + ovs-vsctl set open . external_ids:ovn-enable-flow-based-tunnels=false >> >> ovs-vsctl add-br br-ext >> ovs-vsctl add-port br-ext vif$i -- set Interface vif$i \ >> @@ -31630,14 +31686,26 @@ check_packet_tunnel() { >> local local_encap_ip=$3 >> local remote_encap_ip=$4 >> >> + local flow_based_tunnel=$5 >> + >> local packet=$(fmt_pkt "Ether(dst='${dst_mac}', src='${src_mac}')/ \ >> IP(dst='${dst_ip}', src='${src_ip}')/ \ >> ICMP(type=8)") >> hv=`vif_to_hv vif$src` >> as $hv >> echo "vif$src -> vif$dst should go through tunnel $local_encap_ip -> >> $remote_encap_ip" >> - tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface >> options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip) >> + if test x$flow_based_tunnel == xtrue; then >> + tunnel_ofport=$(ovs-vsctl --bare --column=ofport list interface >> ovn-geneve) >> + else >> + tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface >> options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip) >> + fi >> + ovs-appctl ofproto/trace br-ext in_port=vif$src $packet >> AT_CHECK([test $(ovs-appctl ofproto/trace br-ext in_port=vif$src >> $packet | grep "output:" | awk -F ':' '{ print $2 }') == $tunnel_ofport]) >> + if test x$flow_based_tunnel == xtrue; then >> + trace_output=$(ovs-appctl ofproto/trace br-ext in_port=vif$src >> $packet) >> + AT_CHECK([echo "$trace_output" | grep -q >> "set_field:$local_encap_ip->tun_src"]) >> + AT_CHECK([echo "$trace_output" | grep -q >> "set_field:$remote_encap_ip->tun_dst"]) >> + fi >> } >> >> # Create mac-binding for the destination so that there is no need to >> trigger >> @@ -31659,6 +31727,23 @@ for e in 1 2; do >> check_packet_tunnel 1 2 192.168.0.1${e} 192.168.0.2${e} >> done >> >> +# Test flow-based tunnels >> +for i in 1 2; do >> + as hv$i >> + check ovs-vsctl set open . >> external_ids:ovn-enable-flow-based-tunnels=true >> +done >> +check ovn-nbctl --wait=hv sync >> + >> +for e in 1 2; do >> + for i in 1 2; do >> + as hv$i >> + check ovs-vsctl set open . >> external_ids:ovn-encap-ip-default=192.168.0.${i}${e} >> + done >> + check ovn-nbctl --wait=hv sync >> + >> + check_packet_tunnel 1 2 192.168.0.1${e} 192.168.0.2${e} true >> +done >> + >> OVN_CLEANUP([hv1],[hv2]) >> AT_CLEANUP >> ]) >> diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at >> index 6a3dc51dc392..6e6e5208028e 100644 >> --- a/tests/ovs-macros.at >> +++ b/tests/ovs-macros.at >> @@ -9,13 +9,15 @@ dnl - If NORTHD_USE_DP_GROUPS is defined, then append >> it to the test name and >> dnl set it as a shell variable as well. >> m4_rename([AT_SETUP], [OVS_AT_SETUP]) >> m4_define([AT_SETUP], >> - [OVS_AT_SETUP($@[]m4_ifdef([NORTHD_USE_PARALLELIZATION], [ -- >> parallelization=NORTHD_USE_PARALLELIZATION])[]m4_ifdef([OVN_MONITOR_ALL], [ >> -- ovn_monitor_all=OVN_MONITOR_ALL])) >> + [OVS_AT_SETUP($@[]m4_ifdef([NORTHD_USE_PARALLELIZATION], [ -- >> parallelization=NORTHD_USE_PARALLELIZATION])[]m4_ifdef([OVN_MONITOR_ALL], [ >> -- >> ovn_monitor_all=OVN_MONITOR_ALL])[]m4_ifdef([OVN_ENABLE_FLOW_BASED_TUNNELS], >> [ -- flow_based_tunnels=OVN_ENABLE_FLOW_BASED_TUNNELS])) >> m4_ifdef([NORTHD_USE_PARALLELIZATION], >> [[NORTHD_USE_PARALLELIZATION]=NORTHD_USE_PARALLELIZATION >> ])dnl >> m4_ifdef([NORTHD_DUMMY_NUMA], [[NORTHD_DUMMY_NUMA]=NORTHD_DUMMY_NUMA >> ])dnl >> m4_ifdef([OVN_MONITOR_ALL], [[OVN_MONITOR_ALL]=OVN_MONITOR_ALL >> ])dnl >> +m4_ifdef([OVN_ENABLE_FLOW_BASED_TUNNELS], >> [[OVN_ENABLE_FLOW_BASED_TUNNELS]=OVN_ENABLE_FLOW_BASED_TUNNELS >> +])dnl >> ovs_init >> ]) >> >> -- >> 2.38.1 >> >> _______________________________________________ >> dev mailing list >> [email protected] >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev >> >> > Regards, > Ales > Seems like ovs robot had some issues let's try that again: Recheck-request: github-robot _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
