On Mon, Nov 24, 2025 at 3:47 PM Ales Musil <[email protected]> wrote:
> > > 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 >> > Hi Han, the recheck failed too, I'm afraid there is regression could you please check that out? > >> 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 > Regards, Ales _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
