+
+ If the Gateway router in the OVN Northbound database has been
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index 5a2018c2e..4e406c594 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -8573,10 +8573,16 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
+ return true;
+ }
+
++enum lb_snat_type {
++ NO_FORCE_SNAT,
++ FORCE_SNAT,
++ SKIP_SNAT,
++};
++
+ static void
+ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
+ struct ds *match, struct ds *actions, int priority,
+- bool force_snat_for_lb, struct ovn_lb_vip *lb_vip,
++ enum lb_snat_type snat_type, struct ovn_lb_vip *lb_vip,
+ const char *proto, struct nbrec_load_balancer *lb,
+ struct shash *meter_groups, struct sset *nat_entries)
+ {
+@@ -8585,9 +8591,10 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
+
+ /* A match and actions for new connections. */
+ char *new_match = xasprintf("ct.new && %s", ds_cstr(match));
+- if (force_snat_for_lb) {
+- char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
+- ds_cstr(actions));
++ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
++ char *new_actions = xasprintf("flags.%s_snat_for_lb = 1; %s",
++ snat_type == SKIP_SNAT ? "skip" : "force",
++ ds_cstr(actions));
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
+ new_match, new_actions, &lb->header_);
+ free(new_actions);
+@@ -8598,11 +8605,12 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
+
+ /* A match and actions for established connections. */
+ char *est_match = xasprintf("ct.est && %s", ds_cstr(match));
+- if (force_snat_for_lb) {
++ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
++ char *est_actions = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
++ snat_type == SKIP_SNAT ? "skip" : "force");
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
+- est_match,
+- "flags.force_snat_for_lb = 1; ct_dnat;",
+- &lb->header_);
++ est_match, est_actions, &lb->header_);
++ free(est_actions);
+ } else {
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
+ est_match, "ct_dnat;", &lb->header_);
+@@ -8675,11 +8683,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
+ ds_put_format(&undnat_match, ") && outport == %s && "
+ "is_chassis_resident(%s)", od->l3dgw_port->json_key,
+ od->l3redirect_port->json_key);
+- if (force_snat_for_lb) {
++ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
++ char *action = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
++ snat_type == SKIP_SNAT ? "skip" : "force");
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
+- ds_cstr(&undnat_match),
+- "flags.force_snat_for_lb = 1; ct_dnat;",
++ ds_cstr(&undnat_match), action,
+ &lb->header_);
++ free(action);
+ } else {
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
+ ds_cstr(&undnat_match), "ct_dnat;",
+@@ -8689,6 +8699,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
+ ds_destroy(&undnat_match);
+ }
+
++static void
++build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od,
++ struct hmap *lbs, struct shash *meter_groups,
++ struct sset *nat_entries, struct ds *match,
++ struct ds *actions)
++{
++ /* A set to hold all ips that need defragmentation and tracking. */
++ struct sset all_ips = SSET_INITIALIZER(&all_ips);
++ bool lb_force_snat_ip =
++ !lport_addresses_is_empty(&od->lb_force_snat_addrs);
++
++ for (int i = 0; i < od->nbr->n_load_balancer; i++) {
++ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
++ struct ovn_northd_lb *lb =
++ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
++ ovs_assert(lb);
++
++ bool lb_skip_snat = smap_get_bool(&nb_lb->options, "skip_snat", false);
++ if (lb_skip_snat) {
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120,
++ "flags.skip_snat_for_lb == 1 && ip", "next;");
++ }
++
++ for (size_t j = 0; j < lb->n_vips; j++) {
++ struct ovn_lb_vip *lb_vip = &lb->vips[j];
++ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
++ ds_clear(actions);
++ build_lb_vip_actions(lb_vip, lb_vip_nb, actions,
++ lb->selection_fields, false);
++
++ if (!sset_contains(&all_ips, lb_vip->vip_str)) {
++ sset_add(&all_ips, lb_vip->vip_str);
++ /* If there are any load balancing rules, we should send
++ * the packet to conntrack for defragmentation and
++ * tracking. This helps with two things.
++ *
++ * 1. With tracking, we can send only new connections to
++ * pick a DNAT ip address from a group.
++ * 2. If there are L4 ports in load balancing rules, we
++ * need the defragmentation to match on L4 ports. */
++ ds_clear(match);
++ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
++ ds_put_format(match, "ip && ip4.dst == %s",
++ lb_vip->vip_str);
++ } else {
++ ds_put_format(match, "ip && ip6.dst == %s",
++ lb_vip->vip_str);
++ }
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
++ 100, ds_cstr(match), "ct_next;",
++ &nb_lb->header_);
++ }
++
++ /* Higher priority rules are added for load-balancing in DNAT
++ * table. For every match (on a VIP[:port]), we add two flows
++ * via add_router_lb_flow(). One flow is for specific matching
++ * on ct.new with an action of "ct_lb($targets);". The other
++ * flow is for ct.est with an action of "ct_dnat;". */
++ ds_clear(match);
++ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
++ ds_put_format(match, "ip && ip4.dst == %s",
++ lb_vip->vip_str);
++ } else {
++ ds_put_format(match, "ip && ip6.dst == %s",
++ lb_vip->vip_str);
++ }
++
++ int prio = 110;
++ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
++ bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
++ "sctp");
++ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
++
++ if (lb_vip->vip_port) {
++ ds_put_format(match, " && %s && %s.dst == %d", proto,
++ proto, lb_vip->vip_port);
++ prio = 120;
++ }
++
++ if (od->l3redirect_port &&
++ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ od->l3redirect_port->json_key);
++ }
++
++ enum lb_snat_type snat_type = NO_FORCE_SNAT;
++ if (lb_skip_snat) {
++ snat_type = SKIP_SNAT;
++ } else if (lb_force_snat_ip || od->lb_force_snat_router_ip) {
++ snat_type = FORCE_SNAT;
++ }
++ add_router_lb_flow(lflows, od, match, actions, prio,
++ snat_type, lb_vip, proto, nb_lb,
++ meter_groups, nat_entries);
++ }
++ }
++ sset_destroy(&all_ips);
++}
++
+ #define ND_RA_MAX_INTERVAL_MAX 1800
+ #define ND_RA_MAX_INTERVAL_MIN 4
+
+@@ -11002,668 +11111,643 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
+ }
+ }
+
+-/* NAT, Defrag and load balancing. */
+ static void
+-build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
+- struct hmap *lflows,
+- struct shash *meter_groups,
+- struct hmap *lbs,
+- struct ds *match, struct ds *actions)
++build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
++ const struct nbrec_nat *nat, struct ds *match,
++ struct ds *actions, bool distributed, bool is_v6)
+ {
+- if (od->nbr) {
++ /* Ingress UNSNAT table: It is for already established connections'
++ * reverse traffic. i.e., SNAT has already been done in egress
++ * pipeline and now the packet has entered the ingress pipeline as
++ * part of a reply. We undo the SNAT here.
++ *
++ * Undoing SNAT has to happen before DNAT processing. This is
++ * because when the packet was DNATed in ingress pipeline, it did
++ * not know about the possibility of eventual additional SNAT in
++ * egress pipeline. */
++ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) {
++ return;
++ }
+
+- /* Packets are allowed by default. */
+- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
+- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
+- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
+- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
+- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
+- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
+- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
+-
+- /* Send the IPv6 NS packets to next table. When ovn-controller
+- * generates IPv6 NS (for the action - nd_ns{}), the injected
+- * packet would go through conntrack - which is not required. */
+- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
+-
+- /* NAT rules are only valid on Gateway routers and routers with
+- * l3dgw_port (router has a port with gateway chassis
+- * specified). */
+- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
+- return;
++ bool stateless = lrouter_nat_is_stateless(nat);
++ if (!od->l3dgw_port) {
++ /* Gateway router. */
++ ds_clear(match);
++ ds_clear(actions);
++ ds_put_format(match, "ip && ip%s.dst == %s",
++ is_v6 ? "6" : "4", nat->external_ip);
++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++ ds_put_format(actions, "ip%s.dst=%s; next;",
++ is_v6 ? "6" : "4", nat->logical_ip);
++ } else {
++ ds_put_cstr(actions, "ct_snat;");
+ }
+
+- struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
++ 90, ds_cstr(match), ds_cstr(actions),
++ &nat->header_);
++ } else {
++ /* Distributed router. */
+
+- bool dnat_force_snat_ip =
+- !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
+- bool lb_force_snat_ip =
+- !lport_addresses_is_empty(&od->lb_force_snat_addrs);
++ /* Traffic received on l3dgw_port is subject to NAT. */
++ ds_clear(match);
++ ds_clear(actions);
++ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
++ is_v6 ? "6" : "4", nat->external_ip,
++ od->l3dgw_port->json_key);
++ if (!distributed && od->l3redirect_port) {
++ /* Flows for NAT rules that are centralized are only
++ * programmed on the gateway chassis. */
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ od->l3redirect_port->json_key);
++ }
+
+- for (int i = 0; i < od->nbr->n_nat; i++) {
+- const struct nbrec_nat *nat;
++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++ ds_put_format(actions, "ip%s.dst=%s; next;",
++ is_v6 ? "6" : "4", nat->logical_ip);
++ } else {
++ ds_put_cstr(actions, "ct_snat;");
++ }
+
+- nat = od->nbr->nat[i];
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
++ 100, ds_cstr(match), ds_cstr(actions),
++ &nat->header_);
++ }
++}
+
+- ovs_be32 ip, mask;
+- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
+- bool is_v6 = false;
+- bool stateless = lrouter_nat_is_stateless(nat);
+- struct nbrec_address_set *allowed_ext_ips =
+- nat->allowed_ext_ips;
+- struct nbrec_address_set *exempted_ext_ips =
+- nat->exempted_ext_ips;
++static void
++build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
++ const struct nbrec_nat *nat, struct ds *match,
++ struct ds *actions, bool distributed,
++ ovs_be32 mask, bool is_v6)
++{
++ /* Ingress DNAT table: Packets enter the pipeline with destination
++ * IP address that needs to be DNATted from a external IP address
++ * to a logical IP address. */
++ if (!strcmp(nat->type, "dnat") || !strcmp(nat->type, "dnat_and_snat")) {
++ bool stateless = lrouter_nat_is_stateless(nat);
+
+- if (allowed_ext_ips && exempted_ext_ips) {
+- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
+- "both allowed and exempt external ips set",
+- UUID_ARGS(&(nat->header_.uuid)));
+- continue;
++ if (!od->l3dgw_port) {
++ /* Gateway router. */
++ /* Packet when it goes from the initiator to destination.
++ * We need to set flags.loopback because the router can
++ * send the packet back through the same interface. */
++ ds_clear(match);
++ ds_put_format(match, "ip && ip%s.dst == %s",
++ is_v6 ? "6" : "4", nat->external_ip);
++ ds_clear(actions);
++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
++ is_v6, true, mask);
+ }
+
+- char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
+- if (error || mask != OVS_BE32_MAX) {
+- free(error);
+- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
+- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
+- /* Invalid for both IPv4 and IPv6 */
+- static struct vlog_rate_limit rl =
+- VLOG_RATE_LIMIT_INIT(5, 1);
+- VLOG_WARN_RL(&rl, "bad external ip %s for nat",
+- nat->external_ip);
+- free(error);
+- continue;
+- }
+- /* It was an invalid IPv4 address, but valid IPv6.
+- * Treat the rest of the handling of this NAT rule
+- * as IPv6. */
+- is_v6 = true;
+- }
+-
+- /* Check the validity of nat->logical_ip. 'logical_ip' can
+- * be a subnet when the type is "snat". */
+- int cidr_bits;
+- if (is_v6) {
+- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
+- cidr_bits = ipv6_count_cidr_bits(&mask_v6);
+- } else {
+- error = ip_parse_masked(nat->logical_ip, &ip, &mask);
+- cidr_bits = ip_count_cidr_bits(mask);
++ if (!lport_addresses_is_empty(&od->dnat_force_snat_addrs)) {
++ /* Indicate to the future tables that a DNAT has taken
++ * place and a force SNAT needs to be done in the
++ * Egress SNAT table. */
++ ds_put_format(actions, "flags.force_snat_for_dnat = 1; ");
+ }
+- if (!strcmp(nat->type, "snat")) {
+- if (error) {
+- /* Invalid for both IPv4 and IPv6 */
+- static struct vlog_rate_limit rl =
+- VLOG_RATE_LIMIT_INIT(5, 1);
+- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
+- "in router "UUID_FMT"",
+- nat->logical_ip, UUID_ARGS(&od->key));
+- free(error);
+- continue;
+- }
++
++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++ ds_put_format(actions, "flags.loopback = 1; "
++ "ip%s.dst=%s; next;",
++ is_v6 ? "6" : "4", nat->logical_ip);
+ } else {
+- if (error || (!is_v6 && mask != OVS_BE32_MAX)
+- || (is_v6 && memcmp(&mask_v6, &v6_exact,
+- sizeof mask_v6))) {
+- /* Invalid for both IPv4 and IPv6 */
+- static struct vlog_rate_limit rl =
+- VLOG_RATE_LIMIT_INIT(5, 1);
+- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
+- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
+- free(error);
+- continue;
++ ds_put_format(actions, "flags.loopback = 1; ct_dnat(%s",
++ nat->logical_ip);
++
++ if (nat->external_port_range[0]) {
++ ds_put_format(actions, ",%s", nat->external_port_range);
+ }
++ ds_put_format(actions, ");");
+ }
+
+- /* For distributed router NAT, determine whether this NAT rule
+- * satisfies the conditions for distributed NAT processing. */
+- bool distributed = false;
+- struct eth_addr mac;
+- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
+- nat->logical_port && nat->external_mac) {
+- if (eth_addr_from_string(nat->external_mac, &mac)) {
+- distributed = true;
+- } else {
+- static struct vlog_rate_limit rl =
+- VLOG_RATE_LIMIT_INIT(5, 1);
+- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
+- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
+- continue;
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
++ ds_cstr(match), ds_cstr(actions),
++ &nat->header_);
++ } else {
++ /* Distributed router. */
++
++ /* Traffic received on l3dgw_port is subject to NAT. */
++ ds_clear(match);
++ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
++ is_v6 ? "6" : "4", nat->external_ip,
++ od->l3dgw_port->json_key);
++ if (!distributed && od->l3redirect_port) {
++ /* Flows for NAT rules that are centralized are only
++ * programmed on the gateway chassis. */
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ od->l3redirect_port->json_key);
++ }
++ ds_clear(actions);
++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
++ is_v6, true, mask);
++ }
++
++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++ ds_put_format(actions, "ip%s.dst=%s; next;",
++ is_v6 ? "6" : "4", nat->logical_ip);
++ } else {
++ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip);
++ if (nat->external_port_range[0]) {
++ ds_put_format(actions, ",%s", nat->external_port_range);
+ }
++ ds_put_format(actions, ");");
+ }
+
+- /* Ingress UNSNAT table: It is for already established connections'
+- * reverse traffic. i.e., SNAT has already been done in egress
+- * pipeline and now the packet has entered the ingress pipeline as
+- * part of a reply. We undo the SNAT here.
+- *
+- * Undoing SNAT has to happen before DNAT processing. This is
+- * because when the packet was DNATed in ingress pipeline, it did
+- * not know about the possibility of eventual additional SNAT in
+- * egress pipeline. */
+- if (!strcmp(nat->type, "snat")
+- || !strcmp(nat->type, "dnat_and_snat")) {
+- if (!od->l3dgw_port) {
+- /* Gateway router. */
+- ds_clear(match);
+- ds_clear(actions);
+- ds_put_format(match, "ip && ip%s.dst == %s",
+- is_v6 ? "6" : "4",
+- nat->external_ip);
+- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
+- ds_put_format(actions, "ip%s.dst=%s; next;",
+- is_v6 ? "6" : "4", nat->logical_ip);
+- } else {
+- ds_put_cstr(actions, "ct_snat;");
+- }
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
++ ds_cstr(match), ds_cstr(actions),
++ &nat->header_);
++ }
++ }
++}
+
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
+- 90, ds_cstr(match),
+- ds_cstr(actions),
+- &nat->header_);
+- } else {
+- /* Distributed router. */
++static void
++build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
++ const struct nbrec_nat *nat, struct ds *match,
++ struct ds *actions, bool distributed,
++ struct eth_addr mac, bool is_v6)
++{
++ /* Egress UNDNAT table: It is for already established connections'
++ * reverse traffic. i.e., DNAT has already been done in ingress
++ * pipeline and now the packet has entered the egress pipeline as
++ * part of a reply. We undo the DNAT here.
++ *
++ * Note that this only applies for NAT on a distributed router.
++ * Undo DNAT on a gateway router is done in the ingress DNAT
++ * pipeline stage. */
++ if (!od->l3dgw_port ||
++ (strcmp(nat->type, "dnat") && strcmp(nat->type, "dnat_and_snat"))) {
++ return;
++ }
+
+- /* Traffic received on l3dgw_port is subject to NAT. */
+- ds_clear(match);
+- ds_clear(actions);
+- ds_put_format(match, "ip && ip%s.dst == %s"
+- " && inport == %s",
+- is_v6 ? "6" : "4",
+- nat->external_ip,
+- od->l3dgw_port->json_key);
+- if (!distributed && od->l3redirect_port) {
+- /* Flows for NAT rules that are centralized are only
+- * programmed on the gateway chassis. */
+- ds_put_format(match, " && is_chassis_resident(%s)",
+- od->l3redirect_port->json_key);
+- }
++ ds_clear(match);
++ ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
++ is_v6 ? "6" : "4", nat->logical_ip,
++ od->l3dgw_port->json_key);
++ if (!distributed && od->l3redirect_port) {
++ /* Flows for NAT rules that are centralized are only
++ * programmed on the gateway chassis. */
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ od->l3redirect_port->json_key);
++ }
++ ds_clear(actions);
++ if (distributed) {
++ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
++ ETH_ADDR_ARGS(mac));
++ }
+
+- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
+- ds_put_format(actions, "ip%s.dst=%s; next;",
+- is_v6 ? "6" : "4", nat->logical_ip);
+- } else {
+- ds_put_cstr(actions, "ct_snat;");
+- }
++ if (!strcmp(nat->type, "dnat_and_snat") &&
++ lrouter_nat_is_stateless(nat)) {
++ ds_put_format(actions, "ip%s.src=%s; next;",
++ is_v6 ? "6" : "4", nat->external_ip);
++ } else {
++ ds_put_format(actions, "ct_dnat;");
++ }
+
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
+- 100,
+- ds_cstr(match), ds_cstr(actions),
+- &nat->header_);
+- }
+- }
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
++ ds_cstr(match), ds_cstr(actions),
++ &nat->header_);
++}
+
+- /* Ingress DNAT table: Packets enter the pipeline with destination
+- * IP address that needs to be DNATted from a external IP address
+- * to a logical IP address. */
+- if (!strcmp(nat->type, "dnat")
+- || !strcmp(nat->type, "dnat_and_snat")) {
+- if (!od->l3dgw_port) {
+- /* Gateway router. */
+- /* Packet when it goes from the initiator to destination.
+- * We need to set flags.loopback because the router can
+- * send the packet back through the same interface. */
+- ds_clear(match);
+- ds_put_format(match, "ip && ip%s.dst == %s",
+- is_v6 ? "6" : "4",
+- nat->external_ip);
+- ds_clear(actions);
+- if (allowed_ext_ips || exempted_ext_ips) {
+- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
+- is_v6, true, mask);
+- }
++static void
++build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
++ const struct nbrec_nat *nat, struct ds *match,
++ struct ds *actions, bool distributed,
++ struct eth_addr mac, ovs_be32 mask,
++ int cidr_bits, bool is_v6)
++{
++ /* Egress SNAT table: Packets enter the egress pipeline with
++ * source ip address that needs to be SNATted to a external ip
++ * address. */
++ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) {
++ return;
++ }
+
+- if (dnat_force_snat_ip) {
+- /* Indicate to the future tables that a DNAT has taken
+- * place and a force SNAT needs to be done in the
+- * Egress SNAT table. */
+- ds_put_format(actions,
+- "flags.force_snat_for_dnat = 1; ");
+- }
++ bool stateless = lrouter_nat_is_stateless(nat);
++ if (!od->l3dgw_port) {
++ /* Gateway router. */
++ ds_clear(match);
++ ds_put_format(match, "ip && ip%s.src == %s",
++ is_v6 ? "6" : "4", nat->logical_ip);
++ ds_clear(actions);
+
+- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
+- ds_put_format(actions, "flags.loopback = 1; "
+- "ip%s.dst=%s; next;",
+- is_v6 ? "6" : "4", nat->logical_ip);
+- } else {
+- ds_put_format(actions, "flags.loopback = 1; "
+- "ct_dnat(%s", nat->logical_ip);
++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
++ is_v6, false, mask);
++ }
+
+- if (nat->external_port_range[0]) {
+- ds_put_format(actions, ",%s",
+- nat->external_port_range);
+- }
+- ds_put_format(actions, ");");
+- }
++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++ ds_put_format(actions, "ip%s.src=%s; next;",
++ is_v6 ? "6" : "4", nat->external_ip);
++ } else {
++ ds_put_format(actions, "ct_snat(%s", nat->external_ip);
+
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
+- ds_cstr(match), ds_cstr(actions),
+- &nat->header_);
+- } else {
+- /* Distributed router. */
++ if (nat->external_port_range[0]) {
++ ds_put_format(actions, ",%s",
++ nat->external_port_range);
++ }
++ ds_put_format(actions, ");");
++ }
+
+- /* Traffic received on l3dgw_port is subject to NAT. */
+- ds_clear(match);
+- ds_put_format(match, "ip && ip%s.dst == %s"
+- " && inport == %s",
+- is_v6 ? "6" : "4",
+- nat->external_ip,
+- od->l3dgw_port->json_key);
+- if (!distributed && od->l3redirect_port) {
+- /* Flows for NAT rules that are centralized are only
+- * programmed on the gateway chassis. */
+- ds_put_format(match, " && is_chassis_resident(%s)",
+- od->l3redirect_port->json_key);
+- }
+- ds_clear(actions);
+- if (allowed_ext_ips || exempted_ext_ips) {
+- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
+- is_v6, true, mask);
+- }
++ /* The priority here is calculated such that the
++ * nat->logical_ip with the longest mask gets a higher
++ * priority. */
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
++ cidr_bits + 1, ds_cstr(match),
++ ds_cstr(actions), &nat->header_);
++ } else {
++ uint16_t priority = cidr_bits + 1;
+
+- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
+- ds_put_format(actions, "ip%s.dst=%s; next;",
+- is_v6 ? "6" : "4", nat->logical_ip);
+- } else {
+- ds_put_format(actions, "ct_dnat(%s", nat->logical_ip);
+- if (nat->external_port_range[0]) {
+- ds_put_format(actions, ",%s",
+- nat->external_port_range);
+- }
+- ds_put_format(actions, ");");
+- }
++ /* Distributed router. */
++ ds_clear(match);
++ ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
++ is_v6 ? "6" : "4", nat->logical_ip,
++ od->l3dgw_port->json_key);
++ if (!distributed && od->l3redirect_port) {
++ /* Flows for NAT rules that are centralized are only
++ * programmed on the gateway chassis. */
++ priority += 128;
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ od->l3redirect_port->json_key);
++ }
++ ds_clear(actions);
+
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
+- ds_cstr(match), ds_cstr(actions),
+- &nat->header_);
+- }
+- }
++ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
++ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
++ is_v6, false, mask);
++ }
+
+- /* ARP resolve for NAT IPs. */
+- if (od->l3dgw_port) {
+- if (!strcmp(nat->type, "snat")) {
+- ds_clear(match);
+- ds_put_format(
+- match, "inport == %s && %s == %s",
+- od->l3dgw_port->json_key,
+- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
+- 120, ds_cstr(match), "next;",
+- &nat->header_);
+- }
++ if (distributed) {
++ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
++ ETH_ADDR_ARGS(mac));
++ }
+
+- if (!sset_contains(&nat_entries, nat->external_ip)) {
+- ds_clear(match);
+- ds_put_format(
+- match, "outport == %s && %s == %s",
+- od->l3dgw_port->json_key,
+- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
++ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++ ds_put_format(actions, "ip%s.src=%s; next;",
++ is_v6 ? "6" : "4", nat->external_ip);
++ } else {
++ ds_put_format(actions, "ct_snat(%s",
+ nat->external_ip);
+- ds_clear(actions);
+- ds_put_format(
+- actions, "eth.dst = %s; next;",
+- distributed ? nat->external_mac :
+- od->l3dgw_port->lrp_networks.ea_s);
+- ovn_lflow_add_with_hint(lflows, od,
+- S_ROUTER_IN_ARP_RESOLVE,
+- 100, ds_cstr(match),
+- ds_cstr(actions),
+- &nat->header_);
+- sset_add(&nat_entries, nat->external_ip);
+- }
+- } else {
+- /* Add the NAT external_ip to the nat_entries even for
+- * gateway routers. This is required for adding load balancer
+- * flows.*/
+- sset_add(&nat_entries, nat->external_ip);
++ if (nat->external_port_range[0]) {
++ ds_put_format(actions, ",%s", nat->external_port_range);
+ }
++ ds_put_format(actions, ");");
++ }
+
+- /* Egress UNDNAT table: It is for already established connections'
+- * reverse traffic. i.e., DNAT has already been done in ingress
+- * pipeline and now the packet has entered the egress pipeline as
+- * part of a reply. We undo the DNAT here.
+- *
+- * Note that this only applies for NAT on a distributed router.
+- * Undo DNAT on a gateway router is done in the ingress DNAT
+- * pipeline stage. */
+- if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
+- || !strcmp(nat->type, "dnat_and_snat"))) {
+- ds_clear(match);
+- ds_put_format(match, "ip && ip%s.src == %s"
+- " && outport == %s",
+- is_v6 ? "6" : "4",
+- nat->logical_ip,
+- od->l3dgw_port->json_key);
+- if (!distributed && od->l3redirect_port) {
+- /* Flows for NAT rules that are centralized are only
+- * programmed on the gateway chassis. */
+- ds_put_format(match, " && is_chassis_resident(%s)",
+- od->l3redirect_port->json_key);
+- }
+- ds_clear(actions);
+- if (distributed) {
+- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
+- ETH_ADDR_ARGS(mac));
+- }
++ /* The priority here is calculated such that the
++ * nat->logical_ip with the longest mask gets a higher
++ * priority. */
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
++ priority, ds_cstr(match),
++ ds_cstr(actions), &nat->header_);
++ }
++}
+
+- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
+- ds_put_format(actions, "ip%s.src=%s; next;",
+- is_v6 ? "6" : "4", nat->external_ip);
+- } else {
+- ds_put_format(actions, "ct_dnat;");
+- }
++static void
++build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
++ const struct nbrec_nat *nat, struct ds *match,
++ struct ds *actions, struct eth_addr mac,
++ bool distributed, bool is_v6)
++{
++ if (od->l3dgw_port && !strcmp(nat->type, "snat")) {
++ ds_clear(match);
++ ds_put_format(
++ match, "inport == %s && %s == %s",
++ od->l3dgw_port->json_key,
++ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
++ 120, ds_cstr(match), "next;",
++ &nat->header_);
++ }
++ /* Logical router ingress table 0:
++ * For NAT on a distributed router, add rules allowing
++ * ingress traffic with eth.dst matching nat->external_mac
++ * on the l3dgw_port instance where nat->logical_port is
++ * resident. */
++ if (distributed) {
++ /* Store the ethernet address of the port receiving the packet.
++ * This will save us from having to match on inport further
++ * down in the pipeline.
++ */
++ ds_clear(actions);
++ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
++ od->l3dgw_port->lrp_networks.ea_s);
+
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
+- ds_cstr(match), ds_cstr(actions),
+- &nat->header_);
+- }
++ ds_clear(match);
++ ds_put_format(match,
++ "eth.dst == "ETH_ADDR_FMT" && inport == %s"
++ " && is_chassis_resident(\"%s\")",
++ ETH_ADDR_ARGS(mac),
++ od->l3dgw_port->json_key,
++ nat->logical_port);
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
++ ds_cstr(match), ds_cstr(actions),
++ &nat->header_);
++ }
++}
+
+- /* Egress SNAT table: Packets enter the egress pipeline with
+- * source ip address that needs to be SNATted to a external ip
+- * address. */
+- if (!strcmp(nat->type, "snat")
+- || !strcmp(nat->type, "dnat_and_snat")) {
+- if (!od->l3dgw_port) {
+- /* Gateway router. */
+- ds_clear(match);
+- ds_put_format(match, "ip && ip%s.src == %s",
+- is_v6 ? "6" : "4",
+- nat->logical_ip);
+- ds_clear(actions);
++static int
++lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
++ ovs_be32 *mask, bool *is_v6, int *cidr_bits,
++ struct eth_addr *mac, bool *distributed)
++{
++ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
++ ovs_be32 ip;
+
+- if (allowed_ext_ips || exempted_ext_ips) {
+- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
+- is_v6, false, mask);
+- }
++ if (nat->allowed_ext_ips && nat->exempted_ext_ips) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
++ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
++ "both allowed and exempt external ips set",
++ UUID_ARGS(&(nat->header_.uuid)));
++ return -EINVAL;
++ }
+
+- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
+- ds_put_format(actions, "ip%s.src=%s; next;",
+- is_v6 ? "6" : "4", nat->external_ip);
+- } else {
+- ds_put_format(actions, "ct_snat(%s",
+- nat->external_ip);
++ char *error = ip_parse_masked(nat->external_ip, &ip, mask);
++ *is_v6 = false;
+
+- if (nat->external_port_range[0]) {
+- ds_put_format(actions, ",%s",
+- nat->external_port_range);
+- }
+- ds_put_format(actions, ");");
+- }
++ if (error || *mask != OVS_BE32_MAX) {
++ free(error);
++ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
++ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
++ /* Invalid for both IPv4 and IPv6 */
++ static struct vlog_rate_limit rl =
++ VLOG_RATE_LIMIT_INIT(5, 1);
++ VLOG_WARN_RL(&rl, "bad external ip %s for nat",
++ nat->external_ip);
++ free(error);
++ return -EINVAL;
++ }
++ /* It was an invalid IPv4 address, but valid IPv6.
++ * Treat the rest of the handling of this NAT rule
++ * as IPv6. */
++ *is_v6 = true;
++ }
+
+- /* The priority here is calculated such that the
+- * nat->logical_ip with the longest mask gets a higher
+- * priority. */
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
+- cidr_bits + 1,
+- ds_cstr(match), ds_cstr(actions),
+- &nat->header_);
+- } else {
+- uint16_t priority = cidr_bits + 1;
++ /* Check the validity of nat->logical_ip. 'logical_ip' can
++ * be a subnet when the type is "snat". */
++ if (*is_v6) {
++ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
++ *cidr_bits = ipv6_count_cidr_bits(&mask_v6);
++ } else {
++ error = ip_parse_masked(nat->logical_ip, &ip, mask);
++ *cidr_bits = ip_count_cidr_bits(*mask);
++ }
++ if (!strcmp(nat->type, "snat")) {
++ if (error) {
++ /* Invalid for both IPv4 and IPv6 */
++ static struct vlog_rate_limit rl =
++ VLOG_RATE_LIMIT_INIT(5, 1);
++ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
++ "in router "UUID_FMT"",
++ nat->logical_ip, UUID_ARGS(&od->key));
++ free(error);
++ return -EINVAL;
++ }
++ } else {
++ if (error || (*is_v6 == false && *mask != OVS_BE32_MAX)
++ || (*is_v6 && memcmp(&mask_v6, &v6_exact,
++ sizeof mask_v6))) {
++ /* Invalid for both IPv4 and IPv6 */
++ static struct vlog_rate_limit rl =
++ VLOG_RATE_LIMIT_INIT(5, 1);
++ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
++ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
++ free(error);
++ return -EINVAL;
++ }
++ }
+
+- /* Distributed router. */
+- ds_clear(match);
+- ds_put_format(match, "ip && ip%s.src == %s"
+- " && outport == %s",
+- is_v6 ? "6" : "4",
+- nat->logical_ip,
+- od->l3dgw_port->json_key);
+- if (!distributed && od->l3redirect_port) {
+- /* Flows for NAT rules that are centralized are only
+- * programmed on the gateway chassis. */
+- priority += 128;
+- ds_put_format(match, " && is_chassis_resident(%s)",
+- od->l3redirect_port->json_key);
+- }
+- ds_clear(actions);
++ /* For distributed router NAT, determine whether this NAT rule
++ * satisfies the conditions for distributed NAT processing. */
++ *distributed = false;
++ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
++ nat->logical_port && nat->external_mac) {
++ if (eth_addr_from_string(nat->external_mac, mac)) {
++ *distributed = true;
++ } else {
++ static struct vlog_rate_limit rl =
++ VLOG_RATE_LIMIT_INIT(5, 1);
++ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
++ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
++ return -EINVAL;
++ }
++ }
+
+- if (allowed_ext_ips || exempted_ext_ips) {
+- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
+- is_v6, false, mask);
+- }
++ return 0;
++}
+
+- if (distributed) {
+- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
+- ETH_ADDR_ARGS(mac));
+- }
++/* NAT, Defrag and load balancing. */
++static void
++build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
++ struct hmap *lflows,
++ struct shash *meter_groups,
++ struct hmap *lbs,
++ struct ds *match, struct ds *actions)
++{
++ if (!od->nbr) {
++ return;
++ }
+
+- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
+- ds_put_format(actions, "ip%s.src=%s; next;",
+- is_v6 ? "6" : "4", nat->external_ip);
+- } else {
+- ds_put_format(actions, "ct_snat(%s",
+- nat->external_ip);
+- if (nat->external_port_range[0]) {
+- ds_put_format(actions, ",%s",
+- nat->external_port_range);
+- }
+- ds_put_format(actions, ");");
+- }
++ /* Packets are allowed by default. */
++ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
++ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
++ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
++ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
++
++ /* Send the IPv6 NS packets to next table. When ovn-controller
++ * generates IPv6 NS (for the action - nd_ns{}), the injected
++ * packet would go through conntrack - which is not required. */
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
++
++ /* NAT rules are only valid on Gateway routers and routers with
++ * l3dgw_port (router has a port with gateway chassis
++ * specified). */
++ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
++ return;
++ }
+
+- /* The priority here is calculated such that the
+- * nat->logical_ip with the longest mask gets a higher
+- * priority. */
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
+- priority, ds_cstr(match),
+- ds_cstr(actions),
+- &nat->header_);
+- }
+- }
++ struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
+
+- /* Logical router ingress table 0:
+- * For NAT on a distributed router, add rules allowing
+- * ingress traffic with eth.dst matching nat->external_mac
+- * on the l3dgw_port instance where nat->logical_port is
+- * resident. */
+- if (distributed) {
+- /* Store the ethernet address of the port receiving the packet.
+- * This will save us from having to match on inport further
+- * down in the pipeline.
+- */
+- ds_clear(actions);
+- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
+- od->l3dgw_port->lrp_networks.ea_s);
++ bool dnat_force_snat_ip =
++ !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
++ bool lb_force_snat_ip =
++ !lport_addresses_is_empty(&od->lb_force_snat_addrs);
+
+- ds_clear(match);
+- ds_put_format(match,
+- "eth.dst == "ETH_ADDR_FMT" && inport == %s"
+- " && is_chassis_resident(\"%s\")",
+- ETH_ADDR_ARGS(mac),
+- od->l3dgw_port->json_key,
+- nat->logical_port);
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
+- ds_cstr(match), ds_cstr(actions),
+- &nat->header_);
+- }
++ for (int i = 0; i < od->nbr->n_nat; i++) {
++ const struct nbrec_nat *nat = nat = od->nbr->nat[i];
++ struct eth_addr mac = eth_addr_broadcast;
++ bool is_v6, distributed;
++ ovs_be32 mask;
++ int cidr_bits;
+
+- /* Ingress Gateway Redirect Table: For NAT on a distributed
+- * router, add flows that are specific to a NAT rule. These
+- * flows indicate the presence of an applicable NAT rule that
+- * can be applied in a distributed manner.
+- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
+- * NAT external IP and NAT external mac so the ARP request
+- * generated in the following stage is sent out with proper IP/MAC
+- * src addresses.
+- */
+- if (distributed) {
+- ds_clear(match);
+- ds_clear(actions);
+- ds_put_format(match,
+- "ip%s.src == %s && outport == %s && "
+- "is_chassis_resident(\"%s\")",
+- is_v6 ? "6" : "4", nat->logical_ip,
+- od->l3dgw_port->json_key, nat->logical_port);
+- ds_put_format(actions, "eth.src = %s; %s = %s; next;",
+- nat->external_mac,
+- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
+- nat->external_ip);
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
+- 100, ds_cstr(match),
+- ds_cstr(actions), &nat->header_);
+- }
++ if (lrouter_check_nat_entry(od, nat, &mask, &is_v6, &cidr_bits,
++ &mac, &distributed) < 0) {
++ continue;
++ }
+
+- /* Egress Loopback table: For NAT on a distributed router.
+- * If packets in the egress pipeline on the distributed
+- * gateway port have ip.dst matching a NAT external IP, then
+- * loop a clone of the packet back to the beginning of the
+- * ingress pipeline with inport = outport. */
+- if (od->l3dgw_port) {
+- /* Distributed router. */
+- ds_clear(match);
+- ds_put_format(match, "ip%s.dst == %s && outport == %s",
+- is_v6 ? "6" : "4",
+- nat->external_ip,
+- od->l3dgw_port->json_key);
+- if (!distributed) {
+- ds_put_format(match, " && is_chassis_resident(%s)",
+- od->l3redirect_port->json_key);
+- } else {
+- ds_put_format(match, " && is_chassis_resident(\"%s\")",
+- nat->logical_port);
+- }
++ /* S_ROUTER_IN_UNSNAT */
++ build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions, distributed,
++ is_v6);
++ /* S_ROUTER_IN_DNAT */
++ build_lrouter_in_dnat_flow(lflows, od, nat, match, actions, distributed,
++ mask, is_v6);
+
++ /* ARP resolve for NAT IPs. */
++ if (od->l3dgw_port) {
++ if (!sset_contains(&nat_entries, nat->external_ip)) {
++ ds_clear(match);
++ ds_put_format(
++ match, "outport == %s && %s == %s",
++ od->l3dgw_port->json_key,
++ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
++ nat->external_ip);
+ ds_clear(actions);
+- ds_put_format(actions,
+- "clone { ct_clear; "
+- "inport = outport; outport = \"\"; "
+- "flags = 0; flags.loopback = 1; ");
+- for (int j = 0; j < MFF_N_LOG_REGS; j++) {
+- ds_put_format(actions, "reg%d = 0; ", j);
+- }
+- ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; "
+- "next(pipeline=ingress, table=%d); };",
+- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
+- ds_cstr(match), ds_cstr(actions),
++ ds_put_format(
++ actions, "eth.dst = %s; next;",
++ distributed ? nat->external_mac :
++ od->l3dgw_port->lrp_networks.ea_s);
++ ovn_lflow_add_with_hint(lflows, od,
++ S_ROUTER_IN_ARP_RESOLVE,
++ 100, ds_cstr(match),
++ ds_cstr(actions),
+ &nat->header_);
++ sset_add(&nat_entries, nat->external_ip);
+ }
+- }
+-
+- /* Handle force SNAT options set in the gateway router. */
+- if (!od->l3dgw_port) {
+- if (dnat_force_snat_ip) {
+- if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
+- build_lrouter_force_snat_flows(lflows, od, "4",
+- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
+- "dnat");
+- }
+- if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
+- build_lrouter_force_snat_flows(lflows, od, "6",
+- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
+- "dnat");
+- }
+- }
+- if (lb_force_snat_ip) {
+- if (od->lb_force_snat_addrs.n_ipv4_addrs) {
+- build_lrouter_force_snat_flows(lflows, od, "4",
+- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
+- }
+- if (od->lb_force_snat_addrs.n_ipv6_addrs) {
+- build_lrouter_force_snat_flows(lflows, od, "6",
+- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
+- }
++ } else {
++ /* Add the NAT external_ip to the nat_entries even for
++ * gateway routers. This is required for adding load balancer
++ * flows.*/
++ sset_add(&nat_entries, nat->external_ip);
++ }
++
++ /* S_ROUTER_OUT_UNDNAT */
++ build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, distributed,
++ mac, is_v6);
++ /* S_ROUTER_OUT_SNAT */
++ build_lrouter_out_snat_flow(lflows, od, nat, match, actions, distributed,
++ mac, mask, cidr_bits, is_v6);
++
++ /* S_ROUTER_IN_ADMISSION - S_ROUTER_IN_IP_INPUT */
++ build_lrouter_ingress_flow(lflows, od, nat, match, actions,
++ mac, distributed, is_v6);
++
++ /* Ingress Gateway Redirect Table: For NAT on a distributed
++ * router, add flows that are specific to a NAT rule. These
++ * flows indicate the presence of an applicable NAT rule that
++ * can be applied in a distributed manner.
++ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
++ * NAT external IP and NAT external mac so the ARP request
++ * generated in the following stage is sent out with proper IP/MAC
++ * src addresses.
++ */
++ if (distributed) {
++ ds_clear(match);
++ ds_clear(actions);
++ ds_put_format(match,
++ "ip%s.src == %s && outport == %s && "
++ "is_chassis_resident(\"%s\")",
++ is_v6 ? "6" : "4", nat->logical_ip,
++ od->l3dgw_port->json_key, nat->logical_port);
++ ds_put_format(actions, "eth.src = %s; %s = %s; next;",
++ nat->external_mac,
++ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
++ nat->external_ip);
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
++ 100, ds_cstr(match),
++ ds_cstr(actions), &nat->header_);
++ }
++
++ /* Egress Loopback table: For NAT on a distributed router.
++ * If packets in the egress pipeline on the distributed
++ * gateway port have ip.dst matching a NAT external IP, then
++ * loop a clone of the packet back to the beginning of the
++ * ingress pipeline with inport = outport. */
++ if (od->l3dgw_port) {
++ /* Distributed router. */
++ ds_clear(match);
++ ds_put_format(match, "ip%s.dst == %s && outport == %s",
++ is_v6 ? "6" : "4",
++ nat->external_ip,
++ od->l3dgw_port->json_key);
++ if (!distributed) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ od->l3redirect_port->json_key);
++ } else {
++ ds_put_format(match, " && is_chassis_resident(\"%s\")",
++ nat->logical_port);
+ }
+
+- /* For gateway router, re-circulate every packet through
+- * the DNAT zone. This helps with the following.
+- *
+- * Any packet that needs to be unDNATed in the reverse
+- * direction gets unDNATed. Ideally this could be done in
+- * the egress pipeline. But since the gateway router
+- * does not have any feature that depends on the source
+- * ip address being external IP address for IP routing,
+- * we can do it here, saving a future re-circulation. */
+- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
+- "ip", "flags.loopback = 1; ct_dnat;");
++ ds_clear(actions);
++ ds_put_format(actions,
++ "clone { ct_clear; "
++ "inport = outport; outport = \"\"; "
++ "flags = 0; flags.loopback = 1; ");
++ for (int j = 0; j < MFF_N_LOG_REGS; j++) {
++ ds_put_format(actions, "reg%d = 0; ", j);
++ }
++ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; "
++ "next(pipeline=ingress, table=%d); };",
++ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
++ ds_cstr(match), ds_cstr(actions),
++ &nat->header_);
+ }
++ }
+
+- /* Load balancing and packet defrag are only valid on
+- * Gateway routers or router with gateway port. */
+- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
+- sset_destroy(&nat_entries);
+- return;
++ /* Handle force SNAT options set in the gateway router. */
++ if (!od->l3dgw_port) {
++ if (dnat_force_snat_ip) {
++ if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
++ build_lrouter_force_snat_flows(lflows, od, "4",
++ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
++ "dnat");
++ }
++ if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
++ build_lrouter_force_snat_flows(lflows, od, "6",
++ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
++ "dnat");
++ }
+ }
+-
+- /* A set to hold all ips that need defragmentation and tracking. */
+- struct sset all_ips = SSET_INITIALIZER(&all_ips);
+-
+- for (int i = 0; i < od->nbr->n_load_balancer; i++) {
+- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
+- struct ovn_northd_lb *lb =
+- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
+- ovs_assert(lb);
+-
+- for (size_t j = 0; j < lb->n_vips; j++) {
+- struct ovn_lb_vip *lb_vip = &lb->vips[j];
+- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
+- ds_clear(actions);
+- build_lb_vip_actions(lb_vip, lb_vip_nb, actions,
+- lb->selection_fields, false);
+-
+- if (!sset_contains(&all_ips, lb_vip->vip_str)) {
+- sset_add(&all_ips, lb_vip->vip_str);
+- /* If there are any load balancing rules, we should send
+- * the packet to conntrack for defragmentation and
+- * tracking. This helps with two things.
+- *
+- * 1. With tracking, we can send only new connections to
+- * pick a DNAT ip address from a group.
+- * 2. If there are L4 ports in load balancing rules, we
+- * need the defragmentation to match on L4 ports. */
+- ds_clear(match);
+- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
+- ds_put_format(match, "ip && ip4.dst == %s",
+- lb_vip->vip_str);
+- } else {
+- ds_put_format(match, "ip && ip6.dst == %s",
+- lb_vip->vip_str);
+- }
+- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
+- 100, ds_cstr(match), "ct_next;",
+- &nb_lb->header_);
+- }
+-
+- /* Higher priority rules are added for load-balancing in DNAT
+- * table. For every match (on a VIP[:port]), we add two flows
+- * via add_router_lb_flow(). One flow is for specific matching
+- * on ct.new with an action of "ct_lb($targets);". The other
+- * flow is for ct.est with an action of "ct_dnat;". */
+- ds_clear(match);
+- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
+- ds_put_format(match, "ip && ip4.dst == %s",
+- lb_vip->vip_str);
+- } else {
+- ds_put_format(match, "ip && ip6.dst == %s",
+- lb_vip->vip_str);
+- }
+-
+- int prio = 110;
+- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
+- bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
+- "sctp");
+- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
+-
+- if (lb_vip->vip_port) {
+- ds_put_format(match, " && %s && %s.dst == %d", proto,
+- proto, lb_vip->vip_port);
+- prio = 120;
+- }
+-
+- if (od->l3redirect_port &&
+- (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
+- ds_put_format(match, " && is_chassis_resident(%s)",
+- od->l3redirect_port->json_key);
+- }
+- bool force_snat_for_lb =
+- lb_force_snat_ip || od->lb_force_snat_router_ip;
+- add_router_lb_flow(lflows, od, match, actions, prio,
+- force_snat_for_lb, lb_vip, proto,
+- nb_lb, meter_groups, &nat_entries);
++ if (lb_force_snat_ip) {
++ if (od->lb_force_snat_addrs.n_ipv4_addrs) {
++ build_lrouter_force_snat_flows(lflows, od, "4",
++ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
++ }
++ if (od->lb_force_snat_addrs.n_ipv6_addrs) {
++ build_lrouter_force_snat_flows(lflows, od, "6",
++ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
+ }
+ }
+- sset_destroy(&all_ips);
++
++ /* For gateway router, re-circulate every packet through
++ * the DNAT zone. This helps with the following.
++ *
++ * Any packet that needs to be unDNATed in the reverse
++ * direction gets unDNATed. Ideally this could be done in
++ * the egress pipeline. But since the gateway router
++ * does not have any feature that depends on the source
++ * ip address being external IP address for IP routing,
++ * we can do it here, saving a future re-circulation. */
++ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
++ "ip", "flags.loopback = 1; ct_dnat;");
++ }
++
++ /* Load balancing and packet defrag are only valid on
++ * Gateway routers or router with gateway port. */
++ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
+ sset_destroy(&nat_entries);
++ return;
+ }
++
++ build_lrouter_lb_flows(lflows, od, lbs, meter_groups, &nat_entries,
++ match, actions);
++
++ sset_destroy(&nat_entries);
+ }
+
+
+diff --git a/ovn-nb.xml b/ovn-nb.xml
+index b0a4adffe..408c98090 100644
+--- a/ovn-nb.xml
++++ b/ovn-nb.xml
+@@ -1653,6 +1653,12 @@
+ exactly one IPv4 and/or one IPv6 address on it, separated by a space
+ character.
+
++
++
++ If the load balancing rule is configured with skip_snat
++ option, the force_snat_for_lb option configured for the router
++ pipeline will not be applied for this load balancer.
++
+
+
+
+diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
+index 2cd3e261f..5c64fff12 100644
+--- a/tests/ovn-controller.at
++++ b/tests/ovn-controller.at
+@@ -431,3 +431,83 @@ OVS_WAIT_UNTIL([
+
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
++
++# Test that changes of a port binding from one type to another doesn'that
++# result in any ovn-controller asserts or crashes.
++AT_SETUP([ovn-controller - port binding type change handling])
++AT_KEYWORDS([ovn])
++ovn_start
++
++net_add n1
++sim_add hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++
++check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1
++
++as hv1
++check ovs-vsctl \
++ -- add-port br-int vif1 \
++ -- set Interface vif1 external_ids:iface-id=lsp1
++
++# ovn-controller should bind the interface.
++wait_for_ports_up
++hv_uuid=$(fetch_column Chassis _uuid name=hv1)
++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
++
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]]
++primary lport : [[lsp1]]
++----------------------------------------
++])
++
++# pause ovn-northd
++check as northd ovn-appctl -t ovn-northd pause
++check as northd-backup ovn-appctl -t ovn-northd pause
++
++as northd ovn-appctl -t ovn-northd status
++as northd-backup ovn-appctl -t ovn-northd status
++
++pb_types=(patch chassisredirect l3gateway localnet localport l2gateway
++ virtual external remote vtep)
++for type in ${pb_types[[@]]}
++do
++ for update_type in ${pb_types[[@]]}
++ do
++ check ovn-sbctl set port_binding lsp1 type=$type
++ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$type
++ OVS_WAIT_UNTIL([test $type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)])
++
++ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]]
++----------------------------------------
++])
++
++ echo "Updating to $update_type from $type"
++ check ovn-sbctl set port_binding lsp1 type=$update_type
++ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$update_type
++ OVS_WAIT_UNTIL([test $update_type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)])
++
++ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]]
++----------------------------------------
++])
++ # Set the port binding type back to VIF.
++ check ovn-sbctl set port_binding lsp1 type=\"\"
++ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=foo
++ OVS_WAIT_UNTIL([test foo = $(ovn-sbctl get chassis . other_config:ovn-cms-options)])
++
++ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]]
++primary lport : [[lsp1]]
++----------------------------------------
++])
++ done
++done
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index b78baa708..6d5dce668 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -2551,7 +2551,7 @@ wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1
+
+ AT_CLEANUP
+
+-AT_SETUP([ovn -- lb_force_snat_ip for Gateway Routers])
++AT_SETUP([ovn -- Load Balancers and lb_force_snat_ip for Gateway Routers])
+ ovn_start
+
+ check ovn-nbctl ls-add sw0
+@@ -2589,11 +2589,11 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
+ ])
+
+-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
+-])
+-
+-
+-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
++ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_dnat;)
++ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_lb(backends=10.0.0.4:8080);)
++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+
+ check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4"
+@@ -2608,14 +2608,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ table=5 (lr_in_unsnat ), priority=110 , match=(ip6 && ip6.dst == aef0::4), action=(ct_snat;)
+ ])
+
+-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+
+-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);)
+ table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);)
++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+ ])
+
+ check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip"
+@@ -2633,15 +2637,19 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
+ ])
+
+-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+
+-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+ ])
+
+ check ovn-nbctl --wait=sb remove logical_router lr0 options chassis
+@@ -2653,7 +2661,9 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
+ ])
+
+-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+ ])
+
+ check ovn-nbctl set logical_router lr0 options:chassis=ch1
+@@ -2670,16 +2680,43 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;)
+ ])
+
+-AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
++ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
++ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+
+-AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
++AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
++ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);)
++ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
++])
++
++check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080
++check ovn-nbctl --wait=sb set load_balancer lb2 options:skip_snat=true
++check ovn-nbctl lr-lb-add lr0 lb2
++check ovn-nbctl --wait=sb lb-del lb1
++ovn-sbctl dump-flows lr0 > lr0flows
++
++AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
++ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;)
++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
++ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;)
++])
++
++AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
++ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_dnat;)
++ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
++])
++
++AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
++ table=1 (lr_out_snat ), priority=120 , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;)
+ ])
+
+ AT_CLEANUP
+diff --git a/tests/ovn.at b/tests/ovn.at
+index b465784cd..dbc6e549b 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -11494,6 +11494,59 @@ OVN_CLEANUP([hv1],[hv2])
+
+ AT_CLEANUP
+
++AT_SETUP([ovn -- localport suppress gARP])
++ovn_start
++
++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 set open . external-ids:ovn-bridge-mappings=phys:br-phys
++
++check ovn-nbctl ls-add ls \
++ -- lsp-add ls lp \
++ -- lsp-set-type lp localport \
++ -- lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1" \
++ -- lsp-add ls ln \
++ -- lsp-set-type ln localnet \
++ -- lsp-set-options ln network_name=phys \
++ -- lsp-add ls lsp \
++ -- lsp-set-addresses lsp "00:00:00:00:00:02 10.0.0.2"
++
++dnl First bind the localport.
++check ovs-vsctl add-port br-int vif1 \
++ -- set Interface vif1 external-ids:iface-id=lp
++check ovn-nbctl --wait=hv sync
++
++dnl Then bind the regular vif.
++check ovs-vsctl add-port br-int vif2 \
++ -- set Interface vif2 external-ids:iface-id=lsp \
++ options:tx_pcap=hv1/vif2-tx.pcap \
++ options:rxq_pcap=hv1/vif2-rx.pcap
++
++wait_for_ports_up lsp
++check ovn-nbctl --wait=hv sync
++
++dnl Wait for at least two gARPs from lsp (10.0.0.2).
++lsp_garp=ffffffffffff000000000002080600010800060400010000000000020a0000020000000000000a000002
++OVS_WAIT_UNTIL([
++ garps=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep ${lsp_garp} -c`
++ test $garps -ge 2
++])
++
++dnl At this point it's safe to assume that ovn-controller skipped sending gARP
++dnl for the localport. Check that there are no other packets than the gARPs
++dnl for the regular vif.
++AT_CHECK([
++ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep -v ${lsp_garp} -c`
++ test 0 -eq $pkts
++])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++
+ AT_SETUP([ovn -- 1 LR with HA distributed router gateway port])
+ ovn_start
+
+@@ -16647,56 +16700,67 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \
+
+ ovn-nbctl ls-add sw0
+
+-ovn-nbctl lsp-add sw0 sw0-vir
+-ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
+-ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
+-ovn-nbctl lsp-set-type sw0-vir virtual
+-ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
+-ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
++check ovn-nbctl lsp-add sw0 sw0-vir
++check ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
++check ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
++check ovn-nbctl lsp-set-type sw0-vir virtual
++check ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
++check ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
+
+-ovn-nbctl lsp-add sw0 sw0-p1
+-ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+-ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10"
++check ovn-nbctl lsp-add sw0 sw0-p1
++check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3"
++check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10"
+
+-ovn-nbctl lsp-add sw0 sw0-p2
+-ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+-ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10"
++check ovn-nbctl lsp-add sw0 sw0-p2
++check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
++check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10"
+
+-ovn-nbctl lsp-add sw0 sw0-p3
+-ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5"
+-ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10"
++check ovn-nbctl lsp-add sw0 sw0-p3
++check ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5"
++check ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10"
+
+ # Create the second logical switch with one port
+-ovn-nbctl ls-add sw1
+-ovn-nbctl lsp-add sw1 sw1-p1
+-ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
+-ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
++check ovn-nbctl ls-add sw1
++check ovn-nbctl lsp-add sw1 sw1-p1
++check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
++check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
+
+ # Create a logical router and attach both logical switches
+-ovn-nbctl lr-add lr0
+-ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+-ovn-nbctl lsp-add sw0 sw0-lr0
+-ovn-nbctl lsp-set-type sw0-lr0 router
+-ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
+-ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
++check ovn-nbctl lsp-add sw0 sw0-lr0
++check ovn-nbctl lsp-set-type sw0-lr0 router
++check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
++check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+-ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
+-ovn-nbctl lsp-add sw1 sw1-lr0
+-ovn-nbctl lsp-set-type sw1-lr0 router
+-ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
+-ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
++check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
++check ovn-nbctl lsp-add sw1 sw1-lr0
++check ovn-nbctl lsp-set-type sw1-lr0 router
++check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
++check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+-OVN_POPULATE_ARP
++# Add an ACL that matches on sw0-vir being bound locally.
++check ovn-nbctl acl-add sw0 to-lport 1000 'is_chassis_resident("sw0-vir") && ip' allow
+
+-# Delete sw0-vir and add again.
+-ovn-nbctl lsp-del sw0-vir
++check ovn-nbctl ls-add public
++check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/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
+
+-ovn-nbctl lsp-add sw0 sw0-vir
+-ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
+-ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
+-ovn-nbctl lsp-set-type sw0-vir virtual
+-ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
+-ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
++# localnet port
++check ovn-nbctl lsp-add public ln-public
++check ovn-nbctl lsp-set-type ln-public localnet
++check ovn-nbctl lsp-set-addresses ln-public unknown
++check ovn-nbctl lsp-set-options ln-public network_name=public
++
++# schedule the gw router port to a chassis. Change the name of the chassis
++check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
++
++check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.50 10.0.0.10 sw0-vir 10:54:00:00:00:10
++
++OVN_POPULATE_ARP
+
+ wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+@@ -16746,6 +16810,30 @@ ovs-vsctl del-port hv1-vif3
+ AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+ logical_port=sw0-vir) = x], [0], [])
+
++check_virtual_offlows_present() {
++ hv=$1
++
++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [0], [dnl
++ table=45, priority=2000,ip,metadata=0x1 actions=resubmit(,46)
++ table=45, priority=2000,ipv6,metadata=0x1 actions=resubmit(,46)
++])
++
++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
++ grep "priority=92" | grep 172.168.0.50], [0], [dnl
++ table=11, priority=92,arp,reg14=0x3,metadata=0x3,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],move:NXM_OF_ARP_SPA[[]]->NXM_OF_ARP_TPA[[]],load:0xaca80032->NXM_OF_ARP_SPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37)
++])
++}
++
++check_virtual_offlows_not_present() {
++ hv=$1
++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [1], [dnl
++])
++
++ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
++ grep "priority=92" | grep 172.168.0.50], [1], [dnl
++])
++}
++
+ # From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir
+ # and sw0-p1 should be its virtual_parent.
+ eth_src=505400000003
+@@ -16767,6 +16855,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/
+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
+ ])
+
++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
+ # Forcibly clear virtual_parent. ovn-controller should release the binding
+ # gracefully.
+ pb_uuid=$(ovn-sbctl --bare --columns _uuid find port_binding logical_port=sw0-vir)
+@@ -16777,6 +16872,13 @@ logical_port=sw0-vir) = x])
+
+ wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir
+
++check ovn-nbctl --wait=hv sync
++# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir.
++check_virtual_offlows_not_present hv1
++
++# hv2 should not have the flow for ACL.
++check_virtual_offlows_not_present hv2
++
+ # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir
+ # and sw0-p1 should be its virtual_parent.
+ send_garp 1 1 $eth_src $eth_dst $spa $tpa
+@@ -16789,6 +16891,58 @@ logical_port=sw0-vir) = xsw0-p1])
+
+ wait_for_ports_up sw0-vir
+
++check ovn-nbctl --wait=hv sync
++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
++# Release sw0-p1.
++as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-px
++wait_column "false" nb:Logical_Switch_Port up name=sw0-p1
++wait_column "false" nb:Logical_Switch_Port up name=sw0-vir
++
++check ovn-nbctl --wait=hv sync
++# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_not_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
++# Claim sw0-p1 again.
++as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-p1
++wait_for_ports_up sw0-p1
++
++# hv1 should not have the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_not_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
++# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir
++# and sw0-p1 should be its virtual_parent.
++eth_src=505400000003
++eth_dst=ffffffffffff
++spa=$(ip_to_hex 10 0 0 10)
++tpa=$(ip_to_hex 10 0 0 10)
++send_garp 1 1 $eth_src $eth_dst $spa $tpa
++
++wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid
++check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1
++wait_for_ports_up sw0-vir
++check ovn-nbctl --wait=hv sync
++
++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
+ # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir
+ # and sw0-p3 should be its virtual_parent.
+ eth_src=505400000005
+@@ -16806,8 +16960,8 @@ logical_port=sw0-vir) = xsw0-p3])
+ wait_for_ports_up sw0-vir
+
+ # There should be an arp resolve flow to resolve the virtual_ip with the
+-# sw0-p2's MAC.
+-sleep 1
++# sw0-p3's MAC.
++check ovn-nbctl --wait=hv sync
+ ovn-sbctl dump-flows lr0 > lr0-flows3
+ AT_CAPTURE_FILE([lr0-flows3])
+ cp ovn-sb/ovn-sb.db lr0-flows3.db
+@@ -16815,6 +16969,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's
+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:05; next;)
+ ])
+
++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
+ # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir
+ # and sw0-p2 shpuld be its virtual_parent.
+ eth_src=505400000004
+@@ -16832,14 +16993,21 @@ logical_port=sw0-vir) = xsw0-p2])
+ wait_for_ports_up sw0-vir
+
+ # There should be an arp resolve flow to resolve the virtual_ip with the
+-# sw0-p3's MAC.
+-sleep 1
++# sw0-p2's MAC.
++check ovn-nbctl --wait=hv sync
+ ovn-sbctl dump-flows lr0 > lr0-flows4
+ AT_CAPTURE_FILE([lr0-flows4])
+ AT_CHECK([grep lr_in_arp_resolve lr0-flows4 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl
+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
+ ])
+
++# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_present hv2
++
++# hv1 should not have the above flows.
++check_virtual_offlows_not_present hv1
++
+ # Now send arp reply from sw0-p1. hv1 should claim sw0-vir
+ # and sw0-p1 shpuld be its virtual_parent.
+ eth_src=505400000003
+@@ -16863,6 +17031,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/
+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
+ ])
+
++check ovn-nbctl --wait=hv sync
++# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
+ # Delete hv1-vif1 port. hv1 should release sw0-vir
+ as hv1 ovs-vsctl del-port hv1-vif1
+
+@@ -16883,6 +17059,15 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/
+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
+ ])
+
++check ovn-nbctl --wait=hv sync
++# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_not_present hv1
++
++# hv2 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
++
+ # Now send arp reply from sw0-p2. hv2 should claim sw0-vir
+ # and sw0-p2 should be its virtual_parent.
+ eth_src=505400000004
+@@ -16906,6 +17091,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/
+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
+ ])
+
++check ovn-nbctl --wait=hv sync
++# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_present hv2
++
++# hv1 should not have the above flows.
++check_virtual_offlows_not_present hv1
++
+ # Delete sw0-p2 logical port
+ ovn-nbctl lsp-del sw0-p2
+
+@@ -16933,6 +17126,14 @@ AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../tabl
+ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
+ ])
+
++check ovn-nbctl --wait=hv sync
++# hv2 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and
++# arp responder flow in lr0 pipeline.
++check_virtual_offlows_not_present hv2
++
++# hv1 should not have the above flows.
++check_virtual_offlows_not_present hv2
++
+ ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents
+ ovn-sbctl dump-flows sw0 > sw0-flows4
+ AT_CAPTURE_FILE([sw0-flows4])
+@@ -16942,6 +17143,38 @@ ovn-sbctl dump-flows lr0 > lr0-flows8
+ AT_CAPTURE_FILE([lr0-flows8])
+ AT_CHECK([grep lr_in_arp_resolve lr0-flows8 | grep "reg0 == 10.0.0.10"], [1])
+
++# Delete sw0-vir and add again.
++ovn-nbctl lsp-del sw0-vir
++
++ovn-nbctl lsp-add sw0 sw0-vir
++ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
++ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
++ovn-nbctl lsp-set-type sw0-vir virtual
++ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
++ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
++
++ovn-nbctl --wait=hv sync
++
++# Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline
++# with bind_vport action.
++
++ovn-sbctl dump-flows sw0 > sw0-flows
++AT_CAPTURE_FILE([sw0-flows])
++
++AT_CHECK([grep ls_in_arp_rsp sw0-flows | grep bind_vport | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
++ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
++])
++
++ovn-sbctl dump-flows lr0 > lr0-flows
++AT_CAPTURE_FILE([lr0-flows])
++
++# Since the sw0-vir is not claimed by any chassis, eth.dst should be set to
++# zero if the ip4.dst is the virtual ip in the router pipeline.
++AT_CHECK([grep lr_in_arp_resolve lr0-flows | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl
++ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
++])
++
+ OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
+
+@@ -24918,3 +25151,633 @@ AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl
+
+ OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
++
++AT_SETUP([ovn -- container port changed to normal port and then deleted])
++ovn_start
++
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovs-vsctl -- add-port br-int vm1
++
++check ovn-nbctl ls-add ls
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl lsp-add ls vm-cont vm1 1
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++
++wait_for_ports_up
++
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl clear logical_switch_port vm-cont parent_name
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo
++check ovn-nbctl lsp-del vm-cont
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++ovn-nbctl --wait=hv sync
++
++# Make sure that ovn-controller has not asserted.
++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
++
++wait_column "false" nb:Logical_Switch_Port up name=vm1
++
++check ovn-nbctl lsp-add ls vm-cont1 vm1 1
++check ovn-nbctl lsp-add ls vm-cont2 vm1 2
++
++check ovn-nbctl --wait=sb lsp-del vm1
++
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
++check ovn-nbctl clear logical_switch_port vm-cont2 parent_name
++
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++check ovn-nbctl --wait=hv sync
++
++# Make sure that ovn-controller has not crashed.
++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
++
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1
++check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++
++wait_for_ports_up
++
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl --wait=sb lsp-del vm1
++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
++check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name
++check ovn-nbctl lsp-del vm-cont1
++check ovn-nbctl --wait=sb lsp-del vm-cont2
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++check ovn-nbctl --wait=hv sync
++
++# Make sure that ovn-controller has not crashed.
++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
++
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl lsp-add ls vm-cont1 vm1 1
++check ovn-nbctl lsp-add ls vm-cont2 vm1 2
++
++wait_for_ports_up
++
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
++check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name
++check ovn-nbctl lsp-del vm-cont1
++check ovn-nbctl lsp-del vm-cont2
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++check ovn-nbctl --wait=hv sync
++
++# Make sure that ovn-controller has not crashed.
++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
++
++check ovn-nbctl lsp-add ls vm-cont1 vm1 1
++check ovn-nbctl lsp-add ls vm-cont2 vm1 2
++
++wait_for_ports_up
++
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
++check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name
++
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo
++
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++wait_column "false" nb:Logical_Switch_Port up name=vm1
++wait_column "false" nb:Logical_Switch_Port up name=vm-cont1
++wait_column "false" nb:Logical_Switch_Port up name=vm-cont2
++
++check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1
++
++wait_for_ports_up
++
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm-cont1
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++wait_column "false" nb:Logical_Switch_Port up name=vm1
++wait_column "true" nb:Logical_Switch_Port up name=vm-cont1
++wait_column "false" nb:Logical_Switch_Port up name=vm-cont2
++
++check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm-cont1
++check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=vm-cont1
++
++wait_for_ports_up
++
++# Delete vm1, vm-cont1 and vm-cont2 and recreate again.
++check ovn-nbctl lsp-del vm1
++check ovn-nbctl lsp-del vm-cont1
++check ovn-nbctl --wait=hv lsp-del vm-cont2
++
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl lsp-add ls vm-cont1 vm1 1
++check ovn-nbctl lsp-add ls vm-cont2 vm1 2
++
++wait_for_ports_up
++
++# Make vm1 as a child port of some non existent lport - foo. vm1, vm1-cont1 and
++# vm1-cont2 should be released.
++check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=bar
++wait_column "false" nb:Logical_Switch_Port up name=vm1
++wait_column "false" nb:Logical_Switch_Port up name=vm-cont1
++wait_column "false" nb:Logical_Switch_Port up name=vm-cont2
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++
++AT_SETUP([ovn -- container port changed from one parent to another])
++ovn_start
++
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovs-vsctl -- add-port br-int vm1 -- set interface vm1 ofport-request=1
++ovs-vsctl -- add-port br-int vm2 -- set interface vm1 ofport-request=2
++
++check ovn-nbctl ls-add ls
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl lsp-add ls vm1-cont vm1 1
++check ovn-nbctl lsp-add ls vm2
++check ovn-nbctl lsp-add ls vm2-cont vm2 2
++
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++check as hv1 ovs-vsctl set Interface vm2 external_ids:iface-id=vm2
++
++wait_for_ports_up
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [0], [dnl
++1
++])
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl
++1
++])
++
++# change the parent of vm1-cont to vm2.
++as hv1 ovn-appctl -t ovn-controller vlog/set dbg
++check ovn-nbctl --wait=sb set logical_switch_port vm1-cont parent_name=vm2 \
++-- set logical_switch_port vm1-cont tag_request=3
++
++wait_for_ports_up
++
++check ovn-nbctl --wait=hv sync
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [1], [dnl
++0
++])
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl
++1
++])
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=3], [0], [dnl
++1
++])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++
++AT_SETUP([ovn -- container port use-after-free test])
++ovn_start
++
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovs-vsctl -- add-port br-int vm1
++
++check ovn-nbctl ls-add ls
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl lsp-add ls vm-cont vm1 1
++check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++check ovn-nbctl clear logical_switch_port vm-cont parent_name
++check ovs-vsctl set Interface vm1 external_ids:iface-id=foo
++check ovn-nbctl lsp-del vm-cont
++check ovn-nbctl ls-del ls
++check ovn-nbctl ls-add ls
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl lsp-add ls vm-cont vm1 1
++check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl clear logical_switch_port vm-cont parent_name
++check ovn-nbctl lsp-del vm-cont
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo
++
++ovn-nbctl --wait=hv sync
++
++# Make sure that ovn-controller has not asserted.
++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
++
++wait_column "false" nb:Logical_Switch_Port up name=vm1
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++
++# Test that OVS.external_ids:iface-id doesn't affect non-VIF port bindings.
++AT_SETUP([ovn -- Non-VIF ports incremental processing])
++ovn_start
++
++net_add n1
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.10
++
++check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1
++
++as hv1
++check ovs-vsctl \
++ -- add-port br-int vif1 \
++ -- set Interface vif1 external_ids:iface-id=lsp1
++
++# ovn-controller should bind the interface.
++wait_for_ports_up
++hv_uuid=$(fetch_column Chassis _uuid name=hv1)
++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
++
++# Change the port type to router, ovn-controller should release it.
++check ovn-nbctl --wait=hv lsp-set-type lsp1 router
++check_column "" Port_Binding chassis logical_port=lsp1
++
++# Clear port type, ovn-controller should rebind it.
++check ovn-nbctl --wait=hv lsp-set-type lsp1 ''
++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
++
++# Change the port type to localnet, ovn-controller should release it.
++check ovn-nbctl --wait=hv lsp-set-type lsp1 localnet
++check_column "" Port_Binding chassis logical_port=lsp1
++
++# Clear port type, ovn-controller should rebind it.
++check ovn-nbctl --wait=hv lsp-set-type lsp1 ''
++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
++
++# Change the port type to localport, ovn-controller should release it.
++check ovn-nbctl --wait=hv lsp-set-type lsp1 localport
++check_column "" Port_Binding chassis logical_port=lsp1
++
++# Clear port type, ovn-controller should rebind it.
++check ovn-nbctl --wait=hv lsp-set-type lsp1 ''
++check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
++
++# Change the port type to localnet and then delete it.
++# ovn-controller should handle this properly.
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl --wait=sb lsp-set-type lsp1 localport
++check ovn-nbctl --wait=sb lsp-del lsp1
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++check ovn-nbctl --wait=hv sync
++
++# Make sure that ovn-controller has not asserted.
++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
++
++check ovn-nbctl lsp-add ls1 lsp1
++wait_for_ports_up
++
++# Change the port type to virtual and then delete it.
++# ovn-controller should handle this properly.
++check as hv1 ovn-appctl -t ovn-controller debug/pause
++check ovn-nbctl --wait=sb lsp-set-type lsp1 virtual
++check ovn-nbctl --wait=sb lsp-del lsp1
++check as hv1 ovn-appctl -t ovn-controller debug/resume
++
++check ovn-nbctl --wait=hv sync
++
++# Make sure that ovn-controller has not asserted.
++AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++
++# Tests that ovn-controller creates local bindings correctly by running
++# ovn-appctl -t ovn-controller debug/dump-local-bindings.
++# Ideally this test case should have been a unit test case.
++AT_SETUP([ovn -- ovn-controller local bindings])
++ovn_start
++
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovs-vsctl -- add-port br-int hv1-vm1
++
++sim_add hv2
++as hv2
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.2
++ovs-vsctl -- add-port br-int hv2-vm1
++
++check ovn-nbctl ls-add sw0
++check ovn-nbctl lsp-add sw0 sw0p1
++check ovn-nbctl lsp-add sw0 sw0p2
++
++check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1
++check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2
++
++wait_for_ports_up
++
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p1]]
++----------------------------------------
++])
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p2]]
++----------------------------------------
++])
++
++# Create an ovs interface in hv1
++check as hv1 ovs-vsctl add-port br-int hv1-vm2 -- set interface hv1-vm2 external_ids:iface-id=sw1p1
++check ovn-nbctl --wait=hv sync
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p1]]
++----------------------------------------
++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[0]]
++----------------------------------------
++])
++
++# Create lport sw1p1
++check ovn-nbctl ls-add sw1 -- lsp-add sw1 sw1p1
++
++wait_for_ports_up
++
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p1]]
++----------------------------------------
++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
++primary lport : [[sw1p1]]
++----------------------------------------
++])
++
++# Swap sw0p1 and sw0p2.
++check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p2
++check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p1
++
++check ovn-nbctl --wait=hv sync
++
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p2]]
++----------------------------------------
++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
++primary lport : [[sw1p1]]
++----------------------------------------
++])
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p1]]
++----------------------------------------
++])
++
++# Create child port for sw0p1
++check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c1 sw0p1 1
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
++primary lport : [[sw0p1]]
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p2]]
++----------------------------------------
++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
++primary lport : [[sw1p1]]
++----------------------------------------
++])
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[2]]
++primary lport : [[sw0p1]]
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++----------------------------------------
++])
++
++# Create another child port for sw0p1
++check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c2 sw0p1 2
++
++wait_for_ports_up
++
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]]
++primary lport : [[sw0p1]]
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p2]]
++----------------------------------------
++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
++primary lport : [[sw1p1]]
++----------------------------------------
++])
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[3]]
++primary lport : [[sw0p1]]
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++])
++
++# Swap sw0p1 and sw0p2 again.
++check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1
++check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2
++
++check ovn-nbctl --wait=hv sync
++
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[3]]
++primary lport : [[sw0p1]]
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
++primary lport : [[sw1p1]]
++----------------------------------------
++])
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]]
++primary lport : [[sw0p1]]
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p2]]
++----------------------------------------
++])
++
++# Make sw0p1 as child port of non existent lport - foo
++check ovn-nbctl --wait=hv set logical_switch_port sw0p1 parent_name=foo
++
++AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
++no primary lport
++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[2]]
++no primary lport
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
++primary lport : [[sw1p1]]
++----------------------------------------
++])
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
++no primary lport
++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
++no primary lport
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p2]]
++----------------------------------------
++])
++
++# Change the lport type of sw0p2 to different types and make sure that
++# local bindings are correct.
++
++hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
++check_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2
++
++# Change the port type to router, ovn-controller should release it.
++check ovn-nbctl --wait=hv lsp-set-type sw0p2 router
++check_column "" Port_Binding chassis logical_port=sw0p2
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
++no primary lport
++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
++no primary lport
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
++----------------------------------------
++])
++
++# change the port type to external from router.
++check ovn-nbctl --wait=hv lsp-set-type sw0p2 external
++check_column "" Port_Binding chassis logical_port=sw0p2
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
++no primary lport
++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
++no primary lport
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
++----------------------------------------
++])
++
++# change the port type to localnet from external.
++check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet
++check_column "" Port_Binding chassis logical_port=sw0p2
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
++no primary lport
++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
++no primary lport
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
++----------------------------------------
++])
++
++# change the port type to localport from localnet.
++check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet
++check_column "" Port_Binding chassis logical_port=sw0p2
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
++no primary lport
++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
++no primary lport
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
++----------------------------------------
++])
++
++# change the port type back to vif.
++check ovn-nbctl --wait=hv lsp-set-type sw0p2 ""
++wait_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2
++
++AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
++Local bindings:
++name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
++no primary lport
++child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
++no primary lport
++child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
++child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
++----------------------------------------
++name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
++primary lport : [[sw0p2]]
++----------------------------------------
++])
++
++OVN_CLEANUP([hv1], [hv2])
++AT_CLEANUP
+diff --git a/utilities/ovndb-servers.ocf b/utilities/ovndb-servers.ocf
+index 7351c7d64..eba9c97a1 100755
+--- a/utilities/ovndb-servers.ocf
++++ b/utilities/ovndb-servers.ocf
+@@ -259,6 +259,9 @@ ovsdb_server_notify() {
+ ovn-nbctl -- --id=@conn_uuid create Connection \
+ target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" \
+ inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid
++ else
++ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn})
++ ovn-nbctl set connection "${CONN_UID}" target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}"
+ fi
+
+ conn=`ovn-sbctl get SB_global . connections`
+@@ -267,6 +270,9 @@ inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid
+ ovn-sbctl -- --id=@conn_uuid create Connection \
+ target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" \
+ inactivity_probe=$INACTIVE_PROBE -- set SB_Global . connections=@conn_uuid
++ else
++ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn})
++ ovn-sbctl set connection "${CONN_UID}" target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}"
+ fi
+
+ else
diff --git a/SPECS/ovn-2021.spec b/SPECS/ovn-2021.spec
new file mode 100644
index 0000000..b85781f
--- /dev/null
+++ b/SPECS/ovn-2021.spec
@@ -0,0 +1,618 @@
+# Copyright (C) 2009, 2010, 2013, 2014 Nicira Networks, Inc.
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved. This file is offered as-is,
+# without warranty of any kind.
+#
+# If tests have to be skipped while building, specify the '--without check'
+# option. For example:
+# rpmbuild -bb --without check rhel/openvswitch-fedora.spec
+
+# This defines the base package name's version.
+
+%define pkgver 2.13
+%define pkgname ovn-2021
+
+# If libcap-ng isn't available and there is no need for running OVS
+# as regular user, specify the '--without libcapng'
+%bcond_without libcapng
+
+# Enable PIE, bz#955181
+%global _hardened_build 1
+
+# RHEL-7 doesn't define _rundir macro yet
+# Fedora 15 onwards uses /run as _rundir
+%if 0%{!?_rundir:1}
+%define _rundir /run
+%endif
+
+# Build python2 (that provides python) and python3 subpackages on Fedora
+# Build only python3 (that provides python) subpackage on RHEL8
+# Build only python subpackage on RHEL7
+%if 0%{?rhel} > 7 || 0%{?fedora}
+# On RHEL8 Sphinx is included in buildroot
+%global external_sphinx 1
+%else
+# Don't use external sphinx (RHV doesn't have optional repositories enabled)
+%global external_sphinx 0
+%endif
+
+# We would see rpmlinit error - E: hardcoded-library-path in '% {_prefix}/lib'.
+# But there is no solution to fix this. Using {_lib} macro will solve the
+# rpmlink error, but will install the files in /usr/lib64/.
+# OVN pacemaker ocf script file is copied in /usr/lib/ocf/resource.d/ovn/
+# and we are not sure if pacemaker looks into this path to find the
+# OVN resource agent script.
+%global ovnlibdir %{_prefix}/lib
+
+Name: %{pkgname}
+Summary: Open Virtual Network support
+Group: System Environment/Daemons
+URL: http://www.ovn.org/
+Version: 21.03.0
+Release: 21%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
+
+# Nearly all of openvswitch is ASL 2.0. The bugtool is LGPLv2+, and the
+# lib/sflow*.[ch] files are SISSL
+License: ASL 2.0 and LGPLv2+ and SISSL
+
+# Always pull an upstream release, since this is what we rebase to.
+Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz
+
+%define ovscommit ac85cdb38c1f33e7952bc4c0347d6c7873fb56a1
+%define ovsshortcommit ac85cdb
+
+Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz
+%define ovsdir ovs-%{ovscommit}
+
+%define docutilsver 0.12
+%define pygmentsver 1.4
+%define sphinxver 1.1.3
+Source100: https://pypi.io/packages/source/d/docutils/docutils-%{docutilsver}.tar.gz
+Source101: https://pypi.io/packages/source/P/Pygments/Pygments-%{pygmentsver}.tar.gz
+Source102: https://pypi.io/packages/source/S/Sphinx/Sphinx-%{sphinxver}.tar.gz
+
+Source500: configlib.sh
+Source501: gen_config_group.sh
+Source502: set_config.sh
+
+# Important: source503 is used as the actual copy file
+# @TODO: this causes a warning - fix it?
+Source504: arm64-armv8a-linuxapp-gcc-config
+Source505: ppc_64-power8-linuxapp-gcc-config
+Source506: x86_64-native-linuxapp-gcc-config
+
+Patch: ovn-%{version}.patch
+
+# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's
+# in the -optional repository and so we can't require it directly since RHV
+# doesn't have the -optional repository enabled and so TPS fails
+%if %{external_sphinx}
+BuildRequires: python3-sphinx
+%else
+# Sphinx dependencies
+BuildRequires: python-devel
+BuildRequires: python-setuptools
+#BuildRequires: python2-docutils
+BuildRequires: python-jinja2
+BuildRequires: python-nose
+#BuildRequires: python2-pygments
+# docutils dependencies
+BuildRequires: python-imaging
+# pygments dependencies
+BuildRequires: python-nose
+%endif
+
+BuildRequires: gcc gcc-c++ make
+BuildRequires: autoconf automake libtool
+BuildRequires: systemd-units openssl openssl-devel
+BuildRequires: python3-devel python3-setuptools
+BuildRequires: desktop-file-utils
+BuildRequires: groff-base graphviz
+BuildRequires: unbound-devel
+
+# make check dependencies
+BuildRequires: procps-ng
+%if 0%{?rhel} > 7 || 0%{?fedora}
+BuildRequires: python3-pyOpenSSL
+%endif
+
+%if %{with libcapng}
+BuildRequires: libcap-ng libcap-ng-devel
+%endif
+
+Requires: hostname openssl iproute module-init-tools
+
+Requires(post): systemd-units
+Requires(preun): systemd-units
+Requires(postun): systemd-units
+
+# to skip running checks, pass --without check
+%bcond_without check
+
+%description
+OVN, the Open Virtual Network, is a system to support virtual network
+abstraction. OVN complements the existing capabilities of OVS to add
+native support for virtual network abstractions, such as virtual L2 and L3
+overlays and security groups.
+
+%package central
+Summary: Open Virtual Network support
+License: ASL 2.0
+Requires: %{pkgname}
+Requires: firewalld-filesystem
+Provides: openvswitch%{pkgver}-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-central < 2.11.0-1
+
+%description central
+OVN DB servers and ovn-northd running on a central node.
+
+%package host
+Summary: Open Virtual Network support
+License: ASL 2.0
+Requires: %{pkgname}
+Requires: firewalld-filesystem
+Provides: openvswitch%{pkgver}-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-host < 2.11.0-1
+
+%description host
+OVN controller running on each host.
+
+%package vtep
+Summary: Open Virtual Network support
+License: ASL 2.0
+Requires: %{pkgname}
+Provides: openvswitch%{pkgver}-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-vtep < 2.11.0-1
+
+%description vtep
+OVN vtep controller
+
+%prep
+%if 0%{?commit0:1}
+%autosetup -n ovn-%{commit0} -a 10 -p 1
+%else
+%autosetup -n ovn-%{version} -a 10 -p 1
+%endif
+
+%build
+%if 0%{?commit0:1}
+# fix the snapshot unreleased version to be the released one.
+sed -i.old -e "s/^AC_INIT(openvswitch,.*,/AC_INIT(openvswitch, %{version},/" configure.ac
+%endif
+./boot.sh
+
+# OVN source code is now separate.
+# Build openvswitch first.
+# XXX Current openvswitch2.13 doesn't
+# use "2.13.0" for version. It's a commit hash
+pushd %{ovsdir}
+./boot.sh
+%configure \
+%if %{with libcapng}
+ --enable-libcapng \
+%else
+ --disable-libcapng \
+%endif
+ --enable-ssl \
+ --with-pkidir=%{_sharedstatedir}/openvswitch/pki
+
+make %{?_smp_mflags}
+popd
+
+# Build OVN.
+# XXX OVS version needs to be updated when ovs2.13 is updated.
+%configure \
+ --with-ovs-source=$PWD/%{ovsdir} \
+%if %{with libcapng}
+ --enable-libcapng \
+%else
+ --disable-libcapng \
+%endif
+ --enable-ssl \
+ --with-pkidir=%{_sharedstatedir}/openvswitch/pki
+
+make %{?_smp_mflags}
+
+%install
+%make_install
+install -p -D -m 0644 \
+ rhel/usr_share_ovn_scripts_systemd_sysconfig.template \
+ $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/ovn
+
+for service in ovn-controller ovn-controller-vtep ovn-northd; do
+ install -p -D -m 0644 \
+ rhel/usr_lib_systemd_system_${service}.service \
+ $RPM_BUILD_ROOT%{_unitdir}/${service}.service
+done
+
+install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/ovn
+
+install -d $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/
+install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
+ $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
+install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \
+ $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
+
+install -d -m 0755 $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn
+ln -s %{_datadir}/ovn/scripts/ovndb-servers.ocf \
+ $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
+
+install -p -D -m 0644 rhel/etc_logrotate.d_ovn \
+ $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/ovn
+
+# remove unneeded files.
+rm -f $RPM_BUILD_ROOT%{_bindir}/ovs*
+rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl
+rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep*
+rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/python
+rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovs*
+rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/bugtool-plugins
+rm -f $RPM_BUILD_ROOT%{_libdir}/*.a
+rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
+rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc
+rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/*
+rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash
+rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash
+rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch
+rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovn-bugtool*
+rm -f $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-overlay-driver \
+ $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-underlay-driver
+
+%check
+%if %{with check}
+ touch resolv.conf
+ export OVS_RESOLV_CONF=$(pwd)/resolv.conf
+ if ! make check TESTSUITEFLAGS='%{_smp_mflags} -k ovn'; then
+ cat tests/testsuite.log
+ if ! make check TESTSUITEFLAGS='--recheck'; then
+ cat tests/testsuite.log
+ # Presently a test case - "2796: ovn -- ovn-controller incremental processing"
+ # is failing on aarch64 arch. Let's not exit for this arch
+ # until we figure out why it is failing.
+ # Test case 93: ovn.at:12105 ovn -- ACLs on Port Groups is failing
+ # repeatedly on s390x. This needs to be investigated.
+ %ifnarch aarch64
+ %ifnarch ppc64le
+ %ifnarch s390x
+ exit 1
+ %endif
+ %endif
+ %endif
+ fi
+ fi
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%pre central
+if [ $1 -eq 1 ] ; then
+ # Package install.
+ /bin/systemctl status ovn-northd.service >/dev/null
+ ovn_status=$?
+ rpm -ql openvswitch-ovn-central > /dev/null
+ if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
+ # ovn-northd service is running which means old openvswitch-ovn-central
+ # is already installed and it will be cleaned up. So start ovn-northd
+ # service when posttrans central is called.
+ touch %{_localstatedir}/lib/rpm-state/ovn-northd
+ fi
+fi
+
+%pre host
+if [ $1 -eq 1 ] ; then
+ # Package install.
+ /bin/systemctl status ovn-controller.service >/dev/null
+ ovn_status=$?
+ rpm -ql openvswitch-ovn-host > /dev/null
+ if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
+ # ovn-controller service is running which means old
+ # openvswitch-ovn-host is installed and it will be cleaned up. So
+ # start ovn-controller service when posttrans host is called.
+ touch %{_localstatedir}/lib/rpm-state/ovn-controller
+ fi
+fi
+
+%pre vtep
+if [ $1 -eq 1 ] ; then
+ # Package install.
+ /bin/systemctl status ovn-controller-vtep.service >/dev/null
+ ovn_status=$?
+ rpm -ql openvswitch-ovn-vtep > /dev/null
+ if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
+ # ovn-controller-vtep service is running which means old
+ # openvswitch-ovn-vtep is installed and it will be cleaned up. So
+ # start ovn-controller-vtep service when posttrans host is called.
+ touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
+ fi
+fi
+
+%preun central
+%if 0%{?systemd_preun:1}
+ %systemd_preun ovn-northd.service
+%else
+ if [ $1 -eq 0 ] ; then
+ # Package removal, not upgrade
+ /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || :
+ /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || :
+ fi
+%endif
+
+%preun host
+%if 0%{?systemd_preun:1}
+ %systemd_preun ovn-controller.service
+%else
+ if [ $1 -eq 0 ] ; then
+ # Package removal, not upgrade
+ /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || :
+ /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || :
+ fi
+%endif
+
+%preun vtep
+%if 0%{?systemd_preun:1}
+ %systemd_preun ovn-controller-vtep.service
+%else
+ if [ $1 -eq 0 ] ; then
+ # Package removal, not upgrade
+ /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || :
+ /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || :
+ fi
+%endif
+
+%post
+%if %{with libcapng}
+if [ $1 -eq 1 ]; then
+ sed -i 's:^#OVN_USER_ID=:OVN_USER_ID=:' %{_sysconfdir}/sysconfig/ovn
+ sed -i 's:\(.*su\).*:\1 openvswitch openvswitch:' %{_sysconfdir}/logrotate.d/ovn
+fi
+%endif
+
+%post central
+%if 0%{?systemd_post:1}
+ %systemd_post ovn-northd.service
+%else
+ # Package install, not upgrade
+ if [ $1 -eq 1 ]; then
+ /bin/systemctl daemon-reload >dev/null || :
+ fi
+%endif
+
+%post host
+%if 0%{?systemd_post:1}
+ %systemd_post ovn-controller.service
+%else
+ # Package install, not upgrade
+ if [ $1 -eq 1 ]; then
+ /bin/systemctl daemon-reload >dev/null || :
+ fi
+%endif
+
+%post vtep
+%if 0%{?systemd_post:1}
+ %systemd_post ovn-controller-vtep.service
+%else
+ # Package install, not upgrade
+ if [ $1 -eq 1 ]; then
+ /bin/systemctl daemon-reload >dev/null || :
+ fi
+%endif
+
+%postun
+
+%postun central
+%if 0%{?systemd_postun_with_restart:1}
+ %systemd_postun_with_restart ovn-northd.service
+%else
+ /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+ if [ "$1" -ge "1" ] ; then
+ # Package upgrade, not uninstall
+ /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || :
+ fi
+%endif
+
+%postun host
+%if 0%{?systemd_postun_with_restart:1}
+ %systemd_postun_with_restart ovn-controller.service
+%else
+ /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+ if [ "$1" -ge "1" ] ; then
+ # Package upgrade, not uninstall
+ /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || :
+ fi
+%endif
+
+%postun vtep
+%if 0%{?systemd_postun_with_restart:1}
+ %systemd_postun_with_restart ovn-controller-vtep.service
+%else
+ /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+ if [ "$1" -ge "1" ] ; then
+ # Package upgrade, not uninstall
+ /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || :
+ fi
+%endif
+
+%posttrans central
+if [ $1 -eq 1 ]; then
+ # Package install, not upgrade
+ if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then
+ rm %{_localstatedir}/lib/rpm-state/ovn-northd
+ /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || :
+ fi
+fi
+
+
+%posttrans host
+if [ $1 -eq 1 ]; then
+ # Package install, not upgrade
+ if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then
+ rm %{_localstatedir}/lib/rpm-state/ovn-controller
+ /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || :
+ fi
+fi
+
+%posttrans vtep
+if [ $1 -eq 1 ]; then
+ # Package install, not upgrade
+ if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then
+ rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
+ /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || :
+ fi
+fi
+
+%files
+%{_bindir}/ovn-nbctl
+%{_bindir}/ovn-sbctl
+%{_bindir}/ovn-trace
+%{_bindir}/ovn-detrace
+%{_bindir}/ovn-appctl
+%{_bindir}/ovn-ic-nbctl
+%{_bindir}/ovn-ic-sbctl
+%dir %{_datadir}/ovn/
+%dir %{_datadir}/ovn/scripts/
+%{_datadir}/ovn/scripts/ovn-ctl
+%{_datadir}/ovn/scripts/ovn-lib
+%{_datadir}/ovn/scripts/ovndb-servers.ocf
+%{_mandir}/man8/ovn-ctl.8*
+%{_mandir}/man8/ovn-appctl.8*
+%{_mandir}/man8/ovn-nbctl.8*
+%{_mandir}/man8/ovn-ic-nbctl.8*
+%{_mandir}/man8/ovn-trace.8*
+%{_mandir}/man1/ovn-detrace.1*
+%{_mandir}/man7/ovn-architecture.7*
+%{_mandir}/man8/ovn-sbctl.8*
+%{_mandir}/man8/ovn-ic-sbctl.8*
+%{_mandir}/man5/ovn-nb.5*
+%{_mandir}/man5/ovn-ic-nb.5*
+%{_mandir}/man5/ovn-sb.5*
+%{_mandir}/man5/ovn-ic-sb.5*
+%dir %{ovnlibdir}/ocf/resource.d/ovn/
+%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
+%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/logrotate.d/ovn
+%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/ovn
+
+%files central
+%{_bindir}/ovn-northd
+%{_bindir}/ovn-ic
+%{_mandir}/man8/ovn-northd.8*
+%{_mandir}/man8/ovn-ic.8*
+%{_datadir}/ovn/ovn-nb.ovsschema
+%{_datadir}/ovn/ovn-ic-nb.ovsschema
+%{_datadir}/ovn/ovn-sb.ovsschema
+%{_datadir}/ovn/ovn-ic-sb.ovsschema
+%{_unitdir}/ovn-northd.service
+%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
+
+%files host
+%{_bindir}/ovn-controller
+%{_mandir}/man8/ovn-controller.8*
+%{_unitdir}/ovn-controller.service
+%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
+
+%files vtep
+%{_bindir}/ovn-controller-vtep
+%{_mandir}/man8/ovn-controller-vtep.8*
+%{_unitdir}/ovn-controller-vtep.service
+
+%changelog
+* Wed Apr 14 2021 Numan Siddique - 21.03.0-21
+- controller: Fix virtual lport I-P handling. (#1947823)
+ [Gerrit: 0938c49138dac280bbc59148fe87dc0debed6f62]
+ [Upstream: 1ad0a974b55dc6f31f7ea940e3b7d63368babb04]
+
+* Tue Apr 13 2021 Dumitru Ceara - 21.03.0-20
+- northd: Restore flows that recirculate packets in the router DNAT zone.
+ [Gerrit: 4f5d3099d94c4860737546c4c1f6561f15dc7519]
+ [Upstream: 82b4c619dd6c772a50d5403bf6d40aa4b4f7e38d]
+
+* Tue Apr 13 2021 Numan Siddique - 21.03.0-19
+- Merge "binding: Fix the crashes seen when port binding type changes." into ovn-2021
+ [Gerrit: 5a15f57371ce318a382c0e3aa262e5dab790e168]
+ [Upstream: N/A]
+
+* Tue Apr 13 2021 Lorenzo Bianconi - 21.03.0-18
+- Merge "northd: introduce per-lb lb_skip_snat option" into ovn-2021
+ [Gerrit: 91e869a442511d649b123d42e69b789f5d3ee96e]
+ [Upstream: N/A]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-17
+- northd: introduce lrouter_check_nat_entry routine
+ [Gerrit: 0b3ca120ea30a2b1d34d5b55e9b7b953757da4dd]
+ [Upstream: e02cd3d2001db87b92bd139eab533e69e0d48aee]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-16
+- northd: introduce build_lrouter_ingress_flow routine
+ [Gerrit: db27342ff9355f45021013ff3aaf2ebafe71c47a]
+ [Upstream: 0d16a8b64c5035529cbbbf245384618711024ae4]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-15
+- northd: introduce build_lrouter_out_snat_flow routine
+ [Gerrit: 37b0ba144b999a089cd439023f90980651400616]
+ [Upstream: 5e8fadf69161bc7e56b8f9f57124e5083b496b83]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-14
+- northd: introduce build_lrouter_out_undnat_flow routine
+ [Gerrit: 68226a2a7dd698bb0097a87c2f2367c261f5f234]
+ [Upstream: d8edf46f9e40791954d6bfc0231064e6e09252db]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-13
+- northd: introduce build_lrouter_in_dnat_flow routine
+ [Gerrit: a5189c81e017ef54c5612072c025fb0b2b24f836]
+ [Upstream: 225426081f8533e3d4df022b392105028f8bb37c]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-12
+- northd: introduce build_lrouter_in_unsnat_flow routine
+ [Gerrit: 083b610a6cabac7bb5136e72a15e35ae8a95b6fe]
+ [Upstream: fa91da7c9979d7b21b7a2a5557705c238cde97a0]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-11
+- northd: introduce build_lrouter_lb_flows routine
+ [Gerrit: d737b0572331da6e54b7dfa6a941be48035e061f]
+ [Upstream: 949e4319904938c1d83df2557c37b4bdfa6cbf25]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-10
+- northd: reduce indentation in build_lrouter_nat_defrag_and_lb
+ [Gerrit: 2de341869c169b807c8ae0abb413d01e381e698b]
+ [Upstream: 3ba84a110fd969d2c017070b5047f6df1129ac48]
+
+* Mon Mar 29 2021 Lorenzo Bianconi - 21.03.0-9
+- controller: introduce stats counters for ovn-controller incremental processing
+ [Gerrit: eee1993bbf9812adbb3717a2868b87cb01124665]
+ [Upstream: 0ddb8b2c979c1102d206b4f855eb5fbe1566768e]
+
+* Mon Mar 29 2021 Mark Michelson - 21.03.0-8
+- Add distgit syncing features.
+ [Gerrit: fccf9eb76e6e89e51788a3c3decf02b3b7cf621d]
+ [Upstream: N/A]
+
+* Thu Mar 25 2021 Michele Baldessari - 21.03.0-7
+- Fix connection string in case of changes in the ovndb-servers.ocf RA
+ [Gerrit: aab170f85e7e271d199f62ca3f1d050531f124bf]
+ [Upstream: 7f8bb3f2f77567d8fb30657ad5c3a9408692d6b5]
+
+* Thu Mar 25 2021 Daniel Alvarez Sanchez - 21.03.0-6
+- pinctrl: Don't send gARPs for localports (#1939470)
+ [Gerrit: a49a1c229790e896d391fcc0a6ed07fbf977963f]
+ [Upstream: 578238b36073256c524a4c2b6ed7521f73aa0019]
+
+* Mon Mar 15 2021 Ilya Maximets - 21.03.0-5
+- ci: Fix handling of python packages.
+ [Gerrit: da028c72bdc7742b3065d1df95a3789fbc16b27a]
+ [Upstream: 338a6ddb5ea1c89b48c484b0448a216a82225adc]
+
+* Fri Mar 12 2021 Mark Michelson - 21.03.0-4
+- Prepare for 21.03.1
+ [Gerrit: 79d8c9d594f8cda5023d3e1fefbaf53e109de89b]
+ [Upstream: N/A]
+
diff --git a/SPECS/ovn2.13.spec b/SPECS/ovn2.13.spec
deleted file mode 100644
index 1b900e5..0000000
--- a/SPECS/ovn2.13.spec
+++ /dev/null
@@ -1,856 +0,0 @@
-# Copyright (C) 2009, 2010, 2013, 2014 Nicira Networks, Inc.
-#
-# Copying and distribution of this file, with or without modification,
-# are permitted in any medium without royalty provided the copyright
-# notice and this notice are preserved. This file is offered as-is,
-# without warranty of any kind.
-#
-# If tests have to be skipped while building, specify the '--without check'
-# option. For example:
-# rpmbuild -bb --without check rhel/openvswitch-fedora.spec
-
-# This defines the base package name's version.
-
-%define pkgver 2.13
-%define pkgname ovn2.13
-
-# If libcap-ng isn't available and there is no need for running OVS
-# as regular user, specify the '--without libcapng'
-%bcond_without libcapng
-
-# Enable PIE, bz#955181
-%global _hardened_build 1
-
-# RHEL-7 doesn't define _rundir macro yet
-# Fedora 15 onwards uses /run as _rundir
-%if 0%{!?_rundir:1}
-%define _rundir /run
-%endif
-
-# Build python2 (that provides python) and python3 subpackages on Fedora
-# Build only python3 (that provides python) subpackage on RHEL8
-# Build only python subpackage on RHEL7
-%if 0%{?rhel} > 7 || 0%{?fedora}
-# On RHEL8 Sphinx is included in buildroot
-%global external_sphinx 1
-%else
-# Don't use external sphinx (RHV doesn't have optional repositories enabled)
-%global external_sphinx 0
-%endif
-
-# We would see rpmlinit error - E: hardcoded-library-path in '% {_prefix}/lib'.
-# But there is no solution to fix this. Using {_lib} macro will solve the
-# rpmlink error, but will install the files in /usr/lib64/.
-# OVN pacemaker ocf script file is copied in /usr/lib/ocf/resource.d/ovn/
-# and we are not sure if pacemaker looks into this path to find the
-# OVN resource agent script.
-%global ovnlibdir %{_prefix}/lib
-
-Name: %{pkgname}
-Summary: Open Virtual Network support
-Group: System Environment/Daemons
-URL: http://www.ovn.org/
-Version: 20.12.0
-Release: 85%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
-Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
-
-# Nearly all of openvswitch is ASL 2.0. The bugtool is LGPLv2+, and the
-# lib/sflow*.[ch] files are SISSL
-License: ASL 2.0 and LGPLv2+ and SISSL
-
-# Always pull an upstream release, since this is what we rebase to.
-Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz
-
-%define ovscommit ac09cbfcb70ac6f443f039d5934448bd80f74493
-%define ovsshortcommit ac09cbf
-
-Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz
-%define ovsdir ovs-%{ovscommit}
-
-%define docutilsver 0.12
-%define pygmentsver 1.4
-%define sphinxver 1.1.3
-Source100: https://pypi.io/packages/source/d/docutils/docutils-%{docutilsver}.tar.gz
-Source101: https://pypi.io/packages/source/P/Pygments/Pygments-%{pygmentsver}.tar.gz
-Source102: https://pypi.io/packages/source/S/Sphinx/Sphinx-%{sphinxver}.tar.gz
-
-Source500: configlib.sh
-Source501: gen_config_group.sh
-Source502: set_config.sh
-
-# Important: source503 is used as the actual copy file
-# @TODO: this causes a warning - fix it?
-Source504: arm64-armv8a-linuxapp-gcc-config
-Source505: ppc_64-power8-linuxapp-gcc-config
-Source506: x86_64-native-linuxapp-gcc-config
-
-Patch: ovn-%{version}.patch
-
-# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's
-# in the -optional repository and so we can't require it directly since RHV
-# doesn't have the -optional repository enabled and so TPS fails
-%if %{external_sphinx}
-BuildRequires: python3-sphinx
-%else
-# Sphinx dependencies
-BuildRequires: python-devel
-BuildRequires: python-setuptools
-#BuildRequires: python2-docutils
-BuildRequires: python-jinja2
-BuildRequires: python-nose
-#BuildRequires: python2-pygments
-# docutils dependencies
-BuildRequires: python-imaging
-# pygments dependencies
-BuildRequires: python-nose
-%endif
-
-BuildRequires: gcc gcc-c++ make
-BuildRequires: autoconf automake libtool
-BuildRequires: systemd-units openssl openssl-devel
-BuildRequires: python3-devel python3-setuptools
-BuildRequires: desktop-file-utils
-BuildRequires: groff-base graphviz
-BuildRequires: unbound-devel
-
-# make check dependencies
-BuildRequires: procps-ng
-%if 0%{?rhel} > 7 || 0%{?fedora}
-BuildRequires: python3-pyOpenSSL
-%endif
-
-%if %{with libcapng}
-BuildRequires: libcap-ng libcap-ng-devel
-%endif
-
-Requires: hostname openssl iproute module-init-tools
-
-Requires(post): systemd-units
-Requires(preun): systemd-units
-Requires(postun): systemd-units
-
-# to skip running checks, pass --without check
-%bcond_without check
-
-%description
-OVN, the Open Virtual Network, is a system to support virtual network
-abstraction. OVN complements the existing capabilities of OVS to add
-native support for virtual network abstractions, such as virtual L2 and L3
-overlays and security groups.
-
-%package central
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: %{pkgname}
-Requires: firewalld-filesystem
-Provides: openvswitch%{pkgver}-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-central < 2.11.0-1
-
-%description central
-OVN DB servers and ovn-northd running on a central node.
-
-%package host
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: %{pkgname}
-Requires: firewalld-filesystem
-Provides: openvswitch%{pkgver}-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-host < 2.11.0-1
-
-%description host
-OVN controller running on each host.
-
-%package vtep
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: %{pkgname}
-Provides: openvswitch%{pkgver}-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-vtep < 2.11.0-1
-
-%description vtep
-OVN vtep controller
-
-%prep
-%if 0%{?commit0:1}
-%autosetup -n ovn-%{commit0} -a 10 -p 1
-%else
-%autosetup -n ovn-%{version} -a 10 -p 1
-%endif
-
-%build
-%if 0%{?commit0:1}
-# fix the snapshot unreleased version to be the released one.
-sed -i.old -e "s/^AC_INIT(openvswitch,.*,/AC_INIT(openvswitch, %{version},/" configure.ac
-%endif
-./boot.sh
-
-# OVN source code is now separate.
-# Build openvswitch first.
-# XXX Current openvswitch2.13 doesn't
-# use "2.13.0" for version. It's a commit hash
-pushd %{ovsdir}
-./boot.sh
-%configure \
-%if %{with libcapng}
- --enable-libcapng \
-%else
- --disable-libcapng \
-%endif
- --enable-ssl \
- --with-pkidir=%{_sharedstatedir}/openvswitch/pki
-
-make %{?_smp_mflags}
-popd
-
-# Build OVN.
-# XXX OVS version needs to be updated when ovs2.13 is updated.
-%configure \
- --with-ovs-source=$PWD/%{ovsdir} \
-%if %{with libcapng}
- --enable-libcapng \
-%else
- --disable-libcapng \
-%endif
- --enable-ssl \
- --with-pkidir=%{_sharedstatedir}/openvswitch/pki
-
-make %{?_smp_mflags}
-
-%install
-%make_install
-install -p -D -m 0644 \
- rhel/usr_share_ovn_scripts_systemd_sysconfig.template \
- $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/ovn
-
-for service in ovn-controller ovn-controller-vtep ovn-northd; do
- install -p -D -m 0644 \
- rhel/usr_lib_systemd_system_${service}.service \
- $RPM_BUILD_ROOT%{_unitdir}/${service}.service
-done
-
-install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/ovn
-
-install -d $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/
-install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
- $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
-install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \
- $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
-
-install -d -m 0755 $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn
-ln -s %{_datadir}/ovn/scripts/ovndb-servers.ocf \
- $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
-
-install -p -D -m 0644 rhel/etc_logrotate.d_ovn \
- $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/ovn
-
-# remove unneeded files.
-rm -f $RPM_BUILD_ROOT%{_bindir}/ovs*
-rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl
-rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep*
-rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/python
-rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovs*
-rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/bugtool-plugins
-rm -f $RPM_BUILD_ROOT%{_libdir}/*.a
-rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
-rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc
-rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/*
-rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash
-rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash
-rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch
-rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovn-bugtool*
-rm -f $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-overlay-driver \
- $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-underlay-driver
-
-%check
-%if %{with check}
- touch resolv.conf
- export OVS_RESOLV_CONF=$(pwd)/resolv.conf
- if ! make check TESTSUITEFLAGS='%{_smp_mflags} -k ovn'; then
- cat tests/testsuite.log
- if ! make check TESTSUITEFLAGS='--recheck'; then
- cat tests/testsuite.log
- # Presently a test case - "2796: ovn -- ovn-controller incremental processing"
- # is failing on aarch64 arch. Let's not exit for this arch
- # until we figure out why it is failing.
- # Test case 93: ovn.at:12105 ovn -- ACLs on Port Groups is failing
- # repeatedly on s390x. This needs to be investigated.
- %ifnarch aarch64
- %ifnarch ppc64le
- %ifnarch s390x
- exit 1
- %endif
- %endif
- %endif
- fi
- fi
-%endif
-
-%clean
-rm -rf $RPM_BUILD_ROOT
-
-%pre central
-if [ $1 -eq 1 ] ; then
- # Package install.
- /bin/systemctl status ovn-northd.service >/dev/null
- ovn_status=$?
- rpm -ql openvswitch-ovn-central > /dev/null
- if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
- # ovn-northd service is running which means old openvswitch-ovn-central
- # is already installed and it will be cleaned up. So start ovn-northd
- # service when posttrans central is called.
- touch %{_localstatedir}/lib/rpm-state/ovn-northd
- fi
-fi
-
-%pre host
-if [ $1 -eq 1 ] ; then
- # Package install.
- /bin/systemctl status ovn-controller.service >/dev/null
- ovn_status=$?
- rpm -ql openvswitch-ovn-host > /dev/null
- if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
- # ovn-controller service is running which means old
- # openvswitch-ovn-host is installed and it will be cleaned up. So
- # start ovn-controller service when posttrans host is called.
- touch %{_localstatedir}/lib/rpm-state/ovn-controller
- fi
-fi
-
-%pre vtep
-if [ $1 -eq 1 ] ; then
- # Package install.
- /bin/systemctl status ovn-controller-vtep.service >/dev/null
- ovn_status=$?
- rpm -ql openvswitch-ovn-vtep > /dev/null
- if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
- # ovn-controller-vtep service is running which means old
- # openvswitch-ovn-vtep is installed and it will be cleaned up. So
- # start ovn-controller-vtep service when posttrans host is called.
- touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
- fi
-fi
-
-%preun central
-%if 0%{?systemd_preun:1}
- %systemd_preun ovn-northd.service
-%else
- if [ $1 -eq 0 ] ; then
- # Package removal, not upgrade
- /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || :
- /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || :
- fi
-%endif
-
-%preun host
-%if 0%{?systemd_preun:1}
- %systemd_preun ovn-controller.service
-%else
- if [ $1 -eq 0 ] ; then
- # Package removal, not upgrade
- /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || :
- /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || :
- fi
-%endif
-
-%preun vtep
-%if 0%{?systemd_preun:1}
- %systemd_preun ovn-controller-vtep.service
-%else
- if [ $1 -eq 0 ] ; then
- # Package removal, not upgrade
- /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || :
- /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || :
- fi
-%endif
-
-%post
-%if %{with libcapng}
-if [ $1 -eq 1 ]; then
- sed -i 's:^#OVN_USER_ID=:OVN_USER_ID=:' %{_sysconfdir}/sysconfig/ovn
- sed -i 's:\(.*su\).*:\1 openvswitch openvswitch:' %{_sysconfdir}/logrotate.d/ovn
-fi
-%endif
-
-%post central
-%if 0%{?systemd_post:1}
- %systemd_post ovn-northd.service
-%else
- # Package install, not upgrade
- if [ $1 -eq 1 ]; then
- /bin/systemctl daemon-reload >dev/null || :
- fi
-%endif
-
-%post host
-%if 0%{?systemd_post:1}
- %systemd_post ovn-controller.service
-%else
- # Package install, not upgrade
- if [ $1 -eq 1 ]; then
- /bin/systemctl daemon-reload >dev/null || :
- fi
-%endif
-
-%post vtep
-%if 0%{?systemd_post:1}
- %systemd_post ovn-controller-vtep.service
-%else
- # Package install, not upgrade
- if [ $1 -eq 1 ]; then
- /bin/systemctl daemon-reload >dev/null || :
- fi
-%endif
-
-%postun
-
-%postun central
-%if 0%{?systemd_postun_with_restart:1}
- %systemd_postun_with_restart ovn-northd.service
-%else
- /bin/systemctl daemon-reload >/dev/null 2>&1 || :
- if [ "$1" -ge "1" ] ; then
- # Package upgrade, not uninstall
- /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || :
- fi
-%endif
-
-%postun host
-%if 0%{?systemd_postun_with_restart:1}
- %systemd_postun_with_restart ovn-controller.service
-%else
- /bin/systemctl daemon-reload >/dev/null 2>&1 || :
- if [ "$1" -ge "1" ] ; then
- # Package upgrade, not uninstall
- /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || :
- fi
-%endif
-
-%postun vtep
-%if 0%{?systemd_postun_with_restart:1}
- %systemd_postun_with_restart ovn-controller-vtep.service
-%else
- /bin/systemctl daemon-reload >/dev/null 2>&1 || :
- if [ "$1" -ge "1" ] ; then
- # Package upgrade, not uninstall
- /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || :
- fi
-%endif
-
-%posttrans central
-if [ $1 -eq 1 ]; then
- # Package install, not upgrade
- if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then
- rm %{_localstatedir}/lib/rpm-state/ovn-northd
- /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || :
- fi
-fi
-
-
-%posttrans host
-if [ $1 -eq 1 ]; then
- # Package install, not upgrade
- if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then
- rm %{_localstatedir}/lib/rpm-state/ovn-controller
- /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || :
- fi
-fi
-
-%posttrans vtep
-if [ $1 -eq 1 ]; then
- # Package install, not upgrade
- if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then
- rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
- /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || :
- fi
-fi
-
-%files
-%{_bindir}/ovn-nbctl
-%{_bindir}/ovn-sbctl
-%{_bindir}/ovn-trace
-%{_bindir}/ovn-detrace
-%{_bindir}/ovn-appctl
-%{_bindir}/ovn-ic-nbctl
-%{_bindir}/ovn-ic-sbctl
-%dir %{_datadir}/ovn/
-%dir %{_datadir}/ovn/scripts/
-%{_datadir}/ovn/scripts/ovn-ctl
-%{_datadir}/ovn/scripts/ovn-lib
-%{_datadir}/ovn/scripts/ovndb-servers.ocf
-%{_mandir}/man8/ovn-ctl.8*
-%{_mandir}/man8/ovn-appctl.8*
-%{_mandir}/man8/ovn-nbctl.8*
-%{_mandir}/man8/ovn-ic-nbctl.8*
-%{_mandir}/man8/ovn-trace.8*
-%{_mandir}/man1/ovn-detrace.1*
-%{_mandir}/man7/ovn-architecture.7*
-%{_mandir}/man8/ovn-sbctl.8*
-%{_mandir}/man8/ovn-ic-sbctl.8*
-%{_mandir}/man5/ovn-nb.5*
-%{_mandir}/man5/ovn-ic-nb.5*
-%{_mandir}/man5/ovn-sb.5*
-%{_mandir}/man5/ovn-ic-sb.5*
-%dir %{ovnlibdir}/ocf/resource.d/ovn/
-%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
-%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/logrotate.d/ovn
-%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/ovn
-
-%files central
-%{_bindir}/ovn-northd
-%{_bindir}/ovn-ic
-%{_mandir}/man8/ovn-northd.8*
-%{_mandir}/man8/ovn-ic.8*
-%{_datadir}/ovn/ovn-nb.ovsschema
-%{_datadir}/ovn/ovn-ic-nb.ovsschema
-%{_datadir}/ovn/ovn-sb.ovsschema
-%{_datadir}/ovn/ovn-ic-sb.ovsschema
-%{_unitdir}/ovn-northd.service
-%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
-
-%files host
-%{_bindir}/ovn-controller
-%{_mandir}/man8/ovn-controller.8*
-%{_unitdir}/ovn-controller.service
-%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
-
-%files vtep
-%{_bindir}/ovn-controller-vtep
-%{_mandir}/man8/ovn-controller-vtep.8*
-%{_unitdir}/ovn-controller-vtep.service
-
-%changelog
-* Tue Mar 16 2021 Mark Michelson - 20.12.0-85
-- ovs: Bump submodule version to latest ovsdb-cs changes.
- [a79aa8ecc00450ab9c672dbe8add9a8a231186ab]
-
-* Tue Mar 16 2021 Mark Michelson - 20.12.0-84
-- Add missing patch release number on the end of OVN versions.
- [49444fe569740deb10be87418ab4f9ed1d0d5b4e]
-
-* Fri Mar 12 2021 Numan Siddique - 20.12.0-83
-- northd: Fix ha_chassis_group txn error for external ports. (#1927369)
- [f3e58ca5a997ab7fad0c44c0c5968e4ee53f85d5]
-
-* Wed Mar 10 2021 Gerrit Code Review - 20.12.0-82
-- Merge "Add distgit syncing features." into ovn2.13
- [fdc3e0c5b51295a3c66b469476878128bc013280]
-
-* Sat Mar 06 2021 Numan Siddique - 20.12.0-81
-- binding: Fix potential NULL dereference of lbinding.
- [de6cb3a9f2d8b323d759ab70a81acac2f73d2d77]
-
-* Sat Mar 06 2021 Numan Siddique - 20.12.0-80
-- northd: Fix the missing force_snat_for_lb flows when router_ip is configured.
- [ec4f7228c1b828974ec2909bd108af6a94c3ebc9]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-79
-- northd: Avoid matching on ct.dnat flags for load balancers.
- [2baa1b74fb8e93e158d4800d9e42e24b70b3970a]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-78
-- lflow: Avoid matching on conntrack original tuple if possible.
- [35a6507db36c23724bc319d56dc6b05dd6d60a99]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-77
-- Properly handle hairpin traffic for VIPs with shared backends. (#1931599)
- [811c575effb27cbc416986fafd2e8c815daa4ac6]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-76
-- mac-learn: Fix build due to missing newline at EOF.
- [492f5b3ce7c76af4197ded8540913ea52d272136]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-75
-- northd: Cleanup stale FDB entries.
- [33fe76837b1da9f8f9f1306a78b74d72a5449537]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-74
-- Fix the failing test case - ovn -- ACL skip hints for stateless config.
- [f69f2d79d50efce77831f04464b47a3a58cbc3f4]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-73
-- northd: MAC learning: Add logical flows for fdb.
- [da4e67e3ebaf75701f7018f893d2b3285a5da394]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-72
-- controller: MAC learning: Add OF rules for the FDB entries.
- [54ae01ced6d791f87fd9ca8ef6a8d6680ce7373f]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-71
-- MAC learning: Add new actions - put_fdb, get_fdb and lookup_fdb.
- [cfa19b0905752503056e1e8e4246a629da1d8fbd]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-70
-- MAC learning: Add a new FDB table in southbound db.
- [853c8bfd101109fce7b7422486ddaff4e2e6aa03]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-69
-- controller: Split mac learning code to a separate file.
- [1feebd8577989a0c01bc27d4ee5a8b9eb323ac47]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-68
-- Add sctp_abort logical flow action.
- [180afce292e2c46966ba56cce128f5b3842133c5]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-67
-- Implement SCTP-specific reject() action.
- [35afc86abf71ae557acf2fb7030ca8eedf214449]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-66
-- tests: Improve debuggability of tests.
- [5fdd36b0afc25a20bde632278878bbfa7fcbc7ce]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-65
-- tests: Add more checking to "3 HVs, 1 LS, 3 lports/HV" test.
- [19f0bbfbb4f3b5c0a382154c60b6a53f43cff7a6]
-
-* Thu Feb 25 2021 Dumitru Ceara - 20.12.0-64
-- tests: Eliminate most "sleep" calls.
- [1883c14bb0eb136f2339196218e5c107cf0f79aa]
-
-* Tue Feb 23 2021 Numan Siddique - 20.12.0-63
-- ofctrl: Fix the assert seen when flood removing flows with conj actions. (#1929978)
- [16476ae504d692ec0a7440c27037d155a3f76445]
-
-* Mon Feb 22 2021 Numan Siddique - 20.12.0-62
-- ofctrl: Do not link a desired flow twice.
- [d17a9c36dfa71b04ebea82e4720279b35d42677f]
-
-* Mon Feb 22 2021 Numan Siddique - 20.12.0-61
-- binding: Fix potential crash when binding_seqno_run is skipped. (#1930030)
- [dae3f159a10a3499ef001fddd52e6cd1949927be]
-
-* Mon Feb 22 2021 Numan Siddique - 20.12.0-60
-- northd: Provide the Gateway router option 'lb_force_snat_ip' to take router port ips.
- [e6a52543078d2d618b72151608b9c61326debe05]
-
-* Wed Feb 17 2021 Gerrit Code Review - 20.12.0-59
-- Merge "Fix submodule build when using build directory." into ovn2.13
- [70de1e7f6d98dc45ceb40f5d91627767e3776ae2]
-
-* Wed Feb 17 2021 Gerrit Code Review - 20.12.0-58
-- Merge "Add ovs submodule." into ovn2.13
- [ae816ae0378675ea180879548e41f397ffe61c23]
-
-* Wed Feb 17 2021 Numan Siddique - 20.12.0-57
-- ofctrl: Fix the assert seen when flood removing flows. (#1928012)
- [6cce5c6380d33d8ae0795c10737d637f29680e32]
-
-* Tue Feb 16 2021 Gerrit Code Review - 20.12.0-56
-- Merge "Makefile.am: Fix broken distribution and echo -n checks." into ovn2.13
- [bd7708136650c8f2abb6c5ac005b3141406ec1b5]
-
-* Sat Feb 13 2021 Numan Siddique - 20.12.0-55
-- controller: Fix toggling ct zone ids. (#1903210)
- [48dfb211ef1ecbcee39df4647d905a924b95fcb3]
-
-* Thu Feb 11 2021 Lorenzo Bianconi - 20.12.0-54
-- ovn-nbctl: do not allow duplicated ECMP routes
- [1a7f0f5bd5c23ff5d2583631288001f9bd1e5763]
-
-* Wed Feb 10 2021 Mark Michelson - 20.12.0-53
-- northd: Skip matching on ct flags for stateless configurations. (#1927211)
- [e58c182eaffa79cfdee407bf06531007bf1e3c63]
-
-* Tue Feb 09 2021 Dumitru Ceara - 20.12.0-52
-- lflow: Use learn() action to generate LB hairpin reply flows.
- [0ea619c80146bffd4fef7ac1a8a2aa07f9003eb0]
-
-* Tue Feb 09 2021 Dumitru Ceara - 20.12.0-51
-- Support configuring Load Balancer hairpin source IP.
- [8788ac191a4e0689f0287695c181fe1a781b0d31]
-
-* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-50
-- tests: Fix Port_Binding up test.
- [4e143c1e58b18adf6914ec783ee4503a63dbf3a8]
-
-* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-49
-- northd: Allow backwards compatibility for Logical_Switch_Port.up.
- [07b0f0468faeeb1e149dcc3e4926a54cbb9bb367]
-
-* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-48
-- binding: Set Port_Binding.up only if supported.
- [3de7959b9018f53abd06320bc7f1a43ec216db7e]
-
-* Mon Feb 08 2021 Dumitru Ceara - 20.12.0-47
-- binding: Correctly set Port_Binding.up for container/virtual ports.
- [36a57e7a388277d1e45f0cadd8e2601490a76b2d]
-
-* Thu Feb 04 2021 Numan Siddique - 20.12.0-46
-- Change the gitreview branch to ovn2.13
- [e7d6708ff347a7e02b16b7205b92d052e5681a37]
-
-* Wed Feb 03 2021 Lorenzo Bianconi - 20.12.0-45
-- ovn-nbctl: add --bfd option to lr-route-add
- [97b58dde0f92fc83165a6db816456073f5ddf727]
-
-* Fri Jan 29 2021 Dumitru Ceara - 20.12.0-44
-- binding: Set Logical_Switch_Port.up when all OVS flows are installed.
- [3719a1add73b860c50d85fad0b270c1b69fb9147]
-
-* Fri Jan 29 2021 Dumitru Ceara - 20.12.0-43
-- controller: Implement a generic barrier based on ofctrl cur_cfg sync.
- [2bafeec1b98cfa813fa75dfafa74fdacae8e32c4]
-
-* Fri Jan 29 2021 Dumitru Ceara - 20.12.0-42
-- ofctrl: Rename 'nb_cfg' to 'req_cfg'.
- [c6f4b3a47571d87149b86c999b78509185da7647]
-
-* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-41
-- northd: add --event option to enable controller_event for empty_lb
- [8bcee6092b931caa936ee8ac715cf6ec89d3f18d]
-
-* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-40
-- ovn-nbctl: add ecmp/ecmp-symmetric-reply to lr-route-list command
- [5e1bb597df512510dc82ce47f9b65a02e2fb5c0b]
-
-* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-39
-- ovn-nbctl: add bfd report to lr-route-list command
- [8770192b3b4732e02679f723ea5903a515c6bd8a]
-
-* Thu Jan 28 2021 Lorenzo Bianconi - 20.12.0-38
-- controller: fix pkt_marking with IP buffering
- [5b75b36198b1cdf66aa0bee5a0a73f1e591af1b2]
-
-* Wed Jan 27 2021 Numan Siddique - 20.12.0-37
-- ovn-ctl: Add support for ovsdb-server --disable-file-column-diff.
- [15eefe928ea2a51c7ad03356821f0665ca6abb6d]
-
-* Wed Jan 27 2021 Numan Siddique - 20.12.0-36
-- ovn-controller: Fix wrong conj_id match flows when caching is enabled.
- [11f75ad5bef3d2f6a9d72a8b27468fc3ccfc3d7e]
-
-* Mon Jan 25 2021 Dumitru Ceara - 20.12.0-35
-- northd: Fix duplicate logical port detection. (#1918582)
- [46a4e3bb3a6d01c96721761a0e03d093583ab1cc]
-
-* Wed Jan 20 2021 Dumitru Ceara - 20.12.0-34
-- binding: Always delete child port bindings first.
- [47afa0664d6a41d0a75a65f0ba927974d957cb62]
-
-* Wed Jan 20 2021 Dumitru Ceara - 20.12.0-33
-- binding: Fix container port removal from local bindings.
- [44955fb2395677c9d9bb1afa3985b24317c84431]
-
-* Wed Jan 20 2021 Dumitru Ceara - 20.12.0-32
-- northd: Fix ACL fair log meters for Port_Group ACLs.
- [d2b69af321ad8064d42aad2fd3d15857334e2d63]
-
-* Wed Jan 20 2021 Numan Siddique - 20.12.0-31
-- Change the default gerrit branch to ovn2.13-20.12.
- [4bc45f3897cfdcdc48c354bf69016350d36f9ed9]
-
-* Wed Jan 13 2021 Lorenzo Bianconi - 20.12.0-30
-- bfd: introduce IPv6 support
- [9f42e93b6a25bff87074156586505a6e8968f8cb]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-29
-- ovn: integrate bfd for static routes.
- [986137dc1d4dc6905a7c5ab5e279856260966e12]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-28
-- bfd: support demand mode on rx side.
- [a3a3062985cadc2f2193b10ccb3404d587028c61]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-27
-- controller: bfd: introduce BFD state machine.
- [e75d53c69261a0b104c75d8f6f7dc7175a690833]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-26
-- action: introduce handle_bfd_msg() action.
- [2d71cf47fdb194287719a97ee81dbb0dd9fab9d8]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-25
-- controller: introduce BFD tx path in ovn-controller.
- [2473b80f778654f0204d1cf4671e543cb6467d5f]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-24
-- ovn-northd: move NAT, Defrag and lb to a function.
- [7699c1043a3fec9eb215fc430202ca01846c505e]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-23
-- ovn-northd: Move ipv4 input to a function.
- [761f760a42d97184c870e892d299587e657a2c52]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-22
-- ovn-northd: Move lrouter arp and nd datapath processing to a function.
- [34c2afc7d49f735e825e0d01bf1b2b64bb277f76]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-21
-- ovn-northd: split build_lswitch_output_port_sec into iterators.
- [a6b4b14ac1b6523f85fb13a7f259d9698a70444f]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-20
-- ovn-northd: Move destination handling into functions.
- [137b049777cfc301eadba8a2c3b55764bde6f451]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-19
-- ovn-northd: Move broadcast and multicast lookup in lswitch to a function.
- [9e60b5574786c0ef8f6403ac61567553c1a7717f]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-18
-- ovn-northd: Move ARP response for external ports to a function.
- [d63444b7fcdcc2c68b7af94090410bc3e40e574b]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-17
-- ovn-northd: Move DNS and DHCP defaults to a function.
- [502d52712bca01f237aa15e5853bc3090e6034e5]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-16
-- ovn-northd: Move lswitch DNS lookup and response to a function.
- [685d26ba45965b2268fbbc36d167115419321f25]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-15
-- ovn-northd: Move DHCP Options and Response to a function.
- [e513bafe5718f42844f41d248ddf1777b71aaa50]
-
-* Tue Jan 12 2021 Lorenzo Bianconi - 20.12.0-14
-- ovn-northd: Move lswitch ARP/ND Responder to functions.
- [f21c1b7a467a691847b5552d4570af706fcc5bb0]
-
-* Fri Jan 08 2021 Dumitru Ceara - 20.12.0-13
-- binding: Do not clear container lbinding->pb when parent is deleted.
- [0ec31292fc29d2c111927382b13ea8af0499f6ac]
-
-* Fri Jan 08 2021 Dumitru Ceara - 20.12.0-12
-- ovn-trace: fix trigger_event warning.
- [90bcf225ced8c80caa44f091665b38ae781ee77b]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-11
-- tests: Make "ovn -- ovn-controller incremental processing" more reliable.
- [7a56fc4309bf1103efb1160a2c2defa8c8e8be28]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-10
-- osx: Fix compilation error.
- [b879b6c9350fef6cce96343aa052957eaf6268bd]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-9
-- northd: Add ECMP support to router policies. (#1881826)
- [20575af6dc17edd48e54c831a6f15cca295a1b35]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-8
-- nbctl: Remove column verification for partial updates.
- [455e46cd53152bcd577e775c012dd76cd7f2611c]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-7
-- nbctl: Use partial set updates instead of re-setting the whole column.
- [6c658bc2a566558531d751006d7ec0c7bdc6ef76]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-6
-- nbctl: Cache to which switch or router particular port belongs.
- [9a4ad65122e461ac5764ff9a0d726d46a4502e7f]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-5
-- northd: add reject action for lb with no backends
- [e0aee2d667b64234888cc4b69dc24dc8e9e57770]
-
-* Fri Dec 18 2020 Numan Siddique - 20.12.0-4
-- Add openvswitch-2.14.90 dir.
- [60e2f264de6cb92c37dd76856954bf16276510d4]
-