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]> --- 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
