On Mon, Oct 19, 2026 at 2:25 PM Srikar Tati via dev
<[email protected]> wrote:
>
> From: Srikar Tati <[email protected]>
>
> Static routes can choose the fields that their ECMP group hashes on
> with the "selection_fields" column.  With selection fields the group
> uses the "hash" selection method instead of "dp_hash".  "dp_hash"
> needs a recirculation, which the tc datapath cannot offload, so only
> groups that use "hash" can be offloaded to a NIC.
>
> Learned routes have no NB row, so they always use "dp_hash".  Add the
> "dynamic-routing-ecmp-selection-fields" option to Logical Routers.  It
> takes the same fields as "selection_fields" and applies them to the
> ECMP groups of the router's learned routes.  If the option has a field
> that is not supported, northd logs a warning and keeps "dp_hash".
>
> Assisted-by: Claude Opus 5.5, Claude Code
> Signed-off-by: Srikar Tati <[email protected]>

Acked-by: Numan Siddique <[email protected]>

Numan

> ---
>  NEWS                           |  4 ++
>  northd/en-learned-route-sync.c | 23 +++++++++--
>  ovn-nb.xml                     | 22 +++++++++++
>  tests/ovn-northd.at            | 71 ++++++++++++++++++++++++++++++++++
>  4 files changed, 116 insertions(+), 4 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 13ffd6e2e..5a822cc21 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -28,6 +28,10 @@ Post v26.09.0
>         now written to the SB MAC_Binding table and consumed at the
>         same priority as dynamic entries, making the preference option
>         obsolete.
> +     * Add the "options:dynamic-routing-ecmp-selection-fields" option to
> +       Logical Routers.  It selects the fields that ECMP groups of learned
> +       routes hash on, the same as the "selection_fields" column of a
> +       static route.
>     - Removed OVN's ovs-bugtool plugin and helper scripts.
>     - Removed ovn-sim utility scripts.
>     - Removed ovn-docker utility scripts.
> diff --git a/northd/en-learned-route-sync.c b/northd/en-learned-route-sync.c
> index cbd516b68..959cadf9f 100644
> --- a/northd/en-learned-route-sync.c
> +++ b/northd/en-learned-route-sync.c
> @@ -199,10 +199,25 @@ parse_route_from_sbrec_route(struct hmap 
> *parsed_routes_out,
>          return NULL;
>      }
>
> -    return parsed_route_add(od, nexthop, &prefix, plen, false, lrp_addr_s,
> -                            out_port, 0, false, false, false, NULL,
> -                            ROUTE_SOURCE_LEARNED, true, &route->header_, 
> NULL,
> -                            parsed_routes_out);
> +    struct sset ecmp_selection_fields =
> +        SSET_INITIALIZER(&ecmp_selection_fields);
> +    const char *fields_s = smap_get(&od->nbr->options,
> +                                    "dynamic-routing-ecmp-selection-fields");
> +    if (fields_s && !ovn_ecmp_selection_fields_parse(fields_s,
> +                                                     
> &ecmp_selection_fields)) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +        VLOG_WARN_RL(&rl, "bad 'dynamic-routing-ecmp-selection-fields' %s "
> +                     "on router %s, using the default selection method",
> +                     fields_s, od->nbr->name);
> +    }
> +
> +    struct parsed_route *pr =
> +        parsed_route_add(od, nexthop, &prefix, plen, false, lrp_addr_s,
> +                         out_port, 0, false, false, false,
> +                         &ecmp_selection_fields, ROUTE_SOURCE_LEARNED, true,
> +                         &route->header_, NULL, parsed_routes_out);
> +    sset_destroy(&ecmp_selection_fields);
> +    return pr;
>  }
>
>  static void
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index feea50a25..0a3154858 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -3716,6 +3716,28 @@ or
>          </p>
>        </column>
>
> +      <column name="options" key="dynamic-routing-ecmp-selection-fields"
> +              type='{"type": "string"}'>
> +        <p>
> +          Only relevant if <ref column="options" key="dynamic-routing"/>
> +          is set to <code>true</code>.
> +        </p>
> +
> +        <p>
> +          A comma separated list of the fields that select the nexthop of an
> +          ECMP group of learned routes, for example
> +          <code>ip_proto,ip_src,ip_dst,tp_src,tp_dst</code>.  The supported
> +          fields and their meaning are the same as for the
> +          <ref column="selection_fields" 
> table="Logical_Router_Static_Route"/>
> +          column of a static route.  When set, the ECMP groups of learned
> +          routes use the <code>hash</code> selection method on these fields
> +          instead of <code>dp_hash</code>.  This lets a NIC offload the
> +          group, because <code>hash</code> does not need a recirculation.
> +          If the list contains a field that is not supported, OVN uses
> +          <code>dp_hash</code>.
> +        </p>
> +      </column>
> +
>        <column name="options" key="dynamic-routing-v4-prefix-nexthop"
>                type='{"type": "string"}'>
>          <p>
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index ec04d4d67..6e99e9aa4 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -17552,6 +17552,77 @@ OVN_CLEANUP_NORTHD
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - learned route ecmp selection fields])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 option:dynamic-routing=true
> +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
> +check ovn-nbctl --wait=sb lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 10.0.1.1/24
> +sw0=$(fetch_column port_binding _uuid logical_port=lr0-sw0)
> +sw1=$(fetch_column port_binding _uuid logical_port=lr0-sw1)
> +datapath=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
> +
> +# Learn 192.168.1.0/24 through both ports, so that it is an ECMP group.
> +check_uuid ovn-sbctl create Learned_Route \
> +    datapath=$datapath                    \
> +    logical_port=$sw0                     \
> +    ip_prefix=192.168.1.0/24              \
> +    nexthop=10.0.0.20
> +check_uuid ovn-sbctl create Learned_Route \
> +    datapath=$datapath                    \
> +    logical_port=$sw1                     \
> +    ip_prefix=192.168.1.0/24              \
> +    nexthop=10.0.1.20
> +check ovn-nbctl --wait=sb sync
> +
> +# Without selection fields the group uses dp_hash.
> +ovn-sbctl dump-flows lr0 > lr0flows
> +AT_CHECK([grep -e "(lr_in_ip_routing   ).*192.168.1.0" lr0flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1838 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; 
> reg8[[16..31]] = select(1, 2);)
> +])
> +
> +check ovn-nbctl --wait=sb set Logical_Router lr0 \
> +    options:dynamic-routing-ecmp-selection-fields="ip_proto,ip_src,ip_dst"
> +ovn-sbctl dump-flows lr0 > lr0flows
> +AT_CHECK([grep -e "(lr_in_ip_routing   ).*192.168.1.0" lr0flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1838 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; 
> reg8[[16..31]] = select(values=(1, 2); hash_fields="ip_dst,ip_proto,ip_src");)
> +])
> +
> +# tp_src and tp_dst give one flow for each L4 protocol.
> +check ovn-nbctl --wait=sb set Logical_Router lr0 \
> +    
> options:dynamic-routing-ecmp-selection-fields="ip_src,ip_dst,tp_src,tp_dst"
> +ovn-sbctl dump-flows lr0 > lr0flows
> +AT_CHECK([grep -e "(lr_in_ip_routing   ).*192.168.1.0" lr0flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1838 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; 
> reg8[[16..31]] = select(values=(1, 2); hash_fields="ip_dst,ip_proto,ip_src");)
> +  table=??(lr_in_ip_routing   ), priority=1839 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24 && sctp), action=(ip.ttl--; flags.loopback = 1; 
> reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); 
> hash_fields="ip_dst,ip_proto,ip_src,sctp_dst,sctp_src");)
> +  table=??(lr_in_ip_routing   ), priority=1839 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24 && tcp), action=(ip.ttl--; flags.loopback = 1; 
> reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); 
> hash_fields="ip_dst,ip_proto,ip_src,tcp_dst,tcp_src");)
> +  table=??(lr_in_ip_routing   ), priority=1839 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24 && udp), action=(ip.ttl--; flags.loopback = 1; 
> reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); 
> hash_fields="ip_dst,ip_proto,ip_src,udp_dst,udp_src");)
> +])
> +
> +# A field that is not supported falls back to dp_hash.
> +check ovn-nbctl --wait=sb set Logical_Router lr0 \
> +    options:dynamic-routing-ecmp-selection-fields="ip_src,foo"
> +ovn-sbctl dump-flows lr0 > lr0flows
> +AT_CHECK([grep -e "(lr_in_ip_routing   ).*192.168.1.0" lr0flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1838 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; 
> reg8[[16..31]] = select(1, 2);)
> +])
> +check grep -q "bad 'dynamic-routing-ecmp-selection-fields' ip_src,foo" \
> +    northd/ovn-northd.log
> +
> +check ovn-nbctl --wait=sb remove Logical_Router lr0 \
> +    options dynamic-routing-ecmp-selection-fields
> +ovn-sbctl dump-flows lr0 > lr0flows
> +AT_CHECK([grep -e "(lr_in_ip_routing   ).*192.168.1.0" lr0flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1838 , match=(reg7 == 0 && ip4.dst 
> == 192.168.1.0/24), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; 
> reg8[[16..31]] = select(1, 2);)
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD_NO_HV([
>  AT_SETUP([dynamic-routing - learned routes on unnumbered LRP])
>  AT_KEYWORDS([dynamic-routing])
> --
> 2.54.0
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to