Hi Martin, Felix,

On 12/4/24 4:10 PM, Martin Kalcok wrote:
> From: Felix Huettner <[email protected]>
> 
> In most cases IPv4 packets are routed only over other IPv4 networks and
> IPv6 packets are routed only over IPv6 networks. However there is no
> inherent reason for this limitation. Routing IPv4 packets over IPv6
> networks just requires the router to contain a route for an IPv4 network
> with an IPv6 nexthop.
> 
> This was previously prevented in OVN in ovn-nbctl and northd. By
> removing these filters the forwarding will work if the mac addresses are
> prepopulated.
> 
> If the mac addresses are not prepopulated we will attempt to resolve them 
> using
> the original address family of the packet and not the address family of the
> nexthop. This will fail and we will not forward the packet.
> 
> This feature can for example be used by service providers to
> interconnect multiple IPv4 networks of a customer without needing to
> negotiate free IPv4 addresses by just using any IPv6 address.
> 
> Signed-off-by: Felix Huettner <[email protected]>

Felix, is this email address correct?

> Signed-off-by: Martin Kalcok <[email protected]>
> Co-authored-by: Martin Kalcok <[email protected]>
> ---

After merging Felix's BGP prerequisite patches, there were a bunch of
conflicts when applying this patch but I think I managed to address them.

I pushed the rebased patches (also with the small nits I had below
addressed) to:
https://github.com/dceara/ovn/commits/bcba1b74

It would be great if you could double check that it still looks OK to
you.  If that's the case I can then apply the series to main.

>  NEWS                  |   4 +
>  northd/northd.c       |  81 +++---
>  northd/northd.h       |   2 +
>  tests/ovn-nbctl.at    |  26 +-
>  tests/ovn-northd.at   | 150 +++++++---
>  tests/ovn.at          | 645 ++++++++++++++++++++++++++++++++++++++++++
>  utilities/ovn-nbctl.c |  12 +-
>  7 files changed, 829 insertions(+), 91 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index da3aba739..b5aae7d4b 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -75,6 +75,10 @@ OVN v24.09.0 - 13 Sep 2024
>      "routing-protocol-redirect" and "routing-protocols", that allow
>      redirection of routing protocol traffic received by a router port
>      to a different logical switch port.
> +  - Allow Static Routes where the address families of ip_prefix and nexthop
> +    diverge (e.g. IPv4 packets over IPv6 links). This is currently limited to
> +    nexthops that have their mac addresses prepopulated (so
> +    dynamic_neigh_routers must be false).
>  
>  OVN v24.03.0 - 01 Mar 2024
>  --------------------------
> diff --git a/northd/northd.c b/northd/northd.c
> index 3564d45d9..4fb48838b 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -174,6 +174,7 @@ BUILD_ASSERT_DECL(ACL_OBS_STAGE_MAX < (1 << 2));
>  #define REGBIT_KNOWN_LB_SESSION "reg9[6]"
>  #define REGBIT_DHCP_RELAY_REQ_CHK "reg9[7]"
>  #define REGBIT_DHCP_RELAY_RESP_CHK "reg9[8]"
> +#define REGBIT_NEXTHOP_IS_IPV4    "reg9[9]"
>  
>  /* Register to store the eth address associated to a router port for packets
>   * received in S_ROUTER_IN_ADMISSION.
> @@ -290,7 +291,8 @@ BUILD_ASSERT_DECL(ACL_OBS_STAGE_MAX < (1 << 2));
>   * |     |   LOOKUP_NEIGHBOR_RESULT/ |   |                 |
>   * |     |   SKIP_LOOKUP_NEIGHBOR/   |   |                 |
>   * |     |REGBIT_DHCP_RELAY_REQ_CHK/ |   |                 |
> - * |     |REGBIT_DHCP_RELAY_RESP_CHK}|   |                 |
> + * |     |REGBIT_DHCP_RELAY_RESP_CHK |   |                 |
> + * |     |REGBIT_NEXTHOP_IS_IPV4}    |   |                 |
>   * |     |                           |   |                 |
>   * |     | REG_ORIG_TP_DPORT_ROUTER  |   |                 |
>   * |     |                           |   |                 |
> @@ -10861,13 +10863,15 @@ build_routing_policy_flow(struct lflow_table 
> *lflows, struct ovn_datapath *od,
>                        "outport = %s; "
>                        "flags.loopback = 1; "
>                        REG_ECMP_GROUP_ID" = 0; "
> +                      REGBIT_NEXTHOP_IS_IPV4" = %d; "
>                        "next;",
>                        is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6,
>                        nexthop,
>                        is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6,
>                        lrp_addr_s,
>                        out_port->lrp_networks.ea_s,
> -                      out_port->json_key);
> +                      out_port->json_key,
> +                      is_ipv4);
>  
>      } else if (!strcmp(rule->action, "drop")) {
>          ds_put_cstr(&actions, debug_drop_action());
> @@ -10951,13 +10955,15 @@ build_ecmp_routing_policy_flows(struct lflow_table 
> *lflows,
>                        "eth.src = %s; "
>                        "outport = %s; "
>                        "flags.loopback = 1; "
> +                      REGBIT_NEXTHOP_IS_IPV4" = %d; "
>                        "next;",
>                        is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6,
>                        rp->valid_nexthops[i],
>                        is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6,
>                        lrp_addr_s,
>                        out_port->lrp_networks.ea_s,
> -                      out_port->json_key);
> +                      out_port->json_key,
> +                      is_ipv4);

Should we add a TODO item to remember to add support for "reroute"
routing policies that change next hop family?

>  
>          ds_clear(&match);
>          ds_put_format(&match, REG_ECMP_GROUP_ID" == %"PRIu16" && "
> @@ -11123,6 +11129,8 @@ parsed_routes_add(struct ovn_datapath *od, const 
> struct hmap *lr_ports,
>      /* Verify that the next hop is an IP address with an all-ones mask. */
>      struct in6_addr nexthop;
>      unsigned int plen;
> +    bool is_ipv4_nexthop = true;
> +    bool is_ipv4_prefix;
>      bool is_discard_route = !strcmp(route->nexthop, "discard");
>      bool valid_nexthop = route->nexthop[0] && !is_discard_route;
>      if (valid_nexthop) {
> @@ -11141,6 +11149,7 @@ parsed_routes_add(struct ovn_datapath *od, const 
> struct hmap *lr_ports,
>                           UUID_ARGS(&route->header_.uuid));
>              return;
>          }
> +        is_ipv4_nexthop = IN6_IS_ADDR_V4MAPPED(&nexthop);
>      }
>  
>      /* Parse ip_prefix */
> @@ -11152,18 +11161,7 @@ parsed_routes_add(struct ovn_datapath *od, const 
> struct hmap *lr_ports,
>                       UUID_ARGS(&route->header_.uuid));
>          return;
>      }
> -
> -    /* Verify that ip_prefix and nexthop have same address familiy. */
> -    if (valid_nexthop) {
> -        if (IN6_IS_ADDR_V4MAPPED(&prefix) != IN6_IS_ADDR_V4MAPPED(&nexthop)) 
> {
> -            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -            VLOG_WARN_RL(&rl, "Address family doesn't match between 
> 'ip_prefix'"
> -                         " %s and 'nexthop' %s in static route "UUID_FMT,
> -                         route->ip_prefix, route->nexthop,
> -                         UUID_ARGS(&route->header_.uuid));
> -            return;
> -        }
> -    }
> +    is_ipv4_prefix = IN6_IS_ADDR_V4MAPPED(&prefix);
>  
>      /* Verify that ip_prefix and nexthop are on the same network. */
>      if (!is_discard_route &&
> @@ -11216,6 +11214,8 @@ parsed_routes_add(struct ovn_datapath *od, const 
> struct hmap *lr_ports,
>                                                   "ecmp_symmetric_reply",
>                                                   false);
>      new_pr->is_discard_route = is_discard_route;
> +    new_pr->is_ipv4_prefix = is_ipv4_prefix;
> +    new_pr->is_ipv4_nexthop = is_ipv4_nexthop;
>      sset_init(&new_pr->ecmp_selection_fields);
>  
>      /* If tp_src or tp_dst is included in the selection_fields, implicitly
> @@ -11646,7 +11646,7 @@ build_ecmp_route_flow(struct lflow_table *lflows, 
> struct ovn_datapath *od,
>                        struct lflow_ref *lflow_ref, const char *protocol)
>  
>  {
> -    bool is_ipv4 = IN6_IS_ADDR_V4MAPPED(&eg->prefix);
> +    bool is_ipv4_prefix = IN6_IS_ADDR_V4MAPPED(&eg->prefix);
>      uint16_t priority;
>      struct ecmp_route_list_node *er;
>      struct ds route_match = DS_EMPTY_INITIALIZER;
> @@ -11655,7 +11655,8 @@ build_ecmp_route_flow(struct lflow_table *lflows, 
> struct ovn_datapath *od,
>      int ofs = !strcmp(eg->origin, ROUTE_ORIGIN_CONNECTED) ?
>          ROUTE_PRIO_OFFSET_CONNECTED: ROUTE_PRIO_OFFSET_STATIC;
>      build_route_match(NULL, eg->route_table_id, prefix_s, eg->plen,
> -                      eg->is_src_route, is_ipv4, &route_match, &priority, 
> ofs,
> +                      eg->is_src_route, is_ipv4_prefix, &route_match,
> +                      &priority, ofs,
>                        protocol != NULL);
>      free(prefix_s);
>  
> @@ -11723,7 +11724,8 @@ build_ecmp_route_flow(struct lflow_table *lflows, 
> struct ovn_datapath *od,
>          /* Find the outgoing port. */
>          const char *lrp_addr_s = NULL;
>          struct ovn_port *out_port = NULL;
> -        if (!find_static_route_outport(od, lr_ports, route, is_ipv4,
> +        if (!find_static_route_outport(od, lr_ports, route,
> +                                       route_->is_ipv4_nexthop,
>                                         &lrp_addr_s, &out_port)) {
>              continue;
>          }
> @@ -11746,13 +11748,16 @@ build_ecmp_route_flow(struct lflow_table *lflows, 
> struct ovn_datapath *od,
>                        "%s = %s; "
>                        "eth.src = %s; "
>                        "outport = %s; "
> +                      REGBIT_NEXTHOP_IS_IPV4" = %d; "
>                        "next;",
> -                      is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6,
> +                      route_->is_ipv4_nexthop ?
> +                          REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6,
>                        route->nexthop,
> -                      is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6,
> +                      route_->is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
>                        lrp_addr_s,
>                        out_port->lrp_networks.ea_s,
> -                      out_port->json_key);
> +                      out_port->json_key,
> +                      route_->is_ipv4_nexthop);
>          ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 100,
>                                  ds_cstr(&match), ds_cstr(&actions),
>                                  &route->header_, lflow_ref);
> @@ -11770,15 +11775,15 @@ add_route(struct lflow_table *lflows, struct 
> ovn_datapath *od,
>            bool is_src_route, const uint32_t rtb_id,
>            const struct sset *bfd_ports,
>            const struct ovsdb_idl_row *stage_hint, bool is_discard_route,
> -          int ofs, struct lflow_ref *lflow_ref)
> +          int ofs, struct lflow_ref *lflow_ref,
> +          bool is_ipv4_prefix, bool is_ipv4_nexthop)
>  {
> -    bool is_ipv4 = strchr(network_s, '.') ? true : false;
>      struct ds match = DS_EMPTY_INITIALIZER;
>      uint16_t priority;
>      const struct ovn_port *op_inport = NULL;
>  
>      /* IPv6 link-local addresses must be scoped to the local router port. */
> -    if (!is_ipv4) {
> +    if (!is_ipv4_prefix) {
>          struct in6_addr network;
>          ovs_assert(ipv6_parse(network_s, &network));
>          if (in6_is_lla(&network)) {
> @@ -11786,7 +11791,7 @@ add_route(struct lflow_table *lflows, struct 
> ovn_datapath *od,
>          }
>      }
>      build_route_match(op_inport, rtb_id, network_s, plen, is_src_route,
> -                      is_ipv4, &match, &priority, ofs, false);
> +                      is_ipv4_prefix, &match, &priority, ofs, false);
>  
>      struct ds common_actions = DS_EMPTY_INITIALIZER;
>      struct ds actions = DS_EMPTY_INITIALIZER;
> @@ -11794,22 +11799,25 @@ add_route(struct lflow_table *lflows, struct 
> ovn_datapath *od,
>          ds_put_cstr(&actions, debug_drop_action());
>      } else {
>          ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ",
> -                      is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6);
> +                      is_ipv4_nexthop ? REG_NEXT_HOP_IPV4 : 
> REG_NEXT_HOP_IPV6);
>          if (gateway && gateway[0]) {
>              ds_put_cstr(&common_actions, gateway);
>          } else {
> -            ds_put_format(&common_actions, "ip%s.dst", is_ipv4 ? "4" : "6");
> +            ds_put_format(&common_actions, "ip%s.dst",
> +                          is_ipv4_prefix ? "4" : "6");
>          }
>          ds_put_format(&common_actions, "; "
>                        "%s = %s; "
>                        "eth.src = %s; "
>                        "outport = %s; "
>                        "flags.loopback = 1; "
> +                      REGBIT_NEXTHOP_IS_IPV4" = %d; "
>                        "next;",
> -                      is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6,
> +                      is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
>                        lrp_addr_s,
>                        op->lrp_networks.ea_s,
> -                      op->json_key);
> +                      op->json_key,
> +                      is_ipv4_nexthop);
>          ds_put_format(&actions, "ip.ttl--; %s", ds_cstr(&common_actions));
>      }
>  
> @@ -11860,7 +11868,8 @@ build_static_route_flow(struct lflow_table *lflows, 
> struct ovn_datapath *od,
>                lrp_addr_s, prefix_s, route_->plen, route->nexthop,
>                route_->is_src_route, route_->route_table_id,
>                bfd_ports, &route->header_, route_->is_discard_route,
> -              ofs, lflow_ref);
> +              ofs, lflow_ref,
> +              route_->is_ipv4_prefix, route_->is_ipv4_nexthop);
>  
>      free(prefix_s);
>  }
> @@ -13708,7 +13717,7 @@ build_ip_routing_flows_for_lrp(struct ovn_port *op,
>                    op->lrp_networks.ipv4_addrs[i].network_s,
>                    op->lrp_networks.ipv4_addrs[i].plen, NULL, false, 0,
>                    bfd_ports, &op->nbrp->header_, false,
> -                  ROUTE_PRIO_OFFSET_CONNECTED, lflow_ref);
> +                  ROUTE_PRIO_OFFSET_CONNECTED, lflow_ref, true, true);
>      }
>  
>      for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> @@ -13716,7 +13725,7 @@ build_ip_routing_flows_for_lrp(struct ovn_port *op,
>                    op->lrp_networks.ipv6_addrs[i].network_s,
>                    op->lrp_networks.ipv6_addrs[i].plen, NULL, false, 0,
>                    bfd_ports, &op->nbrp->header_, false,
> -                  ROUTE_PRIO_OFFSET_CONNECTED, lflow_ref);
> +                  ROUTE_PRIO_OFFSET_CONNECTED, lflow_ref, false, false);
>      }
>  }
>  
> @@ -14069,11 +14078,13 @@ build_arp_resolve_flows_for_lrouter(
>                    "ip4.mcast || ip6.mcast", "next;",
>                    lflow_ref);
>  
> -    ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 1, "ip4",
> +    ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 1,
> +                  REGBIT_NEXTHOP_IS_IPV4 " == 1",
>                    "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;",
>                    lflow_ref);
>  
> -    ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 1, "ip6",
> +    ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 1,
> +                  REGBIT_NEXTHOP_IS_IPV4 " == 0",
>                    "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;",
>                    lflow_ref);
>  
> @@ -17033,7 +17044,7 @@ build_routable_flows_for_router_port(
>                                laddrs->ipv4_addrs[k].plen, NULL, false, 0,
>                                bfd_ports, &router_port->nbrp->header_,
>                                false, ROUTE_PRIO_OFFSET_CONNECTED,
> -                              lrp->stateful_lflow_ref);
> +                              lrp->stateful_lflow_ref, true, true);
>                  }
>              }
>          }
> diff --git a/northd/northd.h b/northd/northd.h
> index e93e10f20..a5af72eb7 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -709,6 +709,8 @@ struct parsed_route {
>      const struct nbrec_logical_router *nbr;
>      bool stale;
>      struct sset ecmp_selection_fields;
> +    bool is_ipv4_prefix;
> +    bool is_ipv4_nexthop;
>  };
>  
>  void ovnnb_db_run(struct northd_input *input_data,
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 2efa13b93..addcc4593 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -1857,7 +1857,7 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 
> 11.0.0.2])
>  AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.10.0/24 lp0])
>  AT_CHECK([ovn-nbctl --bfd lr-route-add lr0 10.0.20.0/24 11.0.2.1 lp0])
>  AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.10.0/24 lp1], [1], [],
> -  [ovn-nbctl: bad IPv4 nexthop argument: lp1
> +  [ovn-nbctl: bad nexthop argument: lp1
>  ])
>  
>  dnl Add overlapping route with 10.0.0.1/24
> @@ -1871,13 +1871,13 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24a 
> 11.0.0.1], [1], [],
>    [ovn-nbctl: bad prefix argument: 10.0.0.111/24a
>  ])
>  AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1a], [1], [],
> -  [ovn-nbctl: bad IPv4 nexthop argument: 11.0.0.1a
> +  [ovn-nbctl: bad nexthop argument: 11.0.0.1a
>  ])
>  AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1/24], [1], [],
> -  [ovn-nbctl: bad IPv4 nexthop argument: 11.0.0.1/24
> +  [ovn-nbctl: bad nexthop argument: 11.0.0.1/24
>  ])
>  AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 
> 2001:0db8:0:f103::1/64], [1], [],
> -  [ovn-nbctl: bad IPv6 nexthop argument: 2001:0db8:0:f103::1/64
> +  [ovn-nbctl: bad nexthop argument: 2001:0db8:0:f103::1/64
>  ])
>  AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 20.0.0.0/24 discard], [1], [],
>    [ovn-nbctl: ecmp is not valid for discard routes.
> @@ -2105,6 +2105,24 @@ check ovn-nbctl lr-route-del lr0
>  AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
>  ])
>  
> +dnl Check IPv4 over v6 and IPv6 over v4 routes
> +AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 2001:0db8:0:f103::10])
> +AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 11.0.1.10])
> +
> +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
> +IPv4 Routes
> +Route Table <main>:
> +              10.0.0.0/24       2001:db8:0:f103::10 dst-ip
> +
> +IPv6 Routes
> +Route Table <main>:
> +            2001:db8::/64                 11.0.1.10 dst-ip
> +])
> +
> +check ovn-nbctl lr-route-del lr0
> +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
> +])
> +
>  dnl Check IPv4 routes in route table
>  check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 0.0.0.0/0 192.168.0.1
>  check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index c98f1fcb4..803823afa 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -3442,8 +3442,8 @@ AT_CHECK([grep "lr_in_policy" lr0flows3 | 
> ovn_strip_lflows], [0], [dnl
>    table=??(lr_in_policy       ), priority=0    , match=(1), 
> action=(reg8[[0..15]] = 0; next;)
>    table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.3), action=(reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
>    table=??(lr_in_policy_ecmp  ), priority=0    , match=(1), action=(drop;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 1 
> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 172.168.0.100; 
> eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; 
> next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 1 
> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 172.168.0.100; 
> eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; 
> next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 1 
> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 172.168.0.100; 
> eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; 
> reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 1 
> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 172.168.0.100; 
> eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; 
> reg9[[9]] = 1; next;)
>    table=??(lr_in_policy_ecmp  ), priority=150  , match=(reg8[[0..15]] == 0), 
> action=(next;)
>  ])
>  
> @@ -3458,11 +3458,11 @@ sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == 
> <cleared>/' | ovn_strip_lf
>    table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.3), action=(reg8[[0..15]] = <cleared>; reg8[[16..31]] = select(1, 2);)
>    table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.4), action=(reg8[[0..15]] = <cleared>; reg8[[16..31]] = select(1, 2, 
> 3);)
>    table=??(lr_in_policy_ecmp  ), priority=0    , match=(1), action=(drop;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
>    table=??(lr_in_policy_ecmp  ), priority=150  , match=(reg8[[0..15]] == 
> <cleared>), action=(next;)
>  ])
>  
> @@ -3476,13 +3476,13 @@ sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == 
> <cleared>/' | ovn_strip_lf
>    table=??(lr_in_policy       ), priority=0    , match=(1), 
> action=(reg8[[0..15]] = <cleared>; next;)
>    table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.3), action=(reg8[[0..15]] = <cleared>; reg8[[16..31]] = select(1, 2);)
>    table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.4), action=(reg8[[0..15]] = <cleared>; reg8[[16..31]] = select(1, 2, 
> 3);)
> -  table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.5), action=(reg0 = 172.168.0.110; reg5 = 172.168.0.100; eth.src = 
> 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] 
> = <cleared>; next;)
> +  table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.5), action=(reg0 = 172.168.0.110; reg5 = 172.168.0.100; eth.src = 
> 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] 
> = <cleared>; reg9[[9]] = 1; next;)
>    table=??(lr_in_policy_ecmp  ), priority=0    , match=(1), action=(drop;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
>    table=??(lr_in_policy_ecmp  ), priority=150  , match=(reg8[[0..15]] == 
> <cleared>), action=(next;)
>  ])
>  
> @@ -3495,11 +3495,11 @@ sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = 
> <cleared>/' | \
>  sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == <cleared>/' | 
> ovn_strip_lflows], [0], [dnl
>    table=??(lr_in_policy       ), priority=0    , match=(1), 
> action=(reg8[[0..15]] = <cleared>; next;)
>    table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.4), action=(reg8[[0..15]] = <cleared>; reg8[[16..31]] = select(1, 2, 
> 3);)
> -  table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.5), action=(reg0 = 172.168.0.110; reg5 = 172.168.0.100; eth.src = 
> 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] 
> = <cleared>; next;)
> +  table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.5), action=(reg0 = 172.168.0.110; reg5 = 172.168.0.100; eth.src = 
> 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] 
> = <cleared>; reg9[[9]] = 1; next;)
>    table=??(lr_in_policy_ecmp  ), priority=0    , match=(1), action=(drop;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_policy_ecmp  ), priority=100  , match=(reg8[[0..15]] == 
> <cleared> && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg5 = 
> 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
>    table=??(lr_in_policy_ecmp  ), priority=150  , match=(reg8[[0..15]] == 
> <cleared>), action=(next;)
>  ])
>  
> @@ -3511,7 +3511,7 @@ AT_CHECK([grep "lr_in_policy" lr0flows3 |  \
>  sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = <cleared>/' | \
>  sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == <cleared>/' | 
> ovn_strip_lflows], [0], [dnl
>    table=??(lr_in_policy       ), priority=0    , match=(1), 
> action=(reg8[[0..15]] = <cleared>; next;)
> -  table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.5), action=(reg0 = 172.168.0.110; reg5 = 172.168.0.100; eth.src = 
> 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] 
> = <cleared>; next;)
> +  table=??(lr_in_policy       ), priority=10   , match=(ip4.src == 
> 10.0.0.5), action=(reg0 = 172.168.0.110; reg5 = 172.168.0.100; eth.src = 
> 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] 
> = <cleared>; reg9[[9]] = 1; next;)
>    table=??(lr_in_policy_ecmp  ), priority=0    , match=(1), action=(drop;)
>    table=??(lr_in_policy_ecmp  ), priority=150  , match=(reg8[[0..15]] == 
> <cleared>), action=(next;)
>  ])
> @@ -6823,14 +6823,14 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | 
> 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), 
> 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=124  , match=(ip4.dst == 
> 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; next;)
> +  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 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=162  , match=(reg7 == 0 && ip4.dst 
> == 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=324  , 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; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , 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
>    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"; next;)
> +  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; next;)
>    table=??(lr_in_ip_routing_ecmp), priority=150  , match=(reg8[[0..15]] == 
> 0), action=(next;)
>  ])
>  
> @@ -6841,14 +6841,14 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | 
> 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), 
> 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=124  , match=(ip4.dst == 
> 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; next;)
> +  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 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=162  , match=(reg7 == 0 && ip4.dst 
> == 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=324  , 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; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , 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.??/' | 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"; 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"; next;)
> +  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; 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; next;)
>    table=??(lr_in_ip_routing_ecmp), priority=150  , match=(reg8[[0..15]] == 
> 0), action=(next;)
>  ])
>  
> @@ -6870,14 +6870,14 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | 
> 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), 
> 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=124  , match=(ip4.dst == 
> 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; next;)
> +  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 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=162  , match=(reg7 == 0 && ip4.dst 
> == 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=324  , 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; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , 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.??/' | 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"; 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"; next;)
> +  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; 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; next;)
>    table=??(lr_in_ip_routing_ecmp), priority=150  , match=(reg8[[0..15]] == 
> 0), action=(next;)
>  ])
>  
> @@ -6888,14 +6888,14 @@ check ovn-nbctl --wait=sb lr-route-add lr0 1.0.0.0/24 
> 192.168.0.10
>  ovn-sbctl dump-flows lr0 > lr0flows
>  
>  AT_CHECK([grep -e "lr_in_ip_routing.*192.168.0.10" lr0flows | 
> ovn_strip_lflows], [0], [dnl
> -  table=??(lr_in_ip_routing   ), priority=122  , match=(reg7 == 0 && ip4.dst 
> == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; 
> reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=122  , match=(reg7 == 0 && ip4.dst 
> == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; 
> reg5 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
>  ])
>  
>  check ovn-nbctl --wait=sb lr-route-add lr0 2.0.0.0/24 lr0-public
>  
>  ovn-sbctl dump-flows lr0 > lr0flows
>  AT_CHECK([grep -e "lr_in_ip_routing.*2.0.0.0" lr0flows | ovn_strip_lflows], 
> [0], [dnl
> -  table=??(lr_in_ip_routing   ), priority=122  , match=(reg7 == 0 && ip4.dst 
> == 2.0.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; next;)
> +  table=??(lr_in_ip_routing   ), priority=122  , match=(reg7 == 0 && ip4.dst 
> == 2.0.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;)
>  ])
>  
>  check ovn-nbctl lr-route-add lr0 3.3.0.0/16 192.168.0.11
> @@ -6928,6 +6928,66 @@ AT_CHECK([grep -e "(lr_in_ip_routing   ).*3.3.0.0" 
> lr0flows | sed 's/table=../ta
>  AT_CLEANUP
>  ])
>  
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([ovn -- static routes multiple address families flows])
> +AT_KEYWORDS([static-routes-flows])
> +ovn_start
> +
> +check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set logical_router lr0 options:chassis=ch1
> +check ovn-nbctl ls-add public
> +check ovn-nbctl ls-add private
> +check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 192.168.0.1/24
> +check ovn-nbctl lsp-add public public-lr0
> +check ovn-nbctl lsp-set-type public-lr0 router
> +check ovn-nbctl lsp-set-addresses public-lr0 router
> +check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
> +
> +check ovn-nbctl lrp-add lr0 lr0-private 00:00:20:20:12:14 2001:db8::1/64
> +check ovn-nbctl lsp-add private private-lr0
> +check ovn-nbctl lsp-set-type private-lr0 router
> +check ovn-nbctl lsp-set-addresses private-lr0 router
> +check ovn-nbctl lsp-set-options private-lr0 router-port=lr0-private
> +
> +check ovn-nbctl --wait=sb lr-route-add lr0 10.0.0.0/24 192.168.0.10
> +check ovn-nbctl --wait=sb lr-route-add lr0 11.0.0.0/24 2001:db8::10
> +check ovn-nbctl --wait=sb lr-route-add lr0 2001:db8:1::/64 192.168.0.20
> +check ovn-nbctl --wait=sb lr-route-add lr0 2001:db8:2::/64 2001:db8::20
> +
> +ovn-sbctl dump-flows lr0 > lr0flows
> +AT_CHECK([grep -e "lr_in_ip_routing " lr0flows | 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=122  , match=(reg7 == 0 && ip4.dst 
> == 10.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; 
> 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=122  , match=(reg7 == 0 && ip4.dst 
> == 11.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = 2001:db8::10; 
> xxreg1 = 2001:db8::1; eth.src = 00:00:20:20:12:14; outport = "lr0-private"; 
> flags.loopback = 1; reg9[[9]] = 0; next;)
> +  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 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=322  , match=(reg7 == 0 && ip6.dst 
> == 2001:db8:1::/64), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 
> 192.168.0.20; 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=322  , match=(reg7 == 0 && ip6.dst 
> == 2001:db8:2::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = 
> 2001:db8::20; xxreg1 = 2001:db8::1; eth.src = 00:00:20:20:12:14; outport = 
> "lr0-private"; flags.loopback = 1; reg9[[9]] = 0; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , match=(inport == 
> "lr0-private" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; 
> xxreg0 = ip6.dst; xxreg1 = fe80::200:20ff:fe20:1214; eth.src = 
> 00:00:20:20:12:14; outport = "lr0-private"; flags.loopback = 1; reg9[[9]] = 
> 0; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , 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;)
> +  table=??(lr_in_ip_routing   ), priority=324  , match=(ip6.dst == 
> 2001:db8::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 
> = 2001:db8::1; eth.src = 00:00:20:20:12:14; outport = "lr0-private"; 
> flags.loopback = 1; reg9[[9]] = 0; next;)
> +])
> +
> +AT_CHECK([grep -e "lr_in_arp_resolve" lr0flows | ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_arp_resolve  ), priority=0    , match=(1), action=(drop;)
> +  table=??(lr_in_arp_resolve  ), priority=1    , match=(reg9[[9]] == 0), 
> action=(get_nd(outport, xxreg0); next;)
> +  table=??(lr_in_arp_resolve  ), priority=1    , match=(reg9[[9]] == 1), 
> action=(get_arp(outport, reg0); next;)
> +  table=??(lr_in_arp_resolve  ), priority=500  , match=(ip4.mcast || 
> ip6.mcast), action=(next;)
> +])
> +
> +AT_CHECK([grep -e "lr_in_arp_request" lr0flows | ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_arp_request  ), priority=0    , match=(1), action=(output;)
> +  table=??(lr_in_arp_request  ), priority=100  , match=(eth.dst == 
> 00:00:00:00:00:00 && ip4), action=(arp { eth.dst = ff:ff:ff:ff:ff:ff; arp.spa 
> = reg5; arp.tpa = reg0; arp.op = 1; output; }; output;)
> +  table=??(lr_in_arp_request  ), priority=100  , match=(eth.dst == 
> 00:00:00:00:00:00 && ip6), action=(nd_ns { nd.target = xxreg0; output; }; 
> output;)
> +  table=??(lr_in_arp_request  ), priority=200  , match=(eth.dst == 
> 00:00:00:00:00:00 && ip6 && xxreg0 == 2001:db8::10), action=(nd_ns { eth.dst 
> = 33:33:ff:00:00:10; ip6.dst = ff02::1:ff00:10; nd.target = 2001:db8::10; 
> output; }; output;)
> +  table=??(lr_in_arp_request  ), priority=200  , match=(eth.dst == 
> 00:00:00:00:00:00 && ip6 && xxreg0 == 2001:db8::20), action=(nd_ns { eth.dst 
> = 33:33:ff:00:00:20; ip6.dst = ff02::1:ff00:20; nd.target = 2001:db8::20; 
> output; }; output;)
> +])
> +
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD_NO_HV([
>  AT_SETUP([ovn-northd -- lr multiple gw ports])
>  AT_KEYWORDS([multiple-l3dgw-ports])
> @@ -7346,16 +7406,16 @@ AT_CHECK([grep "lr_in_ip_routing_pre" lr0flows | 
> ovn_strip_lflows], [0], [dnl
>  grep -e "(lr_in_ip_routing   ).*outport" lr0flows
>  
>  AT_CHECK([grep -e "(lr_in_ip_routing   ).*outport" lr0flows | 
> ovn_strip_lflows], [0], [dnl
> -  table=??(lr_in_ip_routing   ), priority=122  , match=(reg7 == 1 && ip4.dst 
> == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.1.10; 
> reg5 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 = 
> 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 
> 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 192.168.1.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 = 
> 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 
> 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 192.168.2.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 = 
> 192.168.2.1; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 
> 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=162  , match=(reg7 == 2 && ip4.dst 
> == 1.1.1.1/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.20; 
> reg5 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=2    , match=(reg7 == 0 && ip4.dst 
> == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg5 
> = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback 
> = 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=2    , match=(reg7 == 2 && ip4.dst 
> == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg5 
> = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback 
> = 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=324  , match=(inport == "lrp0" && 
> ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
> xxreg1 = fe80::200:ff:fe00:1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; 
> flags.loopback = 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=324  , match=(inport == "lrp1" && 
> ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
> xxreg1 = fe80::200:ff:fe00:101; eth.src = 00:00:00:00:01:01; outport = 
> "lrp1"; flags.loopback = 1; next;)
> -  table=??(lr_in_ip_routing   ), priority=324  , match=(inport == "lrp2" && 
> ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
> xxreg1 = fe80::200:ff:fe00:201; eth.src = 00:00:00:00:02:01; outport = 
> "lrp2"; flags.loopback = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=122  , match=(reg7 == 1 && ip4.dst 
> == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.1.10; 
> reg5 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 = 
> 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 
> 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 192.168.1.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 = 
> 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 
> 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=124  , match=(ip4.dst == 
> 192.168.2.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 = 
> 192.168.2.1; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 
> 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=162  , match=(reg7 == 2 && ip4.dst 
> == 1.1.1.1/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.20; 
> reg5 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; 
> flags.loopback = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=2    , match=(reg7 == 0 && ip4.dst 
> == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg5 
> = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback 
> = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=2    , match=(reg7 == 2 && ip4.dst 
> == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg5 
> = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback 
> = 1; reg9[[9]] = 1; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , match=(inport == "lrp0" && 
> ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
> xxreg1 = fe80::200:ff:fe00:1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; 
> flags.loopback = 1; reg9[[9]] = 0; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , match=(inport == "lrp1" && 
> ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
> xxreg1 = fe80::200:ff:fe00:101; eth.src = 00:00:00:00:01:01; outport = 
> "lrp1"; flags.loopback = 1; reg9[[9]] = 0; next;)
> +  table=??(lr_in_ip_routing   ), priority=324  , match=(inport == "lrp2" && 
> ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
> xxreg1 = fe80::200:ff:fe00:201; eth.src = 00:00:00:00:02:01; outport = 
> "lrp2"; flags.loopback = 1; reg9[[9]] = 0; next;)
>  ])
>  
>  AT_CLEANUP
> @@ -13450,8 +13510,8 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | 
> ovn_strip_lflows], [0], [dnl
>  
>  AT_CHECK([grep "lr_in_arp_resolve" lr0flows | ovn_strip_lflows], [0], [dnl
>    table=??(lr_in_arp_resolve  ), priority=0    , match=(1), action=(drop;)
> -  table=??(lr_in_arp_resolve  ), priority=1    , match=(ip4), 
> action=(get_arp(outport, reg0); next;)
> -  table=??(lr_in_arp_resolve  ), priority=1    , match=(ip6), 
> action=(get_nd(outport, xxreg0); next;)
> +  table=??(lr_in_arp_resolve  ), priority=1    , match=(reg9[[9]] == 0), 
> action=(get_nd(outport, xxreg0); next;)
> +  table=??(lr_in_arp_resolve  ), priority=1    , match=(reg9[[9]] == 1), 
> action=(get_arp(outport, reg0); next;)
>    table=??(lr_in_arp_resolve  ), priority=100  , match=(outport == 
> "lr0-public" && reg0 == 172.168.0.100), action=(eth.dst = 00:00:00:00:ff:02; 
> next;)
>    table=??(lr_in_arp_resolve  ), priority=100  , match=(outport == 
> "lr0-public" && reg0 == 172.168.0.110), action=(eth.dst = 30:54:00:00:00:03; 
> next;)
>    table=??(lr_in_arp_resolve  ), priority=100  , match=(outport == 
> "lr0-public" && reg0 == 172.168.0.120), action=(eth.dst = 00:00:00:00:ff:02; 
> next;)
> @@ -13620,8 +13680,8 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | 
> ovn_strip_lflows], [0], [dnl
>  
>  AT_CHECK([grep "lr_in_arp_resolve" lr0flows | ovn_strip_lflows], [0], [dnl
>    table=??(lr_in_arp_resolve  ), priority=0    , match=(1), action=(drop;)
> -  table=??(lr_in_arp_resolve  ), priority=1    , match=(ip4), 
> action=(get_arp(outport, reg0); next;)
> -  table=??(lr_in_arp_resolve  ), priority=1    , match=(ip6), 
> action=(get_nd(outport, xxreg0); next;)
> +  table=??(lr_in_arp_resolve  ), priority=1    , match=(reg9[[9]] == 0), 
> action=(get_nd(outport, xxreg0); next;)
> +  table=??(lr_in_arp_resolve  ), priority=1    , match=(reg9[[9]] == 1), 
> action=(get_arp(outport, reg0); next;)
>    table=??(lr_in_arp_resolve  ), priority=100  , match=(outport == 
> "lr0-public" && reg0 == 172.168.0.100), action=(eth.dst = 00:00:00:00:ff:02; 
> next;)
>    table=??(lr_in_arp_resolve  ), priority=100  , match=(outport == 
> "lr0-public" && reg0 == 172.168.0.110), action=(eth.dst = 00:00:00:00:ff:02; 
> next;)
>    table=??(lr_in_arp_resolve  ), priority=100  , match=(outport == 
> "lr0-public" && reg0 == 172.168.0.120), action=(eth.dst = 00:00:00:00:ff:02; 
> next;)
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 1071208d1..ec90a3b4e 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -39761,3 +39761,648 @@ OVN_CLEANUP([hv1])
>  
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([2 HVs, 2 LS, 1 lport/LS, 2 peer LRs, IPv4 over IPv6])
> +AT_SKIP_IF([test $HAVE_SCAPY = no])
> +ovn_start
> +
> +# Logical network:
> +# Two LRs - R1 and R2 that are connected to each other as peers in 
> 2001:db8::/64
> +# network. R1 has a switchs ls1 (192.168.1.0/24) connected to it.
> +# R2 has ls2 (172.16.1.0/24) connected to it.
> +
> +ls1_lp1_mac="f0:00:00:01:02:03"
> +rp_ls1_mac="00:00:00:01:02:03"
> +rp_ls2_mac="00:00:00:01:02:04"
> +ls2_lp1_mac="f0:00:00:01:02:04"
> +
> +ls1_lp1_ip="192.168.1.2"
> +ls2_lp1_ip="172.16.1.2"
> +
> +check ovn-nbctl lr-add R1
> +check ovn-nbctl lr-add R2
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +check ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 192.168.1.1/24
> +
> +check ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 
> type=router \
> +  options:router-port=ls1 addresses=\"$rp_ls1_mac\"
> +
> +# Connect ls2 to R2
> +check ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 172.16.1.1/24
> +
> +check ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 
> type=router \
> +  options:router-port=ls2 addresses=\"$rp_ls2_mac\"
> +
> +# Connect R1 to R2
> +check ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 2001:db8::1/64 peer=R2_R1
> +check ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 2001:db8::2/64 peer=R1_R2
> +
> +AT_CHECK([ovn-nbctl lr-route-add R1 "0.0.0.0/0" 2001:db8::2])
> +AT_CHECK([ovn-nbctl lr-route-add R2 "0.0.0.0/0" 2001:db8::1])
> +
> +# Create logical port ls1-lp1 in ls1
> +check ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip"
> +
> +# Create logical port ls2-lp1 in ls2
> +check ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip"
> +
> +# Create two hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +sim_add hv2
> +as hv2
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +check ovs-vsctl -- add-port br-int hv2-vif1 -- \
> +    set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv2/vif1-tx.pcap \
> +    options:rxq_pcap=hv2/vif1-rx.pcap \
> +    ofport-request=1
> +
> +
> +# Pre-populate the hypervisors' ARP tables so that we don't lose any
> +# packets for ARP resolution (native tunneling doesn't queue packets
> +# for ARP resolution).
> +OVN_POPULATE_ARP
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +# Packet to send.
> +packet=$(fmt_pkt "Ether(dst='${rp_ls1_mac}', src='${ls1_lp1_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=64)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# Packet to Expect
> +# The TTL should be decremented by 2.
> +expected=$(fmt_pkt "Ether(dst='${ls2_lp1_mac}', src='${rp_ls2_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=62)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +echo ${expected} > expected
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [1
> +])
> +
> +# Disable the ls2-lp1 port.
> +check ovn-nbctl --wait=hv set logical_switch_port ls2-lp1 enabled=false
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [0
> +])
> +
> +# Send the same packet again and it should not be delivered
> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# The 2nd packet sent shound not be received.
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +OVN_CLEANUP([hv1],[hv2])
> +
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv4 over IPv6])
> +AT_SKIP_IF([test $HAVE_SCAPY = no])
> +ovn_start
> +
> +# Logical network:
> +# Two LRs - R1 and R2 that are connected to ls-transfer in 2001:db8::/64
> +# network. R1 has a switchs ls1 (192.168.1.0/24) connected to it.
> +# R2 has ls2 (172.16.1.0/24) connected to it.
> +
> +ls1_lp1_mac="f0:00:00:01:02:03"
> +rp_ls1_mac="00:00:00:01:02:03"
> +rp_ls2_mac="00:00:00:01:02:04"
> +ls2_lp1_mac="f0:00:00:01:02:04"
> +
> +ls1_lp1_ip="192.168.1.2"
> +ls2_lp1_ip="172.16.1.2"
> +
> +check ovn-nbctl lr-add R1
> +check ovn-nbctl lr-add R2
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl ls-add ls2
> +check ovn-nbctl ls-add ls-transfer
> +
> +# Connect ls1 to R1
> +check ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 192.168.1.1/24
> +
> +check ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 
> type=router \
> +  options:router-port=ls1 addresses=\"$rp_ls1_mac\"
> +
> +# Connect ls2 to R2
> +check ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 172.16.1.1/24
> +
> +check ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 
> type=router \
> +  options:router-port=ls2 addresses=\"$rp_ls2_mac\"
> +
> +# Connect R1 to R2
> +check ovn-nbctl lrp-add R1 R1_ls-transfer 00:00:00:02:03:04 2001:db8::1/64
> +check ovn-nbctl lrp-add R2 R2_ls-transfer 00:00:00:02:03:05 2001:db8::2/64
> +
> +check ovn-nbctl lsp-add ls-transfer ls-transfer_r1 -- \
> +  set Logical_Switch_Port ls-transfer_r1 type=router \
> +  options:router-port=R1_ls-transfer addresses=\"router\"
> +check ovn-nbctl lsp-add ls-transfer ls-transfer_r2 -- \
> +  set Logical_Switch_Port ls-transfer_r2 type=router \
> +  options:router-port=R2_ls-transfer addresses=\"router\"
> +
> +AT_CHECK([ovn-nbctl lr-route-add R1 "0.0.0.0/0" 2001:db8::2])
> +AT_CHECK([ovn-nbctl lr-route-add R2 "0.0.0.0/0" 2001:db8::1])
> +
> +# Create logical port ls1-lp1 in ls1
> +check ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip"
> +
> +# Create logical port ls2-lp1 in ls2
> +check ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip"
> +
> +# Create two hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +sim_add hv2
> +as hv2
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +check ovs-vsctl -- add-port br-int hv2-vif1 -- \
> +    set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv2/vif1-tx.pcap \
> +    options:rxq_pcap=hv2/vif1-rx.pcap \
> +    ofport-request=1
> +
> +
> +# Pre-populate the hypervisors' ARP tables so that we don't lose any
> +# packets for ARP resolution (native tunneling doesn't queue packets
> +# for ARP resolution).
> +OVN_POPULATE_ARP
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +# Packet to send.
> +packet=$(fmt_pkt "Ether(dst='${rp_ls1_mac}', src='${ls1_lp1_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=64)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# Packet to Expect
> +# The TTL should be decremented by 2.
> +expected=$(fmt_pkt "Ether(dst='${ls2_lp1_mac}', src='${rp_ls2_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=62)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +echo ${expected} > expected
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [1
> +])
> +
> +# Disable the ls2-lp1 port.
> +check ovn-nbctl --wait=hv set logical_switch_port ls2-lp1 enabled=false
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [0
> +])
> +
> +# Send the same packet again and it should not be delivered
> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# The 2nd packet sent shound not be received.
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +OVN_CLEANUP([hv1],[hv2])
> +
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv4 over IPv6, 
> static mac])
> +AT_SKIP_IF([test $HAVE_SCAPY = no])
> +ovn_start
> +
> +# Logical network:
> +# Two LRs - R1 and R2 that are connected to ls-transfer in 2001:db8::/64
> +# network. R1 has a switchs ls1 (192.168.1.0/24) connected to it.
> +# R2 has ls2 (172.16.1.0/24) connected to it.
> +
> +ls1_lp1_mac="f0:00:00:01:02:03"
> +rp_ls1_mac="00:00:00:01:02:03"
> +rp_ls2_mac="00:00:00:01:02:04"
> +ls2_lp1_mac="f0:00:00:01:02:04"
> +
> +ls1_lp1_ip="192.168.1.2"
> +ls2_lp1_ip="172.16.1.2"
> +
> +check ovn-nbctl lr-add R1
> +check ovn-nbctl lr-add R2
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl ls-add ls2
> +check ovn-nbctl ls-add ls-transfer
> +
> +# Connect ls1 to R1
> +check ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 192.168.1.1/24
> +check ovn-nbctl set Logical_Router R1 options:dynamic_neigh_routers=true
> +
> +check ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 
> type=router \
> +  options:router-port=ls1 addresses=\"$rp_ls1_mac\"
> +
> +# Connect ls2 to R2
> +check ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 172.16.1.1/24
> +check ovn-nbctl set Logical_Router R2 options:dynamic_neigh_routers=true
> +
> +check ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 
> type=router \
> +  options:router-port=ls2 addresses=\"$rp_ls2_mac\"
> +
> +# Connect R1 to R2
> +check ovn-nbctl lrp-add R1 R1_ls-transfer 00:00:00:02:03:04 2001:db8::1/64
> +check ovn-nbctl lrp-add R2 R2_ls-transfer 00:00:00:02:03:05 2001:db8::2/64
> +
> +check ovn-nbctl lsp-add ls-transfer ls-transfer_r1 -- \
> +  set Logical_Switch_Port ls-transfer_r1 type=router \
> +  options:router-port=R1_ls-transfer addresses=\"router\"
> +check ovn-nbctl lsp-add ls-transfer ls-transfer_r2 -- \
> +  set Logical_Switch_Port ls-transfer_r2 type=router \
> +  options:router-port=R2_ls-transfer addresses=\"router\"
> +
> +# Static mac binding entries
> +check ovn-nbctl static-mac-binding-add R1_ls-transfer 2001:db8::2 
> 00:00:00:02:03:05
> +check ovn-nbctl static-mac-binding-add R2_ls-transfer 2001:db8::1 
> 00:00:00:02:03:04
> +
> +AT_CHECK([ovn-nbctl lr-route-add R1 "0.0.0.0/0" 2001:db8::2])
> +AT_CHECK([ovn-nbctl lr-route-add R2 "0.0.0.0/0" 2001:db8::1])
> +
> +# Create logical port ls1-lp1 in ls1
> +check ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip"
> +
> +# Create logical port ls2-lp1 in ls2
> +check ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip"
> +
> +# Create two hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +sim_add hv2
> +as hv2
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +check ovs-vsctl -- add-port br-int hv2-vif1 -- \
> +    set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv2/vif1-tx.pcap \
> +    options:rxq_pcap=hv2/vif1-rx.pcap \
> +    ofport-request=1
> +
> +
> +# Pre-populate the hypervisors' ARP tables so that we don't lose any
> +# packets for ARP resolution (native tunneling doesn't queue packets
> +# for ARP resolution).
> +OVN_POPULATE_ARP
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +# Packet to send.
> +packet=$(fmt_pkt "Ether(dst='${rp_ls1_mac}', src='${ls1_lp1_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=64)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# Packet to Expect
> +# The TTL should be decremented by 2.
> +expected=$(fmt_pkt "Ether(dst='${ls2_lp1_mac}', src='${rp_ls2_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=62)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +echo ${expected} > expected
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [1
> +])
> +
> +# Disable the ls2-lp1 port.
> +check ovn-nbctl --wait=hv set logical_switch_port ls2-lp1 enabled=false
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [0
> +])
> +
> +# Send the same packet again and it should not be delivered
> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# The 2nd packet sent shound not be received.
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +OVN_CLEANUP([hv1],[hv2])
> +
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv4 over IPv6, 
> ECMP])
> +AT_SKIP_IF([test $HAVE_SCAPY = no])
> +ovn_start
> +
> +# Logical network:
> +# Two LRs - R1 and R2 that are connected to ls-transfer1 and lr-transfer2 in
> +# 2001:db8:1::/64 and 2001:db8:2::/64
> +# network. R1 has a switchs ls1 (192.168.1.0/24) connected to it.
> +# R2 has ls2 (172.16.1.0/24) connected to it.
> +
> +ls1_lp1_mac="f0:00:00:01:02:03"
> +rp_ls1_mac="00:00:00:01:02:03"
> +rp_ls2_mac="00:00:00:01:02:04"
> +ls2_lp1_mac="f0:00:00:01:02:04"
> +
> +ls1_lp1_ip="192.168.1.2"
> +ls2_lp1_ip="172.16.1.2"
> +
> +check ovn-nbctl lr-add R1
> +check ovn-nbctl lr-add R2
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl ls-add ls2
> +check ovn-nbctl ls-add ls-transfer1
> +check ovn-nbctl ls-add ls-transfer2
> +
> +# Connect ls1 to R1
> +check ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 192.168.1.1/24
> +
> +check ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 
> type=router \
> +  options:router-port=ls1 addresses=\"$rp_ls1_mac\"
> +
> +# Connect ls2 to R2
> +check ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 172.16.1.1/24
> +
> +check ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 
> type=router \
> +  options:router-port=ls2 addresses=\"$rp_ls2_mac\"
> +
> +# Connect R1 to R2 (ls-transfer1)
> +check ovn-nbctl lrp-add R1 R1_ls-transfer1 00:00:00:02:03:04 2001:db8:1::1/64
> +check ovn-nbctl lrp-add R2 R2_ls-transfer1 00:00:00:02:03:05 2001:db8:1::2/64
> +
> +check ovn-nbctl lsp-add ls-transfer1 ls-transfer1_r1 -- \
> +  set Logical_Switch_Port ls-transfer1_r1 type=router \
> +  options:router-port=R1_ls-transfer1 addresses=\"router\"
> +check ovn-nbctl lsp-add ls-transfer1 ls-transfer1_r2 -- \
> +  set Logical_Switch_Port ls-transfer1_r2 type=router \
> +  options:router-port=R2_ls-transfer1 addresses=\"router\"
> +
> +# Connect R1 to R2 (ls-transfer2)
> +check ovn-nbctl lrp-add R1 R1_ls-transfer2 00:00:00:02:03:14 2001:db8:2::1/64
> +check ovn-nbctl lrp-add R2 R2_ls-transfer2 00:00:00:02:03:15 2001:db8:2::2/64
> +
> +check ovn-nbctl lsp-add ls-transfer2 ls-transfer2_r1 -- \
> +  set Logical_Switch_Port ls-transfer2_r1 type=router \
> +  options:router-port=R1_ls-transfer2 addresses=\"router\"
> +check ovn-nbctl lsp-add ls-transfer2 ls-transfer2_r2 -- \
> +  set Logical_Switch_Port ls-transfer2_r2 type=router \
> +  options:router-port=R2_ls-transfer2 addresses=\"router\"
> +
> +AT_CHECK([ovn-nbctl lr-route-add R1 "0.0.0.0/0" 2001:db8:1::2])
> +AT_CHECK([ovn-nbctl --ecmp lr-route-add R1 "0.0.0.0/0" 2001:db8:2::2])
> +AT_CHECK([ovn-nbctl lr-route-add R2 "0.0.0.0/0" 2001:db8:1::1])
> +AT_CHECK([ovn-nbctl --ecmp lr-route-add R2 "0.0.0.0/0" 2001:db8:2::1])
> +
> +# Create logical port ls1-lp1 in ls1
> +check ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip"
> +
> +# Create logical port ls2-lp1 in ls2
> +check ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip"
> +
> +# Create two hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +sim_add hv2
> +as hv2
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +check ovs-vsctl -- add-port br-int hv2-vif1 -- \
> +    set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv2/vif1-tx.pcap \
> +    options:rxq_pcap=hv2/vif1-rx.pcap \
> +    ofport-request=1
> +
> +
> +# Pre-populate the hypervisors' ARP tables so that we don't lose any
> +# packets for ARP resolution (native tunneling doesn't queue packets
> +# for ARP resolution).
> +OVN_POPULATE_ARP
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +# Packet to send.
> +packet=$(fmt_pkt "Ether(dst='${rp_ls1_mac}', src='${ls1_lp1_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=64)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# Packet to Expect
> +# The TTL should be decremented by 2.
> +expected=$(fmt_pkt "Ether(dst='${ls2_lp1_mac}', src='${rp_ls2_mac}')/ \
> +                        IP(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> ttl=62)/ \
> +                        UDP(sport=53, dport=4369)")

Nit: indentation.

> +echo ${expected} > expected
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [1
> +])
> +
> +# Disable the ls2-lp1 port.
> +check ovn-nbctl --wait=hv set logical_switch_port ls2-lp1 enabled=false
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "reg0 == 172.16.1.2" | wc -l], [0], [0
> +])
> +
> +# Send the same packet again and it should not be delivered
> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# The 2nd packet sent shound not be received.
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +OVN_CLEANUP([hv1],[hv2])
> +
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([2 HVs, 2 LS, 1 lport/LS, 2 peer LRs, IPv6 over IPv4])
> +AT_SKIP_IF([test $HAVE_SCAPY = no])
> +ovn_start
> +
> +# Logical network:
> +# Two LRs - R1 and R2 that are connected to each other as peers in 
> 10.0.0.0/24
> +# network. R1 has a switchs ls1 (2001:db8:1::/64) connected to it.
> +# R2 has ls2 (2001:db8:2::/64) connected to it.
> +
> +ls1_lp1_mac="f0:00:00:01:02:03"
> +rp_ls1_mac="00:00:00:01:02:03"
> +rp_ls2_mac="00:00:00:01:02:04"
> +ls2_lp1_mac="f0:00:00:01:02:04"
> +
> +ls1_lp1_ip="2001:db8:1::2"
> +ls2_lp1_ip="2001:db8:2::2"
> +
> +check ovn-nbctl lr-add R1
> +check ovn-nbctl lr-add R2
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +check ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 2001:db8:1::1/64
> +
> +check ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 
> type=router \
> +  options:router-port=ls1 addresses=\"$rp_ls1_mac\"
> +
> +# Connect ls2 to R2
> +check ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 2001:db8:2::1/64
> +
> +check ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 
> type=router \
> +  options:router-port=ls2 addresses=\"$rp_ls2_mac\"
> +
> +# Connect R1 to R2
> +check ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 10.0.0.1/24 peer=R2_R1
> +check ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 10.0.0.2/24 peer=R1_R2
> +
> +AT_CHECK([ovn-nbctl lr-route-add R1 "::/0" 10.0.0.2])
> +AT_CHECK([ovn-nbctl lr-route-add R2 "::/0" 10.0.0.1])
> +
> +# Create logical port ls1-lp1 in ls1
> +check ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip"
> +
> +# Create logical port ls2-lp1 in ls2
> +check ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip"
> +
> +# Create two hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +sim_add hv2
> +as hv2
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +check ovs-vsctl -- add-port br-int hv2-vif1 -- \
> +    set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv2/vif1-tx.pcap \
> +    options:rxq_pcap=hv2/vif1-rx.pcap \
> +    ofport-request=1
> +
> +
> +# Pre-populate the hypervisors' ARP tables so that we don't lose any
> +# packets for ARP resolution (native tunneling doesn't queue packets
> +# for ARP resolution).
> +OVN_POPULATE_ARP
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +# Packet to send.
> +packet=$(fmt_pkt "Ether(dst='${rp_ls1_mac}', src='${ls1_lp1_mac}')/ \
> +                        IPv6(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> hlim=64)/ \
> +                        UDP(sport=53, dport=4369)")
> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# Packet to Expect
> +# The TTL should be decremented by 2.
> +expected=$(fmt_pkt "Ether(dst='${ls2_lp1_mac}', src='${rp_ls2_mac}')/ \
> +                        IPv6(src='${ls1_lp1_ip}', dst='${ls2_lp1_ip}', 
> hlim=62)/ \
> +                        UDP(sport=53, dport=4369)")
> +echo ${expected} > expected
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "xxreg0 == 2001:db8:2::2" | wc -l], [0], [1
> +])
> +
> +# Disable the ls2-lp1 port.
> +check ovn-nbctl --wait=hv set logical_switch_port ls2-lp1 enabled=false
> +
> +AT_CHECK([ovn-sbctl dump-flows | grep lr_in_arp_resolve | \
> +grep "xxreg0 == 2001:db8:2::2" | wc -l], [0], [0
> +])
> +
> +# Send the same packet again and it should not be delivered
> +check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 "$packet"
> +
> +# The 2nd packet sent shound not be received.
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +OVN_CLEANUP([hv1],[hv2])
> +
> +AT_CLEANUP
> +])
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index d45be75c7..86a3b5966 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -4591,11 +4591,9 @@ nbctl_lr_route_add(struct ctl_context *ctx)
>      }
>  
>      char *route_table = shash_find_data(&ctx->options, "--route-table");
> -    bool v6_prefix = false;
>      prefix = normalize_ipv4_prefix_str(ctx->argv[2]);
>      if (!prefix) {
>          prefix = normalize_ipv6_prefix_str(ctx->argv[2]);
> -        v6_prefix = true;
>      }
>      if (!prefix) {
>          ctl_error(ctx, "bad prefix argument: %s", ctx->argv[2]);
> @@ -4606,15 +4604,15 @@ nbctl_lr_route_add(struct ctl_context *ctx)
>      if (is_discard_route) {
>          next_hop = xasprintf("discard");
>      } else {
> -        next_hop = v6_prefix
> -            ? normalize_ipv6_addr_str(ctx->argv[3])
> -            : normalize_ipv4_addr_str(ctx->argv[3]);
> +        next_hop = normalize_ipv4_addr_str(ctx->argv[3]);
> +        if (!next_hop) {
> +            next_hop = normalize_ipv6_addr_str(ctx->argv[3]);
> +        }
>          if (!next_hop) {
>              /* check if it is a output port. */
>              error = lrp_by_name_or_uuid(ctx, ctx->argv[3], true, &out_lrp);
>              if (error) {
> -                ctl_error(ctx, "bad %s nexthop argument: %s",
> -                          v6_prefix ? "IPv6" : "IPv4", ctx->argv[3]);
> +                ctl_error(ctx, "bad nexthop argument: %s", ctx->argv[3]);
>                  free(error);
>                  goto cleanup;
>              }

Regards,
Dumitru

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to