Regards,
Lucas
Em qui., 22 de out. de 2026 às 12:54, Rosemarie O'Riorden
<[email protected] <mailto:[email protected]>> escreveu:
On 10/22/26 11:39 AM, Rosemarie O'Riorden wrote:
> I've been thinking about this more and I still think it's a great
> solution but I just want to highlight (as you did in comments)
that the
> main weakness of this algorithm is that pretty much the only case in
> which the assigning IDs loop will reach ECMP_ID_MAX_ROUNDS=32 is
if some
> elements have the same hash. In which case there would be 32
rounds of
> the same failure to find a unique ID repeated over and over, and
> deriving new hashes incorporating the round on each iteration
would be
> futile. And then in the end they would be assigned IDs based on
> processing order, which is okay as a last-resort fallback, but
that's 32
> rounds of wasted cycles to get there.
>
> So Lucas, sorry to ask for more changes, but I have two ideas/
requests:
>
> The only way I can think of avoiding the extra rounds is having a
> variable per-round that tracks the total number of claims made in
that
> round, and if there are no claims made in a round, it breaks out
of the
> claiming phase early and jumps straight to the fallback. If zero
claims
> are made in a round, it's extremely likely that those remaining
elements
> have the same hash as another. WDYT?
s/total number of claims/total number of assignments
I meant to track the total number of IDs actually assigned. The claims
would still be happening of course but no assignments would.
My only reservation about my idea is that I wouldn't want to break
early
if there are actual collisions stalling the assignment. I suppose you
could also use round>2 or some number as a condition to the break, so
that it doesn't give up on the deterministic ID assignment too early.
But I think breaking after a round with zero assignments is probably
enough, because it would be soooo incredinly unlikely for actual
collisions to result in a round with no claims.
>
> Another last minute ask I have is could you change 'size_t n' to
'size_t
> num_elements' or 'ecmp_elements' or something, so it has a name that
> conveys what it is more? Becuase we have a lot of loops that run
n times
> and it's easy to lose track of how many times they're running
when n is
> being passed into the functions containing those loops. I guess this
> would also involve changing 'n_left' as well.
>
> Thank you for bearing with me. :)
> - Rosemarie O'Riorden
>
> On 10/20/26 11:57 AM, Rosemarie O'Riorden wrote:
>> Hi Lucas, thanks for the new version!
>>
>> I'm happy with how it looks now. Thank you for taking my
feedback! I
>> think this is a solid algorithm.
>>
>> Since I've been the main reviewer, others might request changes,
but
>> from my side:
>> Acked-by: Rosemarie O'Riorden <[email protected]
<mailto:[email protected]>>
>>
>> (Assuming this passes CI because it's in a long queue right now
(the
>> changes are minor since v4 so it should))
>>
>> On 10/20/26 10:19 AM, Lucas Vargas Dias wrote:
>>> The ECMP group id and the member ids are written into the
>>> lr_in_ip_routing and lr_in_ip_routing_ecmp logical flows, but they
>>> were assigned from the order in which routes reached
>>> en_group_ecmp_route: the group id was hmap_count() at creation time
>>> and the member id was the insertion position. On a full recompute
>>> the routes are walked in hmap order, while incremental processing
>>> appends them in event order, so the same set of routes could get a
>>> different numbering. Every forced or fallback recompute could then
>>> delete and re-insert Logical_Flow rows in the Southbound DB even
>>> though nothing had changed.
>>>
>>> Derive the ids from hashes instead. The group id comes from the
>>> group key, i.e. prefix, prefix length, route table, source or
>>> destination route and source class, which does not change as
members
>>> are added or removed. The member id comes from the UUID of the
>>> route's source row, its nexthop and its output port: a NAT or
LB row
>>> produces one route per path to the peer router, so the UUID
alone is
>>> not unique within a group.
>>>
>>> When two elements derive the same id, all of them retry in the next
>>> round with an id derived from their hash and the round number, so
>>> that the ids only depend on the set of routes and not on the order
>>> in which they were processed. The members are listed in the
select()
>>> action in id order for the same reason.
>>>
>>> Besides making a recompute produce the same flows as incremental
>>> processing, adding or removing a route no longer changes the ids,
>>> and so the flows, of unrelated groups or members.
>>>
>>> Fixes: 6979a96138a2 ("northd: Support I+P for group_ecmp_route
engine.")
>>> Fixes: 5395e12f247c ("northd: Drop traffic for ECMP group with
>>> "discard" route.")
>>> Suggested-by: Rosemarie O'Riorden <[email protected]
<mailto:[email protected]>>
>>> Assisted-by: Claude Opus 5.5, Claude Code
>>> Signed-off-by: Lucas Vargas Dias <[email protected]>
>>> ---
>>> v5:
>>> - Implement route_sources_ecmp_compatible() on top of
>>> ecmp_group_source_class() so that the NAT/LB rule lives in
one
>>> place.
>>> - Hash the whole source row UUID with hash_bytes() in
>>> ecmp_member_hash().
>>> - Derive each id once per round in ecmp_assign_ids().
>>> - Cross-reference ecmp_group_hash() and ecmp_groups_find().
>>> v4:
>>> - Derive the ECMP group and member ids from hashes instead of
>>> sorting,
>>> so that adding or removing a route doesn't change the ids of
>>> unrelated groups or members (suggested by Rosemarie).
>>> - Added a test with NAT and LB routes reachable over several
paths,
>>> whose ECMP members share their source row.
>>> - Added ovn_strip_ecmp_ids(), ecmp_ids() and
ecmp_member_id() for the
>>> tests.
>>> v3:
>>> - Dropped nullable_strcmp() and the patch converting its
callers.
>>> - Removed the unused 'route_count' field from struct
>>> ecmp_groups_node.
>>> v2:
>>> - Moved nullable_strcmp() to lib/ovn-util.h and documented
its NULL
>>> handling.
>>> - Clarified in ecmp_groups_node_cmp() that it can't return 0
for two
>>> distinct groups.
>>>
>>> northd/en-group-ecmp-route.c | 262 ++++++++++++++++++++++++++----
>>> northd/en-group-ecmp-route.h | 1 -
>>> tests/ovn-macros.at <http://ovn-macros.at> | 34 ++++
>>> tests/ovn-northd.at <http://ovn-northd.at> | 300 +++
++++++++++++++++++++++++--------
>>> 4 files changed, 494 insertions(+), 103 deletions(-)
>>>
>>> diff --git a/northd/en-group-ecmp-route.c b/northd/en-group-
ecmp-route.c
>>> index aca197318..28c6d8ce5 100644
>>> --- a/northd/en-group-ecmp-route.c
>>> +++ b/northd/en-group-ecmp-route.c
>>> @@ -24,6 +24,7 @@
>>> #include "en-advertised-route-sync.h"
>>> #include "en-group-ecmp-route.h"
>>> #include "en-learned-route-sync.h"
>>> +#include "hash.h"
>>> #include "hmapx.h"
>>> #include "openvswitch/hmap.h"
>>> @@ -182,22 +183,26 @@ unique_routes_destroy(struct hmap
*unique_routes)
>>> hmap_destroy(unique_routes);
>>> }
>>> -/* Allow LB and NAT forwarding routes for the same prefix to
coexist as
>>> +/* Routes for the same prefix share an ecmp group if their
sources
>>> map to the
>>> + * same class.
>>> + *
>>> + * LB and NAT forwarding routes are in the same class so that
they
>>> coexist as
>>> * ECMP members instead of one silently replacing the other.
This
>>> is an
>>> * edge case: it arises only when two different peer LRs
independently
>>> * use the same IP as a VIP and a NAT external IP, and a CMS
would
>>> * typically prevent such overlap. Without this guard, one route
>>> * would silently win and traffic to the other service would be
>>> * black-holed. */
>>> +static enum route_source
>>> +ecmp_group_source_class(enum route_source source)
>>> +{
>>> + return source == ROUTE_SOURCE_LB ? ROUTE_SOURCE_NAT : source;
>>> +}
>>> +
>>> static bool
>>> route_sources_ecmp_compatible(enum route_source a, enum
>>> route_source b)
>>> {
>>> - if (a == b) {
>>> - return true;
>>> - }
>>> -
>>> - return (a == ROUTE_SOURCE_NAT || a == ROUTE_SOURCE_LB) &&
>>> - (b == ROUTE_SOURCE_NAT || b == ROUTE_SOURCE_LB);
>>> + return ecmp_group_source_class(a) ==
ecmp_group_source_class(b);
>>> }
>>> /* Remove the unique_routes_node from the group, and return the
>>> parsed_route
>>> @@ -228,14 +233,12 @@ ecmp_groups_add_route(struct
ecmp_groups_node
>>> *group,
>>> const struct parsed_route *route)
>>> {
>>> static struct vlog_rate_limit rl =
VLOG_RATE_LIMIT_INIT(5, 1);
>>> - if (group->route_count == UINT16_MAX) {
>>> + if (vector_len(&group->route_list) == UINT16_MAX) {
>>> VLOG_WARN_RL(&rl, "too many routes in a single ecmp
group.");
>>> return;
>>> }
>>> if (route->is_discard_route) {
>>> - group->has_discard_route = true;
>>> -
>>> char *prefix = normalize_v46_prefix(&route->prefix,
route-
>>> >plen);
>>> VLOG_WARN_RL(&rl, "The ECMP route \"%s\" contains
>>> \"discard\" "
>>> "route, the whole group will drop
traffic.",
>>> prefix);
>>> @@ -244,22 +247,13 @@ ecmp_groups_add_route(struct
ecmp_groups_node
>>> *group,
>>> struct ecmp_route_list_node er = (struct
ecmp_route_list_node) {
>>> .route = route,
>>> - .id = ++group->route_count,
>>> };
>>> - if (group->route_count == 1) {
>>> - sset_clone(&group->selection_fields, &route-
>>> >ecmp_selection_fields);
>>> - } else {
>>> - sset_intersect(&group->selection_fields,
>>> - &route->ecmp_selection_fields);
>>> - }
>>> -
>>> vector_push(&group->route_list, &er);
>>> }
>>> -/* Removes a route from an ecmp group. If the ecmp group
should persist
>>> - * afterwards you must call ecmp_groups_update_ids before any
further
>>> - * insertions. */
>>> +/* Removes a route from an ecmp group. The ids of the group
and of its
>>> + * members are refreshed by group_ecmp_datapath_finalize(). */
>>> static const struct parsed_route *
>>> ecmp_groups_remove_route(struct ecmp_groups_node *group,
>>> const struct parsed_route *pr)
>>> @@ -278,15 +272,217 @@ ecmp_groups_remove_route(struct
>>> ecmp_groups_node *group,
>>> return NULL;
>>> }
>>> +/* Hashes the key of 'eg', which is unique within a datapath. It
>>> must cover
>>> + * the fields that ecmp_groups_find() compares. Unlike the
members,
>>> the key
>>> + * does not change when routes are added to or removed from the
>>> group. */
>>> +static uint32_t
>>> +ecmp_group_hash(const struct ecmp_groups_node *eg)
>>> +{
>>> + uint32_t hash = hash_bytes(&eg->prefix, sizeof eg->prefix, 0);
>>> + hash = hash_add(hash, eg->plen);
>>> + hash = hash_add(hash, eg->route_table_id);
>>> + hash = hash_add(hash, eg->is_src_route);
>>> + hash = hash_add(hash, ecmp_group_source_class(eg->source));
>>> + return hash_finish(hash, 0);
>>> +}
>>> +
>>> +/* Hashes what makes 'route' unique within its ecmp group. A
NAT or
>>> LB row
>>> + * produces one route per path to the peer router, so its UUID
alone
>>> is not
>>> + * enough. */
>>> +static uint32_t
>>> +ecmp_member_hash(const struct parsed_route *route)
>>> +{
>>> + const struct uuid *uuid = &route->source_hint->uuid;
>>> + uint32_t hash = hash_bytes(uuid, sizeof *uuid, 0);
>>> + if (route->nexthop) {
>>> + hash = hash_bytes(route->nexthop, sizeof *route->nexthop,
>>> hash);
>>> + }
>>> + if (route->out_port) {
>>> + hash = hash_string(route->out_port->key, hash);
>>> + }
>>> + return hash;
>>> +}
>>> +
>>> +/* Number of rounds after which ecmp_assign_ids() stops retrying
>>> hashes that
>>> + * collide and falls back to the next free id. */
>>> +#define ECMP_ID_MAX_ROUNDS 32
>>> +
>>> +struct ecmp_id_slot {
>>> + struct hmap_node hmap_node;
>>> + uint16_t id;
>>> + bool taken; /* Assigned to an element. */
>>> + size_t n_claims; /* Elements that derived 'id' in the
current
>>> round. */
>>> +};
>>> +
>>> +static struct ecmp_id_slot *
>>> +ecmp_id_slot_get(struct hmap *slots, uint16_t id)
>>> +{
>>> + struct ecmp_id_slot *slot;
>>> + HMAP_FOR_EACH_WITH_HASH (slot, hmap_node, id, slots) {
>>> + if (slot->id == id) {
>>> + return slot;
>>> + }
>>> + }
>>> + slot = xzalloc(sizeof *slot);
>>> + slot->id = id;
>>> + hmap_insert(slots, &slot->hmap_node, id);
>>> + return slot;
>>> +}
>>> +
>>> +static uint16_t
>>> +ecmp_id_from_hash(uint32_t hash, unsigned int round)
>>> +{
>>> + return hash_int(round, hash) % UINT16_MAX + 1;
>>> +}
>>> +
>>> +/* Gives each of the 'n' elements a distinct id in [1,
UINT16_MAX]
>>> derived
>>> + * from 'hashes', so that the id of an element does not depend on
>>> the order
>>> + * in which the elements are passed, nor on the ids of unrelated
>>> elements.
>>> + *
>>> + * An element keeps the id derived from its hash unless that
id is
>>> already
>>> + * taken or another element derives the same id in the same
round.
>>> In that
>>> + * case all of them retry in the next round, with an id
derived from
>>> their
>>> + * hash and the round number. The result therefore only
depends on
>>> the set
>>> + * of hashes. Elements whose hashes are equal can't be told
apart
>>> and, after
>>> + * ECMP_ID_MAX_ROUNDS rounds, take the next free id in the order
>>> they are
>>> + * passed. */
>>> +static void
>>> +ecmp_assign_ids(const uint32_t *hashes, uint16_t *ids, size_t n)
>>> +{
>>> + struct hmap slots = HMAP_INITIALIZER(&slots);
>>> + /* The slot that each element claims in the current round. */
>>> + struct ecmp_id_slot **claims = xmalloc(n * sizeof *claims);
>>> + struct ecmp_id_slot *slot;
>>> + size_t n_left = n;
>>> +
>>> + memset(ids, 0, n * sizeof *ids);
>>> + for (unsigned int round = 0; n_left && round <
ECMP_ID_MAX_ROUNDS;
>>> + round++) {
>>> + for (size_t i = 0; i < n; i++) {
>>> + if (!ids[i]) {
>>> + uint16_t id = ecmp_id_from_hash(hashes[i], round);
>>> + claims[i] = ecmp_id_slot_get(&slots, id);
>>> + claims[i]->n_claims++;
>>> + }
>>> + }
>>> + for (size_t i = 0; i < n; i++) {
>>> + if (!ids[i]) {
>>> + slot = claims[i];
>>> + if (!slot->taken && slot->n_claims == 1) {
>>> + slot->taken = true;
>>> + ids[i] = slot->id;
>>> + n_left--;
>>> + }
>>> + }
>>> + }
>>> + HMAP_FOR_EACH (slot, hmap_node, &slots) {
>>> + slot->n_claims = 0;
>>> + }
>>> + }
>>> + free(claims);
>>> +
>>> + for (size_t i = 0; n_left && i < n; i++) {
>>> + if (ids[i]) {
>>> + continue;
>>> + }
>>> + uint16_t id = ecmp_id_from_hash(hashes[i], 0);
>>> + while ((slot = ecmp_id_slot_get(&slots, id))->taken) {
>>> + id = id % UINT16_MAX + 1;
>>> + }
>>> + slot->taken = true;
>>> + ids[i] = id;
>>> + n_left--;
>>> + }
>>> +
>>> + HMAP_FOR_EACH_POP (slot, hmap_node, &slots) {
>>> + free(slot);
>>> + }
>>> + hmap_destroy(&slots);
>>> +}
>>> +
>>> +static int
>>> +ecmp_route_list_node_id_cmp(const void *a_, const void *b_)
>>> +{
>>> + const struct ecmp_route_list_node *a = a_;
>>> + const struct ecmp_route_list_node *b = b_;
>>> +
>>> + return a->id < b->id ? -1 : a->id > b->id;
>>> +}
>>> +
>>> +/* Recomputes everything in 'group' that is derived from its
>>> members, so that
>>> + * the result only depends on the set of members. */
>>> static void
>>> -ecmp_group_update_ids(struct ecmp_groups_node *group)
>>> +ecmp_group_finalize(struct ecmp_groups_node *group)
>>> {
>>> + size_t n = vector_len(&group->route_list);
>>> + uint32_t *hashes = xmalloc(n * sizeof *hashes);
>>> + uint16_t *ids = xmalloc(n * sizeof *ids);
>>> struct ecmp_route_list_node *er;
>>> size_t i = 0;
>>> +
>>> + VECTOR_FOR_EACH_PTR (&group->route_list, er) {
>>> + hashes[i++] = ecmp_member_hash(er->route);
>>> + }
>>> + ecmp_assign_ids(hashes, ids, n);
>>> + i = 0;
>>> VECTOR_FOR_EACH_PTR (&group->route_list, er) {
>>> - er->id = i++;
>>> + er->id = ids[i++];
>>> }
>>> - group->route_count = i;
>>> + free(hashes);
>>> + free(ids);
>>> +
>>> + /* The members are listed in this order in the select()
action. */
>>> + vector_qsort(&group->route_list, ecmp_route_list_node_id_cmp);
>>> +
>>> + bool first = true;
>>> + group->has_discard_route = false;
>>> + VECTOR_FOR_EACH_PTR (&group->route_list, er) {
>>> + if (er->route->is_discard_route) {
>>> + group->has_discard_route = true;
>>> + }
>>> + if (first) {
>>> + sset_destroy(&group->selection_fields);
>>> + sset_clone(&group->selection_fields,
>>> + &er->route->ecmp_selection_fields);
>>> + group->source = er->route->source;
>>> + first = false;
>>> + } else {
>>> + sset_intersect(&group->selection_fields,
>>> + &er->route->ecmp_selection_fields);
>>> + }
>>> + }
>>> +}
>>> +
>>> +/* Assigns the group and member ids of all ecmp groups of 'gn'.
>>> Must be
>>> + * called after the ecmp groups of 'gn' changed, both on full
>>> recompute and on
>>> + * incremental processing, so that both yield the same ids for
the
>>> same set
>>> + * of routes and the generated logical flows do not change on
>>> recompute. */
>>> +static void
>>> +group_ecmp_datapath_finalize(struct group_ecmp_datapath *gn)
>>> +{
>>> + size_t n = hmap_count(&gn->ecmp_groups);
>>> + if (!n) {
>>> + return;
>>> + }
>>> +
>>> + struct ecmp_groups_node **groups = xmalloc(n * sizeof
*groups);
>>> + uint32_t *hashes = xmalloc(n * sizeof *hashes);
>>> + uint16_t *ids = xmalloc(n * sizeof *ids);
>>> + struct ecmp_groups_node *eg;
>>> + size_t i = 0;
>>> +
>>> + HMAP_FOR_EACH (eg, hmap_node, &gn->ecmp_groups) {
>>> + ecmp_group_finalize(eg);
>>> + groups[i] = eg;
>>> + hashes[i++] = ecmp_group_hash(eg);
>>> + }
>>> + ecmp_assign_ids(hashes, ids, n);
>>> + for (i = 0; i < n; i++) {
>>> + groups[i]->id = ids[i];
>>> + }
>>> + free(groups);
>>> + free(hashes);
>>> + free(ids);
>>> }
>>> static struct ecmp_groups_node *
>>> @@ -302,7 +498,6 @@ ecmp_groups_add(struct group_ecmp_datapath *gn,
>>> struct ecmp_groups_node *eg = xzalloc(sizeof *eg);
>>> hmap_insert(&gn->ecmp_groups, &eg->hmap_node, route->hash);
>>> - eg->id = hmap_count(&gn->ecmp_groups);
>>> eg->prefix = route->prefix;
>>> eg->plen = route->plen;
>>> eg->is_src_route = route->is_src_route;
>>> @@ -321,6 +516,7 @@ ecmp_groups_find(struct group_ecmp_datapath
*gn,
>>> {
>>> struct ecmp_groups_node *eg;
>>> HMAP_FOR_EACH_WITH_HASH (eg, hmap_node, route->hash, &gn-
>>> >ecmp_groups) {
>>> + /* Keep in sync with ecmp_group_hash(). */
>>> if (ipv6_addr_equals(&eg->prefix, &route->prefix) &&
>>> eg->plen == route->plen &&
>>> eg->is_src_route == route->is_src_route &&
>>> @@ -396,6 +592,10 @@ group_ecmp_route(struct group_ecmp_route_data
>>> *data,
>>> gn = group_ecmp_datapath_lookup_or_add(data, pr->od);
>>> add_route(gn, pr);
>>> }
>>> +
>>> + HMAP_FOR_EACH (gn, hmap_node, &data->datapaths) {
>>> + group_ecmp_datapath_finalize(gn);
>>> + }
>>> }
>>> enum engine_node_state
>>> @@ -472,9 +672,7 @@ handle_deleted_route(struct
group_ecmp_route_data
>>> *data,
>>> * unique route. Otherwise it stays an ecmp group
with
>>> just one
>>> * member. */
>>> ecmp_groups_remove_route(eg, pr);
>>> - if (ecmp_group_has_symmetric_reply(eg)) {
>>> - ecmp_group_update_ids(eg);
>>> - } else {
>>> + if (!ecmp_group_has_symmetric_reply(eg)) {
>>> const struct ecmp_route_list_node *er =
>>> vector_get_ptr(&eg->route_list, 0);
>>> unique_routes_add(node, er->route);
>>> @@ -482,11 +680,8 @@ handle_deleted_route(struct
>>> group_ecmp_route_data *data,
>>> ecmp_groups_node_free(eg);
>>> }
>>> } else {
>>> - /* We can just remove the member from the group.
We need
>>> to update
>>> - * the indices of all routes so that future
insertions
>>> directly
>>> - * have a new index. */
>>> + /* We can just remove the member from the group. */
>>> ecmp_groups_remove_route(eg, pr);
>>> - ecmp_group_update_ids(eg);
>>> }
>>> }
>>> @@ -537,6 +732,7 @@
>>> group_ecmp_route_learned_route_change_handler(struct engine_node
>>> *eng_node,
>>> hmapx_add(&data-
>trk_data.deleted_datapath_routes, node);
>>> hmap_remove(&data->datapaths, &node->hmap_node);
>>> } else {
>>> + group_ecmp_datapath_finalize(node);
>>> hmapx_add(&data->trk_data.crupdated_datapath_routes,
>>> node);
>>> }
>>> }
>>> @@ -589,6 +785,7 @@ group_ecmp_route_routes_change_handler(struct
>>> engine_node *eng_node,
>>> hmapx_add(&data-
>trk_data.deleted_datapath_routes, node);
>>> hmap_remove(&data->datapaths, &node->hmap_node);
>>> } else {
>>> + group_ecmp_datapath_finalize(node);
>>> hmapx_add(&data->trk_data.crupdated_datapath_routes,
>>> node);
>>> }
>>> }
>>> @@ -645,6 +842,7 @@
>>> group_ecmp_route_dynamic_routes_change_handler(struct engine_node
>>> *eng_node,
>>> hmapx_add(&gdata-
>trk_data.deleted_datapath_routes, node);
>>> hmap_remove(&gdata->datapaths, &node->hmap_node);
>>> } else {
>>> + group_ecmp_datapath_finalize(node);
>>> hmapx_add(&gdata-
>trk_data.crupdated_datapath_routes,
>>> node);
>>> }
>>> }
>>> diff --git a/northd/en-group-ecmp-route.h b/northd/en-group-
ecmp-route.h
>>> index b40935be2..13f84a036 100644
>>> --- a/northd/en-group-ecmp-route.h
>>> +++ b/northd/en-group-ecmp-route.h
>>> @@ -37,7 +37,6 @@ struct ecmp_groups_node {
>>> bool has_discard_route;
>>> enum route_source source;
>>> uint32_t route_table_id;
>>> - uint16_t route_count;
>>> struct vector route_list; /* Contains ecmp_route_list_node */
>>> struct sset selection_fields;
>>> };
>>> diff --git a/tests/ovn-macros.at <http://ovn-macros.at> b/
tests/ovn-macros.at <http://ovn-macros.at>
>>> index 7a19c0130..1f36f127a 100644
>>> --- a/tests/ovn-macros.at <http://ovn-macros.at>
>>> +++ b/tests/ovn-macros.at <http://ovn-macros.at>
>>> @@ -1568,6 +1568,40 @@ ovn_strip_lflows() {
>>> sed 's/table=[[0-9]]\{1,2\}\s\?/table=??/g' | sort
>>> }
>>> +# The ECMP group and member ids are derived from hashes of the
>>> routes, replace
>>> +# them with <gid> and <mid>.
>>> +ovn_strip_ecmp_ids() {
>>> + sed -e 's/\(reg8\[[0\.\.15\]] =\{1,2\} \)[[1-9]][[0-9]]*/
>>> \1<gid>/' \
>>> + -e 's/\(reg8\[[16\.\.31\]] =\{1,2\} \)[[1-9]][[0-9]]*/
>>> \1<mid>/' \
>>> + -e ':a' \
>>> + -e 's/\(select[[^)]]*[[(, ]]\)[[0-9]][[0-9]]*/\1<mid>/' \
>>> + -e 'ta'
>>> +}
>>> +
>>> +# ecmp_ids LR PREFIX: prints the group id and the comma separated
>>> member ids
>>> +# of the ECMP group of PREFIX on LR, and fails if the members
listed
>>> in its
>>> +# select() action are not the ones that have a flow in
>>> lr_in_ip_routing_ecmp.
>>> +ecmp_ids() {
>>> + local flows gid sel mids
>>> + flows=$(ovn-sbctl dump-flows $1)
>>> + gid=$(echo "$flows" | sed -n "s|.*ip[[46]]\.dst ==
>>> $2).*reg8\[[0\.\.15\]] = \([[0-9]]*\);.*|\1|p")
>>> + sel=$(echo "$flows" | sed -n "s|.*ip[[46]]\.dst ==
>>> $2).*select(\([[^)]]*\));.*|\1|p" | tr -d ' ')
>>> + mids=$(echo "$flows" | sed -n "s|.*(reg8\[[0\.\.15\]] ==
$gid &&
>>> reg8\[[16\.\.31\]] == \([[0-9]]*\)).*|\1|p" | sort -n | paste -
sd, -)
>>> + if test -z "$gid" || test "$sel" != "$mids"; then
>>> + echo "$2: group '$gid', select($sel) !=
members($mids)" >&2
>>> + return 1
>>> + fi
>>> + echo "$gid $sel"
>>> +}
>>> +
>>> +# ecmp_member_id LR PREFIX NEXTHOP: prints the member id of
NEXTHOP
>>> in the
>>> +# ECMP group of PREFIX on LR.
>>> +ecmp_member_id() {
>>> + local gid
>>> + gid=$(ecmp_ids $1 $2 | cut -d' ' -f1)
>>> + ovn-sbctl dump-flows $1 | sed -n "s|.*(reg8\[[0\.\.15\]]
== $gid
>>> && reg8\[[16\.\.31\]] == \([[0-9]]*\)), action=(reg0 = $3;.*|\1|p"
>>> +}
>>> +
>>> ovn_strip_collector_set() {
>>> sed 's/collector_set=[[0-9]]*,\?/collector_set=??,/g'
>>> }
>>> diff --git a/tests/ovn-northd.at <http://ovn-northd.at> b/
tests/ovn-northd.at <http://ovn-northd.at>
>>> index 7a0a400c4..e00627e42 100644
>>> --- a/tests/ovn-northd.at <http://ovn-northd.at>
>>> +++ b/tests/ovn-northd.at <http://ovn-northd.at>
>>> @@ -7799,18 +7799,18 @@ check_row_count ECMP_Nexthop 1
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_lflows],
>>> [0], [dnl
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing ), priority=0 , match=(1),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=10300,
>>> match=(ct_mark.ecmp_reply_port == 1 && reg7 == 0 && ip4.dst ==
>>> 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1; eth.src =
>>> 00:00:20:20:12:13; reg5 = 192.168.0.1; outport = "lr0-public";
next;)
>>> table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs ||
>>> nd_ra), action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 192.168.0.0/24 <http://192.168.0.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-
>>> public"; flags.loopback = 1; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing ), priority=1936 , match=(reg7
== 0 &&
>>> ip4.dst == 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = 1; next;)
>>> + table=??(lr_in_ip_routing ), priority=1936 , match=(reg7
== 0 &&
>>> ip4.dst == 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = <mid>; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--;
>>> reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 =
>>> fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport =
>>> "lr0-public"; flags.loopback = 1; reg9[[9]] = 0; next;)
>>> ])
>>> -AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows |
>>> ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows |
>>> ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 =
>>> 192.168.0.10; reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13;
>>> outport = "lr0-public"; reg9[[9]] = 1; reg9[[10]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 192.168.0.10; reg5 = 192.168.0.1; eth.src =
>>> 00:00:20:20:12:13; outport = "lr0-public"; reg9[[9]] = 1;
reg9[[10]]
>>> = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -7825,18 +7825,18 @@ check_column 192.168.0.10 ECMP_Nexthop
>>> nexthop nexthop=192.168.0.10
>>> check_column 192.168.0.20 ECMP_Nexthop nexthop
nexthop=192.168.0.20
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_lflows],
>>> [0], [dnl
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing ), priority=0 , match=(1),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=10300,
>>> match=(ct_mark.ecmp_reply_port == 1 && reg7 == 0 && ip4.dst ==
>>> 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1; eth.src =
>>> 00:00:20:20:12:13; reg5 = 192.168.0.1; outport = "lr0-public";
next;)
>>> table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs ||
>>> nd_ra), action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 192.168.0.0/24 <http://192.168.0.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-
>>> public"; flags.loopback = 1; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing ), priority=1936 , match=(reg7
== 0 &&
>>> ip4.dst == 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> + table=??(lr_in_ip_routing ), priority=1936 , match=(reg7
== 0 &&
>>> ip4.dst == 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--;
>>> reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 =
>>> fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport =
>>> "lr0-public"; flags.loopback = 1; reg9[[9]] = 0; next;)
>>> ])
>>> -AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed
>>> 's/192\.168\.0\..0/192.168.0.??/ <http://192.168.0.??/>' |
ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed
>>> 's/192\.168\.0\..0/192.168.0.??/ <http://192.168.0.??/>' |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 =
>>> 192.168.0.??; reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13;
>>> outport = "lr0-public"; reg9[[9]] = 1; reg9[[10]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 =
>>> 192.168.0.??; reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13;
>>> outport = "lr0-public"; reg9[[9]] = 1; reg9[[10]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 192.168.0.??; reg5 = 192.168.0.1; eth.src =
>>> 00:00:20:20:12:13; outport = "lr0-public"; reg9[[9]] = 1;
reg9[[10]]
>>> = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 192.168.0.??; reg5 = 192.168.0.1; eth.src =
>>> 00:00:20:20:12:13; outport = "lr0-public"; reg9[[9]] = 1;
reg9[[10]]
>>> = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -7867,18 +7867,18 @@ check_column 192.168.0.10 ECMP_Nexthop
>>> nexthop nexthop=192.168.0.10
>>> check_column 192.168.0.20 ECMP_Nexthop nexthop
nexthop=192.168.0.20
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_lflows],
>>> [0], [dnl
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing ), priority=0 , match=(1),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=10300,
>>> match=(ct_mark.ecmp_reply_port == 1 && reg7 == 0 && ip4.dst ==
>>> 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1; eth.src =
>>> 00:00:20:20:12:13; reg5 = 192.168.0.1; outport = "lr0-public";
next;)
>>> table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs ||
>>> nd_ra), action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 192.168.0.0/24 <http://192.168.0.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-
>>> public"; flags.loopback = 1; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing ), priority=1936 , match=(reg7
== 0 &&
>>> ip4.dst == 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> + table=??(lr_in_ip_routing ), priority=1936 , match=(reg7
== 0 &&
>>> ip4.dst == 1.0.0.1/32 <http://1.0.0.1/32>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--;
>>> reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 =
>>> fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport =
>>> "lr0-public"; flags.loopback = 1; reg9[[9]] = 0; next;)
>>> ])
>>> -AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed
>>> 's/192\.168\.0\..0/192.168.0.??/ <http://192.168.0.??/>' |
ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed
>>> 's/192\.168\.0\..0/192.168.0.??/ <http://192.168.0.??/>' |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 =
>>> 192.168.0.??; reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13;
>>> outport = "lr0-public"; reg9[[9]] = 1; reg9[[10]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 =
>>> 192.168.0.??; reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13;
>>> outport = "lr0-public"; reg9[[9]] = 1; reg9[[10]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 192.168.0.??; reg5 = 192.168.0.1; eth.src =
>>> 00:00:20:20:12:13; outport = "lr0-public"; reg9[[9]] = 1;
reg9[[10]]
>>> = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 192.168.0.??; reg5 = 192.168.0.1; eth.src =
>>> 00:00:20:20:12:13; outport = "lr0-public"; reg9[[9]] = 1;
reg9[[10]]
>>> = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -7916,8 +7916,8 @@ check ovn-nbctl set
logical_router_static_route
>>> $route2_uuid selection_fields="i
>>> check ovn-nbctl --wait=sb sync
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -e "(lr_in_ip_routing ).*3.3.0.0" lr0flows | sed
>>> 's/ table=../table=??/' | sort], [0], [dnl
>>> - table=??(lr_in_ip_routing ), priority=1744 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16>), 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");)
>>> +AT_CHECK([grep -e "(lr_in_ip_routing ).*3.3.0.0" lr0flows |
>>> ovn_strip_ecmp_ids | sed 's/table=../table=??/' | sort], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1744 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(values=(<mid>,
<mid>);
>>> hash_fields="ip_dst,ip_proto,ip_src");)
>>> ])
>>> check ovn-nbctl set logical_router_static_route $route1_uuid
>>> selection_fields="ip_src,ip_dst,tp_src,tp_dst"
>>> @@ -7925,11 +7925,11 @@ check ovn-nbctl set
>>> logical_router_static_route $route2_uuid selection_fields="i
>>> check ovn-nbctl --wait=sb sync
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -e "(lr_in_ip_routing ).*3.3.0.0" lr0flows | sed
>>> 's/ table=../table=??/' | sort], [0], [dnl
>>> - table=??(lr_in_ip_routing ), priority=1744 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16>), 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=1745 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16> && 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=1745 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16> && 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=1745 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16> && 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");)
>>> +AT_CHECK([grep -e "(lr_in_ip_routing ).*3.3.0.0" lr0flows |
>>> ovn_strip_ecmp_ids | sed 's/table=../table=??/' | sort], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1744 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16>), action=(ip.ttl--;
flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(values=(<mid>,
<mid>);
>>> hash_fields="ip_dst,ip_proto,ip_src");)
>>> + table=??(lr_in_ip_routing ), priority=1745 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16> && sctp),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(values=(<mid>,
<mid>);
>>> hash_fields="ip_dst,ip_proto,ip_src,sctp_dst,sctp_src");)
>>> + table=??(lr_in_ip_routing ), priority=1745 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16> && tcp),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(values=(<mid>,
<mid>);
>>> hash_fields="ip_dst,ip_proto,ip_src,tcp_dst,tcp_src");)
>>> + table=??(lr_in_ip_routing ), priority=1745 , match=(reg7
== 0 &&
>>> ip4.dst == 3.3.0.0/16 <http://3.3.0.0/16> && udp),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(values=(<mid>,
<mid>);
>>> hash_fields="ip_dst,ip_proto,ip_src,udp_dst,udp_src");)
>>> ])
>>> AT_CHECK([grep -e "lr_in_ecmp_stateful_egr" lr0flows |
>>> ovn_strip_lflows], [0], [dnl
>>> @@ -17440,19 +17440,19 @@ datapath=$(fetch_column datapath_binding
>>> _uuid external_ids:name=lr0)
>>> # Validate the routes.
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_lflows],
>>> [0], [dnl
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing ), priority=0 , match=(1),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs ||
>>> nd_ra), action=(drop;)
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.0.0/24 <http://10.0.0.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.1.0/24 <http://10.0.1.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.1.1; eth.src = 00:00:00:00:ff:02; outport = "lr0-sw1";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-sw0" && ip6.dst == fe80::/64), action=(ip.ttl--;
reg8[[0..15]] =
>>> 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff01; eth.src =
>>> 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 1;
reg9[[9]]
>>> = 0; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-sw1" && ip6.dst == fe80::/64), action=(ip.ttl--;
reg8[[0..15]] =
>>> 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff02; eth.src =
>>> 00:00:00:00:ff:02; outport = "lr0-sw1"; flags.loopback = 1;
reg9[[9]]
>>> = 0; next;)
>>> ])
>>> -AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed -e
>>> 's/10\.0\..\./10.0.??./g' -e 's/lr0-sw./lr0-sw??/' -e
>>> 's/00:ff:0./00:ff:0?/' | ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed -e
>>> 's/10\.0\..\./10.0.??./g' -e 's/lr0-sw./lr0-sw??/' -e
>>> 's/00:ff:0./00:ff:0?/' | ovn_strip_ecmp_ids |
ovn_strip_lflows], [0],
>>> [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 =
>>> 10.0.??.10; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 =
>>> 10.0.??.10; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -17466,20 +17466,20 @@ check ovn-nbctl --wait=sb sync
>>> check_row_count Advertised_Route 4
>>> check_row_count Learned_Route 1
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_lflows],
>>> [0], [dnl
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing ), priority=0 , match=(1),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs ||
>>> nd_ra), action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=1838 , match=(reg7
== 0
>>> && ip4.dst == 192.168.1.0/24 <http://192.168.1.0/24>),
action=(ip.ttl--; reg8[[0..15]] = 0;
>>> reg0 = 10.0.0.20; reg5 = 10.0.0.1; eth.src = 00:00:00:00:ff:01;
>>> outport = "lr0-sw0"; flags.loopback = 1; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.0.0/24 <http://10.0.0.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.1.0/24 <http://10.0.1.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.1.1; eth.src = 00:00:00:00:ff:02; outport = "lr0-sw1";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-sw0" && ip6.dst == fe80::/64), action=(ip.ttl--;
reg8[[0..15]] =
>>> 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff01; eth.src =
>>> 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 1;
reg9[[9]]
>>> = 0; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-sw1" && ip6.dst == fe80::/64), action=(ip.ttl--;
reg8[[0..15]] =
>>> 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff02; eth.src =
>>> 00:00:00:00:ff:02; outport = "lr0-sw1"; flags.loopback = 1;
reg9[[9]]
>>> = 0; next;)
>>> ])
>>> -AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed -e
>>> 's/10\.0\..\./10.0.??./g' -e 's/lr0-sw./lr0-sw??/' -e
>>> 's/00:ff:0./00:ff:0?/' | ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed -e
>>> 's/10\.0\..\./10.0.??./g' -e 's/lr0-sw./lr0-sw??/' -e
>>> 's/00:ff:0./00:ff:0?/' | ovn_strip_ecmp_ids |
ovn_strip_lflows], [0],
>>> [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 =
>>> 10.0.??.10; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 =
>>> 10.0.??.10; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -17494,22 +17494,22 @@ check ovn-nbctl --wait=sb sync
>>> check_row_count Advertised_Route 4
>>> check_row_count Learned_Route 2
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_lflows],
>>> [0], [dnl
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing ), priority=0 , match=(1),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs ||
>>> nd_ra), action=(drop;)
>>> - table=??(lr_in_ip_routing ), priority=1838 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.1.0/24 <http://192.168.1.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 2; reg8[[16..31]] = select(1, 2);)
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> + table=??(lr_in_ip_routing ), priority=1838 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.1.0/24 <http://192.168.1.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.0.0/24 <http://10.0.0.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.1.0/24 <http://10.0.1.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.1.1; eth.src = 00:00:00:00:ff:02; outport = "lr0-sw1";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-sw0" && ip6.dst == fe80::/64), action=(ip.ttl--;
reg8[[0..15]] =
>>> 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff01; eth.src =
>>> 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 1;
reg9[[9]]
>>> = 0; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-sw1" && ip6.dst == fe80::/64), action=(ip.ttl--;
reg8[[0..15]] =
>>> 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff02; eth.src =
>>> 00:00:00:00:ff:02; outport = "lr0-sw1"; flags.loopback = 1;
reg9[[9]]
>>> = 0; next;)
>>> ])
>>> -AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed -e
>>> 's/10\.0\..\./10.0.??./g' -e 's/lr0-sw./lr0-sw??/' -e
>>> 's/00:ff:0./00:ff:0?/' | ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed -e
>>> 's/10\.0\..\./10.0.??./g' -e 's/lr0-sw./lr0-sw??/' -e
>>> 's/00:ff:0./00:ff:0?/' | ovn_strip_ecmp_ids |
ovn_strip_lflows], [0],
>>> [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 =
>>> 10.0.??.10; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 =
>>> 10.0.??.10; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 2 && reg8[[16..31]] == 1), action=(reg0 =
>>> 10.0.??.20; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 2 && reg8[[16..31]] == 2), action=(reg0 =
>>> 10.0.??.20; reg5 = 10.0.??.1; eth.src = 00:00:00:00:ff:0?;
outport =
>>> "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.20; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.??.20; reg5 = 10.0.??.1; eth.src =
>>> 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -17536,12 +17536,12 @@ check ovn-nbctl --wait=sb sync
>>> check_row_count Advertised_Route 4
>>> check_row_count Learned_Route 4
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | sed -e 's/
>>> reg8\[[0..15\]] = [[123]]/reg8\[[0..15\]] = ??/' |
ovn_strip_lflows],
>>> [0], [dnl
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
ovn_strip_ecmp_ids |
>>> ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing ), priority=0 , match=(1),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs ||
>>> nd_ra), action=(drop;)
>>> - table=??(lr_in_ip_routing ), priority=1838 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = ??; reg8[[16..31]] = select(1, 2);)
>>> - table=??(lr_in_ip_routing ), priority=1838 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.1.0/24 <http://192.168.1.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = ??; reg8[[16..31]] = select(1, 2);)
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = ??; reg8[[16..31]] = select(1, 2);)
>>> + table=??(lr_in_ip_routing ), priority=1838 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> + table=??(lr_in_ip_routing ), priority=1838 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.1.0/24 <http://192.168.1.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.0.0/24 <http://192.168.0.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.0.0/24 <http://10.0.0.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=1842 ,
match=(ip4.dst ==
>>> 10.0.1.0/24 <http://10.0.1.0/24>), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = ip4.dst;
>>> reg5 = 10.0.1.1; eth.src = 00:00:00:00:ff:02; outport = "lr0-sw1";
>>> flags.loopback = 1; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing ), priority=2322 ,
match=(inport ==
>>> "lr0-sw0" && ip6.dst == fe80::/64), action=(ip.ttl--;
reg8[[0..15]] =
>>> 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff01; eth.src =
>>> 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 1;
reg9[[9]]
>>> = 0; next;)
>>> @@ -17586,13 +17586,13 @@ check_uuid ovn-sbctl create
Learned_Route \
>>> check ovn-nbctl --wait=sb sync
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | grep -F
>>> "42.42.42.42" | ovn_strip_lflows], [0], [dnl
>>> - table=??(lr_in_ip_routing ), priority=1934 , match=(reg7
== 0 &&
>>> ip4.dst == 42.42.42.42/32 <http://42.42.42.42/32>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | grep -F
>>> "42.42.42.42" | ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1934 , match=(reg7
== 0 &&
>>> ip4.dst == 42.42.42.42/32 <http://42.42.42.42/32>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> ])
>>> -AT_CHECK([grep "lr_in_ip_routing_ecmp" lr0flows | grep "42" |
sed -e
>>> 's/reg8\[[16..31\]] == [[12]]/reg8\[[16..31\]] == ??/g' |
>>> ovn_strip_lflows], [0], [dnl
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ??), action=(reg0 =
>>> 192.168.2.42; eth.src = 00:00:00:00:ff:02; outport = "lrp2";
>>> reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ??), action=(reg0 =
>>> 192.168.3.42; eth.src = 00:00:00:00:ff:03; outport = "lrp3";
>>> reg9[[9]] = 1; next;)
>>> +AT_CHECK([grep "lr_in_ip_routing_ecmp" lr0flows | grep "42" |
>>> ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 192.168.2.42; eth.src = 00:00:00:00:ff:02;
outport =
>>> "lrp2"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 192.168.3.42; eth.src = 00:00:00:00:ff:03;
outport =
>>> "lrp3"; reg9[[9]] = 1; next;)
>>> ])
>>> OVN_CLEANUP_NORTHD
>>> @@ -19350,8 +19350,8 @@ check_row_count Advertised_Route 1 \
>>> # One ECMP selection flow covers the prefix and two member flows
>>> carry the
>>> # respective neighbor nexthops.
>>> ovn-sbctl lflow-list lr0 > lr0_flows
>>> -AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32
<http://172.16.1.10/32>' lr0_flows | \
>>> - grep -c 'select(1, 2)'], [0], [1
>>> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32
<http://172.16.1.10/32>' lr0_flows |
>>> ovn_strip_ecmp_ids | \
>>> + grep -c 'select(<mid>, <mid>)'], [0], [1
>>> ])
>>> AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0_flows | \
>>> grep -E -c 'reg0 = 10\.0\.0\.[[12]];'], [0], [2
>>> @@ -19370,6 +19370,85 @@ OVN_CLEANUP_NORTHD
>>> AT_CLEANUP
>>> ])
>>> +OVN_FOR_EACH_NORTHD_NO_HV([
>>> +AT_SETUP([dynamic-routing - NAT and LB reachable over several
paths
>>> use ECMP])
>>> +AT_KEYWORDS([dynamic-routing ecmp])
>>> +ovn_start
>>> +
>>> +# lr0 reaches lr1 over the switches "a" and "b" and redistributes
>>> NAT and LB
>>> +# addresses on both ports. The NAT and the LB of lr1 then
produce one
>>> +# forwarding route per path on lr0. The two routes of each ECMP
>>> group share
>>> +# their source row and only differ in the nexthop and output
port,
>>> they must
>>> +# still get distinct ids that don't depend on the processing
order.
>>> +
>>> +check ovn-nbctl lr-add lr0 -- set Logical_Router lr0 \
>>> + options:dynamic-routing=true options:chassis=hv1
>>> +check ovn-nbctl lr-add lr1 -- set Logical_Router lr1
>>> options:chassis=hv1
>>> +i=0
>>> +for s in a b; do
>>> + i=$((i + 1))
>>> + check ovn-nbctl ls-add $s
>>> + check ovn-nbctl lrp-add lr0 lr0-$s 00:00:00:00:0$i:01
10.0.$i.1/24
>>> + check ovn-nbctl lrp-set-options lr0-$s dynamic-routing-
>>> redistribute=lb,nat
>>> + check ovn-nbctl lsp-add-router-port $s $s-lr0 lr0-$s
>>> + check ovn-nbctl lrp-add lr1 lr1-$s 00:00:00:00:0$i:02
10.0.$i.2/24
>>> + check ovn-nbctl lsp-add-router-port $s $s-lr1 lr1-$s
>>> +done
>>> +check ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.16.1.10
192.168.1.10
>>> +check ovn-nbctl lb-add lb1 172.16.1.20:80
<http://172.16.1.20:80> 192.168.1.20:80 <http://192.168.1.20:80>
>>> +check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
>>> +
>>> +ovn-sbctl dump-flows lr0 > lr0flows
>>> +AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | grep
"172\.16\.1\."
>>> | ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1935 ,
match=(ip4.dst ==
>>> 172.16.1.10/32 <http://172.16.1.10/32>), action=(ip.ttl--;
flags.loopback = 1; reg8[[0..15]]
>>> = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> + table=??(lr_in_ip_routing ), priority=1935 ,
match=(ip4.dst ==
>>> 172.16.1.20/32 <http://172.16.1.20/32>), action=(ip.ttl--;
flags.loopback = 1; reg8[[0..15]]
>>> = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> +])
>>> +AT_CHECK([grep "lr_in_ip_routing_ecmp" lr0flows | grep "reg0 =
>>> 10\.0\." | ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.1.2; reg5 = 10.0.1.1; eth.src =
>>> 00:00:00:00:01:01; outport = "lr0-a"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.1.2; reg5 = 10.0.1.1; eth.src =
>>> 00:00:00:00:01:01; outport = "lr0-a"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.2.2; reg5 = 10.0.2.1; eth.src =
>>> 00:00:00:00:02:01; outport = "lr0-b"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.2.2; reg5 = 10.0.2.1; eth.src =
>>> 00:00:00:00:02:01; outport = "lr0-b"; reg9[[9]] = 1; next;)
>>> +])
>>> +
>>> +dnl Both members of each group come from the same NAT or LB row.
>>> +nat=$(fetch_column nb:NAT _uuid external_ip=172.16.1.10)
>>> +lb=$(fetch_column nb:Load_Balancer _uuid name=lb1)
>>> +check_row_count Logical_Flow 2 external_ids:stage-
>>> name=lr_in_ip_routing_ecmp \
>>> + external_ids:stage-hint=${nat:0:8}
>>> +check_row_count Logical_Flow 2 external_ids:stage-
>>> name=lr_in_ip_routing_ecmp \
>>> + external_ids:stage-hint=${lb:0:8}
>>> +
>>> +dnl ecmp_ids also checks that the members have distinct ids.
>>> +nat_ids=$(ecmp_ids lr0 172.16.1.10/32 <http://172.16.1.10/32>)
>>> +lb_ids=$(ecmp_ids lr0 172.16.1.20/32 <http://172.16.1.20/32>)
>>> +AT_CHECK([test -n "$nat_ids" && test -n "$lb_ids"])
>>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>>> +
>>> +dnl A third path adds a member to each group without changing the
>>> ids of the
>>> +dnl existing members.
>>> +check ovn-nbctl ls-add c
>>> +check ovn-nbctl lrp-add lr0 lr0-c 00:00:00:00:03:01
10.0.3.1/24 <http://10.0.3.1/24>
>>> +check ovn-nbctl lrp-set-options lr0-c dynamic-routing-
>>> redistribute=lb,nat
>>> +check ovn-nbctl lsp-add-router-port c c-lr0 lr0-c
>>> +check ovn-nbctl lrp-add lr1 lr1-c 00:00:00:00:03:02
10.0.3.2/24 <http://10.0.3.2/24>
>>> +check ovn-nbctl --wait=sb lsp-add-router-port c c-lr1 lr1-c
>>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>>> +
>>> +for old in "$nat_ids 172.16.1.10/32 <http://172.16.1.10/32>"
"$lb_ids 172.16.1.20/32 <http://172.16.1.20/32>"; do
>>> + set -- $old
>>> + new=$(ecmp_ids lr0 $3)
>>> + AT_CHECK([test "${new%% *}" = "$1"])
>>> + AT_CHECK([echo "${new#* }" | tr , '\n' | wc -l], [0], [3
>>> +])
>>> + for m in $(echo $2 | tr , ' '); do
>>> + AT_CHECK([echo "${new#* }" | tr , '\n' | grep -qx $m])
>>> + done
>>> +done
>>> +
>>> +OVN_CLEANUP_NORTHD
>>> +AT_CLEANUP
>>> +])
>>> +
>>> OVN_FOR_EACH_NORTHD_NO_HV([
>>> AT_SETUP([dynamic-routing - LB redistribute uses backend LSP as
>>> tracked_port])
>>> AT_KEYWORDS([dynamic-routing])
>>> @@ -24092,6 +24171,87 @@ done
>>> OVN_CLEANUP_NORTHD
>>> AT_CLEANUP
>>> +OVN_FOR_EACH_NORTHD_NO_HV([
>>> +AT_SETUP([Static routes - ECMP ids stable across recompute])
>>> +AT_KEYWORDS([ecmp])
>>> +ovn_start
>>> +
>>> +check ovn-nbctl lr-add lr0
>>> +for i in 1 2 3 4; do
>>> + check ovn-nbctl lrp-add lr0 lr0-sw$i 00:00:00:00:0$i:01 10.0.
>>> $i.1/24
>>> +done
>>> +check ovn-nbctl --wait=sb sync
>>> +
>>> +dnl Add the routes in "reverse" order, the ids must not depend
on it.
>>> +for p in 192.168.30.0/24 <http://192.168.30.0/24>
192.168.20.0/24 <http://192.168.20.0/24> 192.168.10.0/24
<http://192.168.10.0/24>; do
>>> + for i in 3 2 1; do
>>> + check ovn-nbctl --wait=sb --ecmp lr-route-add lr0 $p
10.0.$i.10
>>> + done
>>> +done
>>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>>> +
>>> +AT_CHECK([ovn-sbctl dump-flows lr0 | grep -w lr_in_ip_routing
| grep
>>> select | ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>,
<mid>);)
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.20.0/24 <http://192.168.20.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>,
<mid>);)
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.30.0/24 <http://192.168.30.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>,
<mid>);)
>>> +])
>>> +g10=$(ecmp_ids lr0 192.168.10.0/24 <http://192.168.10.0/24>)
>>> +g20=$(ecmp_ids lr0 192.168.20.0/24 <http://192.168.20.0/24>)
>>> +g30=$(ecmp_ids lr0 192.168.30.0/24 <http://192.168.30.0/24>)
>>> +AT_CHECK([test -n "$g10" && test -n "$g20" && test -n "$g30"])
>>> +AT_CHECK([printf '%s\n' "$g10" "$g20" "$g30" | cut -d' ' -f1 |
sort
>>> - u | wc -l], [0], [3
>>> +])
>>> +
>>> +dnl Remove a member from the middle of a group with more than 2
>>> members, the
>>> +dnl ids of the other members and groups must not change.
>>> +m2=$(ecmp_member_id lr0 192.168.10.0/24
<http://192.168.10.0/24> 10.0.2.10)
>>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>>> +check ovn-nbctl --wait=sb lr-route-del lr0 192.168.10.0/24
<http://192.168.10.0/24> 10.0.2.10
>>> +check_engine_compute group_ecmp_route incremental
>>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>>> +g10_ids=$(echo "${g10#* }" | tr , '\n' | grep -vx "$m2" |
paste -sd, -)
>>> +AT_CHECK_UNQUOTED([ecmp_ids lr0 192.168.10.0/24
<http://192.168.10.0/24>], [0], [${g10%% *}
>>> $g10_ids
>>> +])
>>> +AT_CHECK_UNQUOTED([ecmp_ids lr0 192.168.20.0/24
<http://192.168.20.0/24>], [0], [$g20
>>> +])
>>> +AT_CHECK_UNQUOTED([ecmp_ids lr0 192.168.30.0/24
<http://192.168.30.0/24>], [0], [$g30
>>> +])
>>> +g10=$(ecmp_ids lr0 192.168.10.0/24 <http://192.168.10.0/24>)
>>> +
>>> +dnl Remove a whole group and then add a new one, the ids of the
>>> other groups
>>> +dnl must not change and the group ids must stay unique.
>>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>>> +check ovn-nbctl --wait=sb lr-route-del lr0 192.168.20.0/24
<http://192.168.20.0/24>
>>> +check ovn-nbctl --wait=sb --ecmp lr-route-add lr0
192.168.40.0/24 <http://192.168.40.0/24>
>>> 10.0.1.10
>>> +check ovn-nbctl --wait=sb --ecmp lr-route-add lr0
192.168.40.0/24 <http://192.168.40.0/24>
>>> 10.0.4.10
>>> +check_engine_compute group_ecmp_route incremental
>>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>>> +AT_CHECK_UNQUOTED([ecmp_ids lr0 192.168.10.0/24
<http://192.168.10.0/24>], [0], [$g10
>>> +])
>>> +AT_CHECK_UNQUOTED([ecmp_ids lr0 192.168.30.0/24
<http://192.168.30.0/24>], [0], [$g30
>>> +])
>>> +g40=$(ecmp_ids lr0 192.168.40.0/24 <http://192.168.40.0/24>)
>>> +AT_CHECK([test -n "$g40"])
>>> +AT_CHECK([printf '%s\n' "$g10" "$g30" "$g40" | cut -d' ' -f1 |
sort
>>> - u | wc -l], [0], [3
>>> +])
>>> +
>>> +dnl Once no member is a discard route anymore the group must stop
>>> dropping.
>>> +route=$(ovn-nbctl --bare --columns _uuid find
>>> Logical_Router_Static_Route \
>>> + ip_prefix="192.168.30.0/24 <http://192.168.30.0/24>"
nexthop="10.0.3.10")
>>> +check ovn-nbctl --wait=sb set Logical_Router_Static_Route $route
>>> nexthop=discard
>>> +AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_ip_routing_ecmp |
>>> grep -c "drop;"], [0], [4
>>> +])
>>> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>>> +check ovn-nbctl --wait=sb set Logical_Router_Static_Route $route
>>> nexthop=10.0.3.10
>>> +check_engine_compute group_ecmp_route incremental
>>> +AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_ip_routing_ecmp |
>>> grep -c "drop;"], [0], [1
>>> +])
>>> +CHECK_NO_CHANGE_AFTER_RECOMPUTE
>>> +
>>> +OVN_CLEANUP_NORTHD
>>> +AT_CLEANUP
>>> +])
>>> +
>>> OVN_FOR_EACH_NORTHD_NO_HV([
>>> AT_SETUP([Static routes - ECMP with discard])
>>> ovn_start
>>> @@ -24106,14 +24266,14 @@ check ovn-
>>> nbctl \
>>> check ovn-nbctl --wait=sb sync
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_lflows], [0], [dnl
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> +AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> ])
>>> -AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows | sed -E 's/==
>>> [[1-9]]+\)/== \?)/' | ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows |
ovn_strip_ecmp_ids
>>> | ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(reg0 =
>>> 10.0.10.10; reg5 = 10.0.10.1; eth.src = 00:00:00:00:10:01;
outport =
>>> "lr0-sw1"; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(reg0 =
>>> 10.0.20.10; reg5 = 10.0.20.1; eth.src = 00:00:00:00:20:01;
outport =
>>> "lr0-sw2"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.10.10; reg5 = 10.0.10.1; eth.src =
>>> 00:00:00:00:10:01; outport = "lr0-sw1"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.20.10; reg5 = 10.0.20.1; eth.src =
>>> 00:00:00:00:20:01; outport = "lr0-sw2"; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -24124,14 +24284,14 @@ ecmp2=$(fetch_column
>>> nb:Logical_Router_Static_Route _uuid nexthop="10.0.20.10")
>>> check ovn-nbctl --wait=sb set Logical_Router_Static_Route $ecmp1
>>> nexthop="discard"
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_lflows], [0], [dnl
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> +AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> ])
>>> -AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows | sed -E 's/==
>>> [[1-9]]+\)/== \?)/' | ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows |
ovn_strip_ecmp_ids
>>> | ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(drop;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(drop;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -24139,14 +24299,14 @@ AT_CHECK([grep 'lr_in_ip_routing_ecmp'
>>> lr0flows | sed -E 's/== [[1-9]]+\)/== \?)
>>> check ovn-nbctl --wait=sb set Logical_Router_Static_Route $ecmp2
>>> nexthop="discard"
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_lflows], [0], [dnl
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> +AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> ])
>>> -AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows | sed -E 's/==
>>> [[1-9]]+\)/== \?)/' | ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows |
ovn_strip_ecmp_ids
>>> | ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(drop;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(drop;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(drop;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>> @@ -24155,14 +24315,14 @@ check ovn-nbctl set
>>> Logical_Router_Static_Route $ecmp1 nexthop="10.0.10.10"
>>> check ovn-nbctl --wait=sb set Logical_Router_Static_Route $ecmp2
>>> nexthop="10.0.20.10"
>>> ovn-sbctl dump-flows lr0 > lr0flows
>>> -AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_lflows], [0], [dnl
>>> - table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>>> +AT_CHECK([grep 'lr_in_ip_routing' lr0flows | grep 'select' |
>>> ovn_strip_ecmp_ids | ovn_strip_lflows], [0], [dnl
>>> + table=??(lr_in_ip_routing ), priority=1840 , match=(reg7
== 0 &&
>>> ip4.dst == 192.168.10.0/24 <http://192.168.10.0/24>),
action=(ip.ttl--; flags.loopback = 1;
>>> reg8[[0..15]] = <gid>; reg8[[16..31]] = select(<mid>, <mid>);)
>>> ])
>>> -AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows | sed -E 's/==
>>> [[1-9]]+\)/== \?)/' | ovn_strip_lflows], [0], [dnl
>>> +AT_CHECK([grep 'lr_in_ip_routing_ecmp' lr0flows |
ovn_strip_ecmp_ids
>>> | ovn_strip_lflows], [0], [dnl
>>> table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
>>> action=(drop;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(reg0 =
>>> 10.0.10.10; reg5 = 10.0.10.1; eth.src = 00:00:00:00:10:01;
outport =
>>> "lr0-sw1"; reg9[[9]] = 1; next;)
>>> - table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ?), action=(reg0 =
>>> 10.0.20.10; reg5 = 10.0.20.1; eth.src = 00:00:00:00:20:01;
outport =
>>> "lr0-sw2"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.10.10; reg5 = 10.0.10.1; eth.src =
>>> 00:00:00:00:10:01; outport = "lr0-sw1"; reg9[[9]] = 1; next;)
>>> + table=??(lr_in_ip_routing_ecmp), priority=100 ,
>>> match=(reg8[[0..15]] == <gid> && reg8[[16..31]] == <mid>),
>>> action=(reg0 = 10.0.20.10; reg5 = 10.0.20.1; eth.src =
>>> 00:00:00:00:20:01; outport = "lr0-sw2"; reg9[[9]] = 1; next;)
>>> table=??(lr_in_ip_routing_ecmp), priority=150 ,
>>> match=(reg8[[0..15]] == 0), action=(next;)
>>> ])
>>
>
/‘Esta mensagem é direcionada apenas para os endereços constantes no
cabeçalho inicial. Se você não está listado nos endereços constantes no
cabeçalho, pedimos-lhe que desconsidere completamente o conteúdo dessa
mensagem e cuja cópia, encaminhamento e/ou execução das ações citadas
estão imediatamente anuladas e proibidas’./
///‘Apesar do Magazine Luiza tomar todas as precauções razoáveis para
assegurar que nenhum vírus esteja presente nesse e-mail, a empresa não
poderá aceitar a responsabilidade por quaisquer perdas ou danos causados
por esse e-mail ou por seus anexos’./