From 3aeb8b73e0947010945aea8bf8fa6df1b7a558a7 Mon Sep 17 00:00:00 2001
From: Mark Michelson <mmichels@redhat.com>
Date: Thu, 6 Aug 2020 15:04:15 -0400
Subject: [PATCH 16/22] Allow force_snat options to work for dual-stack
routers.
The lb_force_snat and dnat_force_snat options could accept only a single
IP address. For routers that only route traffic of a single IP address
family, this is fine. However, if a router routes both IPv4 and IPv6
traffic, then this limitation is a problem.
This patch addresses this problem by allowing for these options to
specify both an IPv4 and IPv6 address.
Signed-off-by: Mark Michelson <mmichels@redhat.com>
Acked-by: Dumitru Ceara <dceara@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
(cherry-picked from upstream master commit 474821c55608cbad5bdb8deee468827ab489c02b)
Change-Id: I42266af72622d1f15ec94d68f106954cc49979bd
---
northd/ovn-northd.c | 187 ++++++++-------
ovn-nb.xml | 24 +-
tests/system-ovn.at | 541 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 657 insertions(+), 95 deletions(-)
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index cb8e25bdf..1f5433d9d 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -8013,44 +8013,37 @@ op_put_v6_networks(struct ds *ds, const struct ovn_port *op)
ds_put_cstr(ds, "}");
}
-static const char *
+static bool
get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
- struct v46_ip *ip)
+ struct lport_addresses *laddrs)
{
char *key = xasprintf("%s_force_snat_ip", key_type);
- const char *ip_address = smap_get(&od->nbr->options, key);
+ const char *addresses = smap_get(&od->nbr->options, key);
free(key);
- if (ip_address) {
- ovs_be32 mask;
- ip->family = AF_INET;
- char *error = ip_parse_masked(ip_address, &ip->ipv4, &mask);
- if (error || mask != OVS_BE32_MAX) {
- free(error);
- struct in6_addr mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
- ip->family = AF_INET6;
- error = ipv6_parse_masked(ip_address, &ip->ipv6, &mask_v6);
- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"",
- ip_address, UUID_ARGS(&od->key));
- memset(ip, 0, sizeof *ip);
- ip->family = AF_UNSPEC;
- return NULL;
- }
- }
- return ip_address;
+ if (!addresses) {
+ return false;
}
- memset(ip, 0, sizeof *ip);
- ip->family = AF_UNSPEC;
- return NULL;
+ if (!extract_ip_addresses(addresses, laddrs) ||
+ laddrs->n_ipv4_addrs > 1 ||
+ laddrs->n_ipv6_addrs > 1 ||
+ (laddrs->n_ipv4_addrs && laddrs->ipv4_addrs[0].plen != 32) ||
+ (laddrs->n_ipv6_addrs && laddrs->ipv6_addrs[0].plen != 128)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"",
+ addresses, UUID_ARGS(&od->key));
+ destroy_lport_addresses(laddrs);
+ return false;
+ }
+
+ return true;
}
static void
add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
struct ds *match, struct ds *actions, int priority,
- const char *lb_force_snat_ip, struct lb_vip *lb_vip,
+ bool lb_force_snat_ip, struct lb_vip *lb_vip,
const char *proto, struct nbrec_load_balancer *lb,
struct shash *meter_groups, struct sset *nat_entries)
{
@@ -8371,6 +8364,32 @@ build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
ds_destroy(&actions);
}
+static void
+build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
+ const char *ip_version, const char *ip_addr,
+ const char *context)
+{
+ struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds actions = DS_EMPTY_INITIALIZER;
+ ds_put_format(&match, "ip%s && ip%s.dst == %s",
+ ip_version, ip_version, ip_addr);
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 110,
+ ds_cstr(&match), "ct_snat;");
+
+ /* Higher priority rules to force SNAT with the IP addresses
+ * configured in the Gateway router. This only takes effect
+ * when the packet has already been DNATed or load balanced once. */
+ ds_clear(&match);
+ ds_put_format(&match, "flags.force_snat_for_%s == 1 && ip%s",
+ context, ip_version);
+ ds_put_format(&actions, "ct_snat(%s);", ip_addr);
+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 100,
+ ds_cstr(&match), ds_cstr(&actions));
+
+ ds_destroy(&match);
+ ds_destroy(&actions);
+}
+
static void
build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
struct hmap *lflows, struct shash *meter_groups,
@@ -8892,24 +8911,37 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* A gateway router can have 2 SNAT IP addresses to force DNATed and
+ /* A gateway router can have 4 SNAT IP addresses to force DNATed and
* LBed traffic respectively to be SNATed. In addition, there can be
* a number of SNAT rules in the NAT table. */
struct v46_ip *snat_ips = xmalloc(sizeof *snat_ips
- * (op->od->nbr->n_nat + 2));
+ * (op->od->nbr->n_nat + 4));
size_t n_snat_ips = 0;
+ struct lport_addresses snat_addrs;
- struct v46_ip snat_ip;
- const char *dnat_force_snat_ip = get_force_snat_ip(op->od, "dnat",
- &snat_ip);
- if (dnat_force_snat_ip) {
- snat_ips[n_snat_ips++] = snat_ip;
+ if (get_force_snat_ip(op->od, "dnat", &snat_addrs)) {
+ if (snat_addrs.n_ipv4_addrs) {
+ snat_ips[n_snat_ips].family = AF_INET;
+ snat_ips[n_snat_ips++].ipv4 = snat_addrs.ipv4_addrs[0].addr;
+ }
+ if (snat_addrs.n_ipv6_addrs) {
+ snat_ips[n_snat_ips].family = AF_INET6;
+ snat_ips[n_snat_ips++].ipv6 = snat_addrs.ipv6_addrs[0].addr;
+ }
+ destroy_lport_addresses(&snat_addrs);
}
- const char *lb_force_snat_ip = get_force_snat_ip(op->od, "lb",
- &snat_ip);
- if (lb_force_snat_ip) {
- snat_ips[n_snat_ips++] = snat_ip;
+ memset(&snat_addrs, 0, sizeof(snat_addrs));
+ if (get_force_snat_ip(op->od, "lb", &snat_addrs)) {
+ if (snat_addrs.n_ipv4_addrs) {
+ snat_ips[n_snat_ips].family = AF_INET;
+ snat_ips[n_snat_ips++].ipv4 = snat_addrs.ipv4_addrs[0].addr;
+ }
+ if (snat_addrs.n_ipv6_addrs) {
+ snat_ips[n_snat_ips].family = AF_INET6;
+ snat_ips[n_snat_ips++].ipv6 = snat_addrs.ipv6_addrs[0].addr;
+ }
+ destroy_lport_addresses(&snat_addrs);
}
for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
@@ -9269,11 +9301,12 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
- struct v46_ip snat_ip, lb_snat_ip;
- const char *dnat_force_snat_ip = get_force_snat_ip(od, "dnat",
- &snat_ip);
- const char *lb_force_snat_ip = get_force_snat_ip(od, "lb",
- &lb_snat_ip);
+ struct lport_addresses dnat_force_snat_addrs;
+ struct lport_addresses lb_force_snat_addrs;
+ bool dnat_force_snat_ip = get_force_snat_ip(od, "dnat",
+ &dnat_force_snat_addrs);
+ bool lb_force_snat_ip = get_force_snat_ip(od, "lb",
+ &lb_force_snat_addrs);
for (int i = 0; i < od->nbr->n_nat; i++) {
const struct nbrec_nat *nat;
@@ -9739,49 +9772,28 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
}
/* Handle force SNAT options set in the gateway router. */
- if (dnat_force_snat_ip && !od->l3dgw_port) {
- /* If a packet with destination IP address as that of the
- * gateway router (as set in options:dnat_force_snat_ip) is seen,
- * UNSNAT it. */
- ds_clear(&match);
- ds_put_format(&match, "ip && ip%s.dst == %s",
- snat_ip.family == AF_INET ? "4" : "6",
- dnat_force_snat_ip);
- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 110,
- ds_cstr(&match), "ct_snat;");
-
- /* Higher priority rules to force SNAT with the IP addresses
- * configured in the Gateway router. This only takes effect
- * when the packet has already been DNATed once. */
- ds_clear(&match);
- ds_put_format(&match, "flags.force_snat_for_dnat == 1 && ip");
- ds_clear(&actions);
- ds_put_format(&actions, "ct_snat(%s);", dnat_force_snat_ip);
- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 100,
- ds_cstr(&match), ds_cstr(&actions));
- }
- if (lb_force_snat_ip && !od->l3dgw_port) {
- /* If a packet with destination IP address as that of the
- * gateway router (as set in options:lb_force_snat_ip) is seen,
- * UNSNAT it. */
- ds_clear(&match);
- ds_put_format(&match, "ip && ip%s.dst == %s",
- lb_snat_ip.family == AF_INET ? "4" : "6",
- lb_force_snat_ip);
- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
- ds_cstr(&match), "ct_snat;");
-
- /* Load balanced traffic will have flags.force_snat_for_lb set.
- * Force SNAT it. */
- ds_clear(&match);
- ds_put_format(&match, "flags.force_snat_for_lb == 1 && ip");
- ds_clear(&actions);
- ds_put_format(&actions, "ct_snat(%s);", lb_force_snat_ip);
- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 100,
- ds_cstr(&match), ds_cstr(&actions));
- }
-
if (!od->l3dgw_port) {
+ if (dnat_force_snat_ip) {
+ if (dnat_force_snat_addrs.n_ipv4_addrs) {
+ build_lrouter_force_snat_flows(lflows, od, "4",
+ dnat_force_snat_addrs.ipv4_addrs[0].addr_s, "dnat");
+ }
+ if (dnat_force_snat_addrs.n_ipv6_addrs) {
+ build_lrouter_force_snat_flows(lflows, od, "6",
+ dnat_force_snat_addrs.ipv6_addrs[0].addr_s, "dnat");
+ }
+ }
+ if (lb_force_snat_ip) {
+ if (lb_force_snat_addrs.n_ipv4_addrs) {
+ build_lrouter_force_snat_flows(lflows, od, "4",
+ lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
+ }
+ if (lb_force_snat_addrs.n_ipv6_addrs) {
+ build_lrouter_force_snat_flows(lflows, od, "6",
+ lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
+ }
+ }
+
/* For gateway router, re-circulate every packet through
* the DNAT zone. This helps with the following.
*
@@ -9795,6 +9807,13 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
"ip", "flags.loopback = 1; ct_dnat;");
}
+ if (dnat_force_snat_ip) {
+ destroy_lport_addresses(&dnat_force_snat_addrs);
+ }
+ if (lb_force_snat_ip) {
+ destroy_lport_addresses(&lb_force_snat_addrs);
+ }
+
/* 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) {
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 98d36b270..5f4291559 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1816,27 +1816,29 @@
</column>
<column name="options" key="dnat_force_snat_ip">
<p>
- If set, indicates the IP address to use to force SNAT a packet
- that has already been DNATed in the gateway router. When multiple
- gateway routers are configured, a packet can potentially enter any
- of the gateway router, get DNATted and eventually reach the logical
- switch port. For the return traffic to go back to the same gateway
- router (for unDNATing), the packet needs a SNAT in the first place.
- This can be achieved by setting the above option with a gateway
- specific IP address.
+ If set, indicates a set of IP addresses to use to force SNAT a
+ packet that has already been DNATed in the gateway router. When
+ multiple gateway routers are configured, a packet can potentially
+ enter any of the gateway router, get DNATted and eventually reach the
+ logical switch port. For the return traffic to go back to the same
+ gateway router (for unDNATing), the packet needs a SNAT in the first
+ place. This can be achieved by setting the above option with a
+ gateway specific set of IP addresses. This option may have exactly
+ one IPv4 and/or one IPv6 address on it, separated by a a space.
</p>
</column>
<column name="options" key="lb_force_snat_ip">
<p>
- If set, indicates the IP address to use to force SNAT a packet
+ If set, indicates a set of IP addresses to use to force SNAT a packet
that has already been load-balanced in the gateway router. When
multiple gateway routers are configured, a packet can potentially
enter any of the gateway routers, get DNATted as part of the load-
balancing and eventually reach the logical switch port.
For the return traffic to go back to the same gateway router (for
unDNATing), the packet needs a SNAT in the first place. This can be
- achieved by setting the above option with a gateway specific IP
- address.
+ achieved by setting the above option with a gateway specific set of
+ IP addresses. This option may have exactly one IPv4 and/or one IPv6
+ address on it, separated by a space character.
</p>
</column>
<column name="options" key="mcast_relay" type='{"type": "boolean"}'>
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index bce097b17..0d478b4aa 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -1026,6 +1026,323 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
/connection dropped.*/d"])
AT_CLEANUP
+AT_SETUP([ovn -- multiple gateway routers, SNAT and DNAT - Dual Stack])
+AT_KEYWORDS([ovnnat])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+# Logical network:
+# Three LRs - R1, R2 and R3 that are connected to each other via LS "join"
+# in 20.0.0.0/24 and fd20::/64 networks. R1 has switches foo (192.168.1.0/24
+# and fd11::/64) and bar (192.168.2.0/24 and fd12::/64) connected to it. R2
+# has alice (172.16.1.0/24 and fd30::/64) connected to it. R3 has bob
+# (172.16.1.0/24 andfd30::/64) connected to it. Note how both alice and bob
+# have the same subnets behind them. We are trying to simulate external network
+# via those 2 switches. In real world the switch ports of these switches will
+# have addresses set as "unknown" to make them learning switches. Or those
+# switches will be "localnet" ones.
+#
+# foo -- R1 -- join - R2 -- alice
+# | |
+# bar ---- - R3 --- bob
+
+ovn-nbctl create Logical_Router name=R1
+ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
+ovn-nbctl create Logical_Router name=R3 options:chassis=hv1
+
+ovn-nbctl ls-add foo
+ovn-nbctl ls-add bar
+ovn-nbctl ls-add alice
+ovn-nbctl ls-add bob
+ovn-nbctl ls-add join
+
+# Connect foo to R1
+ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 fd11::1/64
+ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
+ type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
+
+# Connect bar to R1
+ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 fd12::1/64
+ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
+ type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
+
+# Connect alice to R2
+ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 fd30::1/64
+ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
+ type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
+
+# Connect bob to R3
+ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24 fd30::2/64
+ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \
+ type=router options:router-port=bob addresses=\"00:00:03:01:02:03\"
+
+# Connect R1 to join
+ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 fd20::1/64
+ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
+ type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
+
+# Connect R2 to join
+ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 fd20::2/64
+ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
+ type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
+
+# Connect R3 to join
+ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24 fd20::3/64
+ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \
+ type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"'
+
+# Install static routes with source ip address as the policy for routing.
+# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3.
+ovn-nbctl --policy="src-ip" lr-route-add R1 fd11::/64 fd20::2
+ovn-nbctl --policy="src-ip" lr-route-add R1 fd12::/64 fd20::3
+ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2
+ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3
+
+# Static routes.
+ovn-nbctl lr-route-add R2 fd11::/64 fd20::1
+ovn-nbctl lr-route-add R2 fd12::/64 fd20::1
+ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
+ovn-nbctl lr-route-add R3 fd11::/64 fd20::1
+ovn-nbctl lr-route-add R3 fd12::/64 fd20::1
+ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1
+
+# For gateway routers R2 and R3, set a force SNAT rule.
+ovn-nbctl set logical_router R2 options:dnat_force_snat_ip="20.0.0.2 fd20::2"
+ovn-nbctl set logical_router R3 options:dnat_force_snat_ip="20.0.0.3 fd20::3"
+
+# Logical port 'foo1' in switch 'foo'.
+ADD_NAMESPACES(foo1)
+ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
+ "192.168.1.1")
+ovn-nbctl lsp-add foo foo1 \
+-- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
+
+ADD_NAMESPACES(foo16)
+ADD_VETH(foo16, foo16, br-int, "fd11::2/64", "f0:00:00:02:02:03", \
+ "fd11::1")
+OVS_WAIT_UNTIL([test "$(ip netns exec foo16 ip a | grep fd11::2 | grep tentative)" = ""])
+ovn-nbctl lsp-add foo foo16 \
+-- lsp-set-addresses foo16 "f0:00:00:02:02:03 fd11::2"
+
+# Logical port 'alice1' in switch 'alice'.
+ADD_NAMESPACES(alice1)
+ADD_VETH(alice1, alice1, br-int, "172.16.1.3/24", "f0:00:00:01:02:04", \
+ "172.16.1.1")
+ovn-nbctl lsp-add alice alice1 \
+-- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.3"
+
+ADD_NAMESPACES(alice16)
+ADD_VETH(alice16, alice16, br-int, "fd30::3/64", "f0:00:00:02:02:04", \
+ "fd30::1")
+OVS_WAIT_UNTIL([test "$(ip netns exec alice16 ip a | grep fd30::3 | grep tentative)" = ""])
+ovn-nbctl lsp-add alice alice16 \
+-- lsp-set-addresses alice16 "f0:00:00:02:02:04 fd30::3"
+
+# Logical port 'bar1' in switch 'bar'.
+ADD_NAMESPACES(bar1)
+ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
+"192.168.2.1")
+ovn-nbctl lsp-add bar bar1 \
+-- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
+
+ADD_NAMESPACES(bar16)
+ADD_VETH(bar16, bar16, br-int, "fd12::2/64", "f0:00:00:02:02:05", \
+ "fd12::1")
+OVS_WAIT_UNTIL([test "$(ip netns exec bar16 ip a | grep fd12::2 | grep tentative)" = ""])
+ovn-nbctl lsp-add bar bar16 \
+-- lsp-set-addresses bar16 "f0:00:00:02:02:05 fd12::2"
+
+# Logical port 'bob1' in switch 'bob'.
+ADD_NAMESPACES(bob1)
+ADD_VETH(bob1, bob1, br-int, "172.16.1.4/24", "f0:00:00:01:02:06", \
+ "172.16.1.2")
+ovn-nbctl lsp-add bob bob1 \
+-- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4"
+
+ADD_NAMESPACES(bob16)
+ADD_VETH(bob16, bob16, br-int, "fd30::4/64", "f0:00:00:02:02:06", \
+ "fd30::2")
+OVS_WAIT_UNTIL([test "$(ip netns exec bob16 ip a | grep fd30::4 | grep tentative)" = ""])
+ovn-nbctl lsp-add bob bob16 \
+-- lsp-set-addresses bob16 "f0:00:00:02:02:06 fd30::4"
+
+# Router R2
+# Add a DNAT rule.
+ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
+ external_ip=30.0.0.2 -- add logical_router R2 nat @nat
+ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip='"fd11::2"' \
+ external_ip='"fd40::2"' -- add logical_router R2 nat @nat
+
+# Add a SNAT rule
+ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.1.2 \
+ external_ip=30.0.0.1 -- add logical_router R2 nat @nat
+ovn-nbctl -- --id=@nat create nat type="snat" logical_ip='"fd11::2"' \
+ external_ip='"fd40::1"' -- add logical_router R2 nat @nat
+
+# Router R3
+# Add a DNAT rule.
+ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip=192.168.1.2 \
+ external_ip=30.0.0.3 -- add logical_router R3 nat @nat
+ovn-nbctl -- --id=@nat create nat type="dnat" logical_ip='"fd11::2"' \
+ external_ip='"fd40::3"' -- add logical_router R3 nat @nat
+
+# Add a SNAT rule
+ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
+ external_ip=30.0.0.4 -- add logical_router R3 nat @nat
+ovn-nbctl -- --id=@nat create nat type="snat" logical_ip='"fd12::2"' \
+ external_ip='"fd40::4"' -- add logical_router R3 nat @nat
+
+# wait for ovn-controller to catch up.
+ovn-nbctl --wait=hv sync
+OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=fd40::4)'])
+OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=30.0.0.4)'])
+
+# North-South DNAT: 'alice1' should be able to ping 'foo1' via 30.0.0.2
+NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# North-South DNAT: 'alice16' should be able to ping 'foo16' via fd30::2
+NS_CHECK_EXEC([alice16], [ping -6 -q -c 3 -i 0.3 -w 2 fd40::2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Check conntrack entries.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=172.16.1.3,dst=30.0.0.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.3,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmpv6,orig=(src=fd30::3,dst=fd40::2,id=<cleared>,type=128,code=0),reply=(src=fd11::2,dst=fd30::3,id=<cleared>,type=129,code=0),zone=<cleared>
+])
+
+# But foo1 should receive traffic from 20.0.0.2
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=172.16.1.3,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=20.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+# But foo16 should receive traffic from fd20::2
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::2) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmpv6,orig=(src=fd30::3,dst=fd11::2,id=<cleared>,type=128,code=0),reply=(src=fd11::2,dst=fd20::2,id=<cleared>,type=129,code=0),zone=<cleared>
+])
+
+# North-South DNAT: 'bob1' should be able to ping 'foo1' via 30.0.0.3
+NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# North-South DNAT: 'bob16' should be able to ping 'foo16' via fd40::3
+NS_CHECK_EXEC([bob16], [ping -6 -q -c 3 -i 0.3 -w 2 fd40::3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Check conntrack entries.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.4) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=172.16.1.4,dst=30.0.0.3,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=172.16.1.4,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::4) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmpv6,orig=(src=fd30::4,dst=fd40::3,id=<cleared>,type=128,code=0),reply=(src=fd11::2,dst=fd30::4,id=<cleared>,type=129,code=0),zone=<cleared>
+])
+
+# But foo1 should receive traffic from 20.0.0.3
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=172.16.1.4,dst=192.168.1.2,id=<cleared>,type=8,code=0),reply=(src=192.168.1.2,dst=20.0.0.3,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+# But foo16 should receive traffic from fd20::3
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmpv6,orig=(src=fd30::4,dst=fd11::2,id=<cleared>,type=128,code=0),reply=(src=fd11::2,dst=fd20::3,id=<cleared>,type=129,code=0),zone=<cleared>
+])
+
+# South-North SNAT: 'bar1' pings 'bob1'. But 'bob1' receives traffic
+# from 30.0.0.4
+NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+# South-North SNAT: 'bar16' pings 'bob16'. But 'bob16' receives traffic
+# from fd40::4
+NS_CHECK_EXEC([bar16], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# We verify that SNAT indeed happened via 'dump-conntrack' command.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.4) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=192.168.2.2,dst=172.16.1.4,id=<cleared>,type=8,code=0),reply=(src=172.16.1.4,dst=30.0.0.4,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd40::4) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmpv6,orig=(src=fd12::2,dst=fd30::4,id=<cleared>,type=128,code=0),reply=(src=fd30::4,dst=fd40::4,id=<cleared>,type=129,code=0),zone=<cleared>
+])
+
+# South-North SNAT: 'foo1' pings 'alice1'. But 'alice1' receives traffic
+# from 30.0.0.1
+NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# South-North SNAT: 'foo16' pings 'alice16'. But 'alice16' receives traffic
+# from fd40::1
+NS_CHECK_EXEC([foo16], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# We verify that SNAT indeed happened via 'dump-conntrack' command.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=192.168.1.2,dst=172.16.1.3,id=<cleared>,type=8,code=0),reply=(src=172.16.1.3,dst=30.0.0.1,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd40::1) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmpv6,orig=(src=fd11::2,dst=fd30::3,id=<cleared>,type=128,code=0),reply=(src=fd30::3,dst=fd40::1,id=<cleared>,type=129,code=0),zone=<cleared>
+])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+
+
AT_SETUP([ovn -- load-balancing])
AT_KEYWORDS([ovnlb])
@@ -2405,6 +2722,230 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
/connection dropped.*/d"])
AT_CLEANUP
+AT_SETUP([ovn -- multiple gateway routers, load-balancing - Dual Stack])
+AT_KEYWORDS([ovnlb])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+# Logical network:
+# Three LRs - R1, R2 and R3 that are connected to each other via LS "join"
+# in 20.0.0.0/24 and fd20::/64 networks. R1 has switches foo (192.168.1.0/24
+# and fd11::/64) and bar (192.168.2.0/24 and fd12::/64) connected to it. R2
+# has alice (172.16.1.0/24 and fd72::/64) connected to it. R3 has bob
+# (172.16.1.0/24 and fd72::/64) connected to it. Note how both alice and
+# bob have the same subnets behind them. We are trying to simulate external
+# network via those 2 switches. In real world the switch ports of these
+# switches will have addresses set as "unknown" to make them learning switches.
+# Or those switches will be "localnet" ones.
+#
+# foo -- R1 -- join - R2 -- alice
+# | |
+# bar ---- - R3 --- bob
+
+ovn-nbctl create Logical_Router name=R1
+ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
+ovn-nbctl create Logical_Router name=R3 options:chassis=hv1
+
+ovn-nbctl ls-add foo
+ovn-nbctl ls-add bar
+ovn-nbctl ls-add alice
+ovn-nbctl ls-add bob
+ovn-nbctl ls-add join
+
+# Connect foo to R1
+ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 fd11::1/64
+ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
+ type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
+
+# Connect bar to R1
+ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24 fd12::1/64
+ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
+ type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
+
+# Connect alice to R2
+ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24 fd72::1/64
+ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
+ type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
+
+# Connect bob to R3
+ovn-nbctl lrp-add R3 bob 00:00:03:01:02:03 172.16.1.2/24 fd72::2/64
+ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob \
+ type=router options:router-port=bob addresses=\"00:00:03:01:02:03\"
+
+# Connect R1 to join
+ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24 fd20::1/64
+ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
+ type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
+
+# Connect R2 to join
+ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24 fd20::2/64
+ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
+ type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
+
+# Connect R3 to join
+ovn-nbctl lrp-add R3 R3_join 00:00:04:01:02:05 20.0.0.3/24 fd20::3/64
+ovn-nbctl lsp-add join r3-join -- set Logical_Switch_Port r3-join \
+ type=router options:router-port=R3_join addresses='"00:00:04:01:02:05"'
+
+# Install static routes with source ip address as the policy for routing.
+# We want traffic from 'foo' to go via R2 and traffic of 'bar' to go via R3.
+ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.1.0/24 20.0.0.2
+ovn-nbctl --policy="src-ip" lr-route-add R1 192.168.2.0/24 20.0.0.3
+ovn-nbctl --policy="src-ip" lr-route-add R1 fd11::/64 fd20::2
+ovn-nbctl --policy="src-ip" lr-route-add R1 fd12::/64 fd20::3
+
+# Static routes.
+ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
+ovn-nbctl lr-route-add R3 192.168.0.0/16 20.0.0.1
+ovn-nbctl lr-route-add R2 fd11::/64 fd20::1
+ovn-nbctl lr-route-add R2 fd12::/64 fd20::1
+ovn-nbctl lr-route-add R3 fd11::/64 fd20::1
+ovn-nbctl lr-route-add R3 fd12::/64 fd20::1
+
+# For gateway routers R2 and R3, set a force SNAT rule.
+ovn-nbctl set logical_router R2 options:lb_force_snat_ip="20.0.0.2 fd20::2"
+ovn-nbctl set logical_router R3 options:lb_force_snat_ip="20.0.0.3 fd20::3"
+
+# Logical port 'foo1' in switch 'foo'.
+ADD_NAMESPACES(foo1)
+ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
+ "192.168.1.1")
+ovn-nbctl lsp-add foo foo1 \
+-- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
+
+# Logical port 'foo16' in switch 'foo'.
+ADD_NAMESPACES(foo16)
+ADD_VETH(foo16, foo16, br-int, "fd11::2/64", "f0:00:06:01:02:03", \
+ "fd11::1")
+ovn-nbctl lsp-add foo foo16 \
+-- lsp-set-addresses foo16 "f0:00:06:01:02:03 fd11::2"
+
+# Logical port 'alice1' in switch 'alice'.
+ADD_NAMESPACES(alice1)
+ADD_VETH(alice1, alice1, br-int, "172.16.1.3/24", "f0:00:00:01:02:04", \
+ "172.16.1.1")
+ovn-nbctl lsp-add alice alice1 \
+-- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.3"
+
+# Logical port 'alice16' in switch 'alice'.
+ADD_NAMESPACES(alice16)
+ADD_VETH(alice16, alice16, br-int, "fd72::3/64", "f0:00:06:01:02:04", \
+ "fd72::1")
+ovn-nbctl lsp-add alice alice16 \
+-- lsp-set-addresses alice16 "f0:00:06:01:02:04 fd72::3"
+
+# Logical port 'bar1' in switch 'bar'.
+ADD_NAMESPACES(bar1)
+ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
+"192.168.2.1")
+ovn-nbctl lsp-add bar bar1 \
+-- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
+
+# Logical port 'bar16' in switch 'bar'.
+ADD_NAMESPACES(bar16)
+ADD_VETH(bar16, bar16, br-int, "fd12::2/64", "f0:00:06:01:02:05", \
+"fd12::1")
+ovn-nbctl lsp-add bar bar16 \
+-- lsp-set-addresses bar16 "f0:00:06:01:02:05 fd12::2"
+
+# Logical port 'bob1' in switch 'bob'.
+ADD_NAMESPACES(bob1)
+ADD_VETH(bob1, bob1, br-int, "172.16.1.4/24", "f0:00:00:01:02:06", \
+ "172.16.1.2")
+ovn-nbctl lsp-add bob bob1 \
+-- lsp-set-addresses bob1 "f0:00:00:01:02:06 172.16.1.4"
+
+# Logical port 'bob16' in switch 'bob'.
+ADD_NAMESPACES(bob16)
+ADD_VETH(bob16, bob16, br-int, "fd72::4/64", "f0:00:06:01:02:06", \
+ "fd72::2")
+ovn-nbctl lsp-add bob bob16 \
+-- lsp-set-addresses bob16 "f0:00:06:01:02:06 fd72::4"
+
+# Config OVN load-balancer with a VIP.
+uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2" \
+vips:\"fd30::1\"=\"fd11::2,fd12::2\"`
+ovn-nbctl set logical_router R2 load_balancer=$uuid
+ovn-nbctl set logical_router R3 load_balancer=$uuid
+
+# Wait for ovn-controller to catch up.
+ovn-nbctl --wait=hv sync
+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+grep 'nat(dst=192.168.2.2)'])
+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+grep 'nat(dst=fd12::2)'])
+
+# Start webservers in 'foo1', 'foo16, 'bar1', and 'bar16'.
+OVS_START_L7([foo1], [http])
+OVS_START_L7([bar1], [http])
+OVS_START_L7([foo16], [http6])
+OVS_START_L7([bar16], [http6])
+
+dnl Should work with the virtual IP address through NAT
+for i in `seq 1 20`; do
+ echo Request $i
+ NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+for i in `seq 1 20`; do
+ echo Request ${i}_6
+ NS_CHECK_EXEC([alice16], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget${i}_6.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::1) | grep -v fe80 |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+dnl Force SNAT should have worked.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0) |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=172.16.1.3,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::2) | grep -v fe80 |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=fd72::3,dst=fd11::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=fd72::3,dst=fd12::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+
AT_SETUP([ovn -- load balancing in router with gateway router port])
AT_KEYWORDS([ovnlb])
--
2.26.2