+ ds_put_format(match, " && (%s)", nat->match);
+ }
+
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
ds_cstr(match), ds_cstr(actions),
&nat->header_, lflow_ref);
}
@@ -14751,7 +14760,7 @@ build_lrouter_out_snat_stateless_flow(struct
lflow_table *lflows,
ds_clear(actions);
- uint16_t priority = lrouter_nat_get_priority(od, false, cidr_bits);
+ uint16_t priority = lrouter_nat_get_priority(od, nat, false, cidr_bits);
build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
cidr_bits, is_v6, l3dgw_port, lflow_ref,
false);
@@ -14764,6 +14773,10 @@ build_lrouter_out_snat_stateless_flow(struct
lflow_table *lflows,
ds_put_format(actions, "ip%c.src=%s; next;",
is_v6 ? '6' : '4', nat->external_ip);
+ if (nat->match[0]) {
+ ds_put_format(match, " && (%s)", nat->match);
+ }
+
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
priority, ds_cstr(match),
ds_cstr(actions), &nat->header_,
@@ -14786,7 +14799,7 @@ build_lrouter_out_snat_in_czone_flow(struct lflow_table
*lflows,
ds_clear(actions);
- uint16_t priority = lrouter_nat_get_priority(od, false, cidr_bits);
+ uint16_t priority = lrouter_nat_get_priority(od, nat, false, cidr_bits);
struct ds zone_actions = DS_EMPTY_INITIALIZER;
build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
@@ -14845,7 +14858,7 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows,
ds_clear(actions);
- uint16_t priority = lrouter_nat_get_priority(od, false, cidr_bits);
+ uint16_t priority = lrouter_nat_get_priority(od, nat, false, cidr_bits);
build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
cidr_bits, is_v6, l3dgw_port, lflow_ref,
@@ -14864,6 +14877,10 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows,
}
ds_put_format(actions, ");");
+ if (nat->match[0]) {
+ ds_put_format(match, " && (%s)", nat->match);
+ }
+
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
priority, ds_cstr(match),
ds_cstr(actions), &nat->header_,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 2e588e28f..f4e5028ec 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -3780,6 +3780,23 @@ next;
<code>exempted_ext_ips</code>.
</p>
+ <p>
+ For each configuration in the OVN Northbound database, that asks
+ to change the destination IP address of a packet from <var>A</var>
+ to <var>B</var>, match <var>M</var> and priority <var>P</var>,
+ a logical flow that matches <code>ip && ip4.dst ==
+ <var>A</var></code> or <code>ip && ip6.dst == <var>A</var>
+ && (<var>M</var>)</code> with an action
+ <code>flags.loopback = 1; ct_dnat(<var>B</var>);</code>.
+ The priority of the flow is calculated based as
+ <code>300 + <var>P</var></code>. If the Gateway router is
+ configured to force SNAT any DNATed packet, the above action will
+ be replaced by <code>flags.force_snat_for_dnat = 1;
+ flags.loopback = 1; ct_dnat(<var>B</var>);</code>. If the NAT rule
+ is of type dnat_and_snat and has <code>stateless=true</code> in the
+ options, then the action would be <code>ip4/6.dst=
+ (<var>B</var>)</code>.
+ </p>
</li>
<li>
@@ -5148,6 +5165,20 @@ nd_ns {
options, then the action would be <code>ip4/6.src=
(<var>B</var>)</code>.
</p>
+
+ <p>
+ For each configuration in the OVN Northbound database, that asks
+ to change the source IP address of a packet from an IP address of
+ <var>A</var> or to change the source IP address of a packet that
+ belongs to network <var>A</var> to <var>B</var>, match <var>M</var>
+ and priority <var>P</var>, a flow matches <code>ip &&
+ ip4.src == <var>A</var> && (!ct.trk || !ct.rpl) &&
+ (<var>M</var>)</code> with an action <code>ct_snat(<var>B</var>);
+ </code>. The priority of the flow is calculated based as
+ <code>300 + <var>P</var></code>. If the NAT rule is of type
+ dnat_and_snat and has <code>stateless=true</code> in the options,
+ then the action would be <code>ip4/6.src=(<var>B</var>)</code>.
+ </p>
</li>
<li>
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 680d96675..9a339095b 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1024,22 +1024,46 @@ ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat
172.16.1.1 50.0.0.11
check_flow_match_sets 2 2 2 0 0 0 0
ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+echo
+echo "IPv4: stateful with match"
+ovn-nbctl --wait=sb --match="udp" lr-nat-add R1 dnat_and_snat 172.16.1.1
50.0.0.11
+check_flow_match_sets 2 2 2 0 0 0 0
+ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+
echo
echo "IPv4: stateless"
ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat 172.16.1.1
50.0.0.11
check_flow_match_sets 2 0 0 1 1 0 0
ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+echo
+echo "IPv4: stateless with match"
+ovn-nbctl --wait=sb --match="udp" --stateless lr-nat-add R1 dnat_and_snat
172.16.1.1 50.0.0.11
+check_flow_match_sets 2 0 0 1 1 0 0
+ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+
echo
echo "IPv6: stateful"
ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
check_flow_match_sets 2 2 2 0 0 0 0
ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1
+echo
+echo "IPv6: stateful with match"
+ovn-nbctl --wait=sb --match="udp" lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
+check_flow_match_sets 2 2 2 0 0 0 0
+ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1
+
echo
echo "IPv6: stateless"
ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
check_flow_match_sets 2 0 0 0 0 1 1
+ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1
+
+echo
+echo "IPv6: stateless with match"
+ovn-nbctl --wait=sb --match="udp" --stateless lr-nat-add R1 dnat_and_snat
fd01::1 fd11::2
+check_flow_match_sets 2 0 0 0 0 1 1
AT_CLEANUP
])
@@ -12544,3 +12568,58 @@ check_engine_stats northd recompute nocompute
check_engine_stats lflow recompute nocompute
AT_CLEANUP
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([NAT with match])
+ovn_start
+
+check ovn-sbctl chassis-add hv1 geneve 127.0.0.1
+
+check ovn-nbctl lr-add lr -- \
+ lrp-add lr lr-ls 02:ac:10:01:00:01 172.16.1.1/24
+
+check ovn-nbctl ls-add ls -- \
+ lsp-add ls ls-lr -- \
+ lsp-set-type ls-lr router -- \
+ lsp-set-addresses ls-lr router -- \
+ lsp-set-options ls-lr router-port=lr-ls
+
+check ovn-nbctl lr-nat-add lr snat 172.16.1.1 10.0.0.0/24
+check ovn-nbctl --match="udp" --priority=10 lr-nat-add lr snat 172.16.1.2
10.0.0.0/24
+
+check ovn-nbctl lr-nat-add lr dnat 10.0.0.100 10.0.0.10
+check ovn-nbctl --match="udp" --priority=20 lr-nat-add lr dnat 10.0.0.100
10.0.0.20
+
+check ovn-nbctl --wait=sb lrp-set-gateway-chassis lr-ls hv1
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_out_snat | ovn_strip_lflows], [0],
[dnl
+ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+ table=??(lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport ==
"lr-ls" && is_chassis_resident("cr-lr-ls") && (!ct.trk || !ct.rpl)),
action=(ct_snat(172.16.1.1);)
+ table=??(lr_out_snat ), priority=310 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr-ls"
&& is_chassis_resident("cr-lr-ls") && (!ct.trk || !ct.rpl) && (udp)), action=(ct_snat(172.16.1.2);)
+])
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_in_dnat | ovn_strip_lflows], [0],
[dnl
+ table=??(lr_in_dnat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_in_dnat ), priority=100 , match=(ip && ip4.dst == 10.0.0.100 && inport ==
"lr-ls" && is_chassis_resident("cr-lr-ls")), action=(ct_dnat(10.0.0.10);)
+ table=??(lr_in_dnat ), priority=320 , match=(ip && ip4.dst == 10.0.0.100 && inport == "lr-ls"
&& is_chassis_resident("cr-lr-ls") && (udp)), action=(ct_dnat(10.0.0.20);)
+])
+
+check ovn-nbctl lrp-del-gateway-chassis lr-ls hv1
+check ovn-nbctl --wait=sb set logical_router lr options:chassis=hv1
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_out_snat | ovn_strip_lflows], [0],
[dnl
+ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+ table=??(lr_out_snat ), priority=25 , match=(ip && ip4.src == 10.0.0.0/24
&& (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+ table=??(lr_out_snat ), priority=310 , match=(ip && ip4.src == 10.0.0.0/24
&& (!ct.trk || !ct.rpl) && (udp)), action=(ct_snat(172.16.1.2);)
+])
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_in_dnat | ovn_strip_lflows], [0],
[dnl
+ table=??(lr_in_dnat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_in_dnat ), priority=100 , match=(ip && ip4.dst ==
10.0.0.100), action=(flags.loopback = 1; ct_dnat(10.0.0.10);)
+ table=??(lr_in_dnat ), priority=320 , match=(ip && ip4.dst == 10.0.0.100
&& (udp)), action=(flags.loopback = 1; ct_dnat(10.0.0.20);)
+])
+
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 86fd240d2..a658849e6 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -12736,3 +12736,275 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
/.*terminating with signal 15.*/d"])
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([NAT arbitrary match - IPv4])
+AT_KEYWORDS([ovnlb])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+check 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 \
+ -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add lr
+check ovn-nbctl ls-add internal
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 192.168.100.1/24
+check ovn-nbctl lsp-add public pub-lr -- set Logical_Switch_Port pub-lr \
+ type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\"
+
+check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 192.168.200.1/24
+check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port
internal-lr \
+ type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\"
+
+check ovn-nbctl lsp-add internal vm0 \
+ -- lsp-set-addresses vm0 "f0:00:0f:01:02:03 192.168.200.10"
+check ovn-nbctl lsp-add internal vm2 \
+ -- lsp-set-addresses vm2 "f0:00:0f:01:02:04 192.168.200.20"
+check ovn-nbctl lsp-add internal vm3 \
+ -- lsp-set-addresses vm3 "f0:00:0f:01:02:05 192.168.200.30"
+
+ovn-nbctl lsp-add public ln_port \
+ -- lsp-set-addresses ln_port unknown \
+ -- lsp-set-type ln_port localnet \
+ -- lsp-set-options ln_port network_name=phynet
+
+check ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(vm0)
+ADD_VETH(vm0, vm0, br-int, "192.168.200.10/24", "f0:00:0f:01:02:03",
"192.168.200.1")
+
+ADD_NAMESPACES(vm1)
+ADD_VETH(vm1, vm1, br-ext, "192.168.100.10/24", "f0:00:00:01:02:03",
"192.168.100.1")
+
+ADD_NAMESPACES(vm2)
+ADD_VETH(vm2, vm2, br-int, "192.168.200.20/24", "f0:00:0f:01:02:04",
"192.168.200.1")
+
+ADD_NAMESPACES(vm3)
+ADD_VETH(vm3, vm3, br-int, "192.168.200.30/24", "f0:00:0f:01:02:05",
"192.168.200.1")
+
+NETNS_DAEMONIZE([vm0], [nc -l -k 192.168.200.10 4242], [server0.pid])
+NETNS_DAEMONIZE([vm1], [nc -l -k 192.168.100.10 4242], [server1.pid])
+NETNS_DAEMONIZE([vm2], [nc -l -k 192.168.200.20 4242], [server2.pid])
+NETNS_DAEMONIZE([vm3], [nc -l -k 192.168.200.30 4242], [server3.pid])
+
+check_snat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr snat 192.168.100.1 192.168.200.0/24
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr snat
192.168.100.11 192.168.200.0/24
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr snat
192.168.100.12 192.168.200.0/24
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2000])
+ NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2001])
+ NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2002])
+
+ snat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_snat | cut
-d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.100.10) |
grep "zone=$snat_id"], [0], [dnl
+tcp,orig=(src=192.168.200.10,dst=192.168.100.10,sport=<cleared>,dport=<cleared>),reply=(src=192.168.100.10,dst=192.168.100.1,sport=<cleared>,dport=<cleared>),zone=$snat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.200.10,dst=192.168.100.10,sport=<cleared>,dport=<cleared>),reply=(src=192.168.100.10,dst=192.168.100.11,sport=<cleared>,dport=<cleared>),zone=$snat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.200.10,dst=192.168.100.10,sport=<cleared>,dport=<cleared>),reply=(src=192.168.100.10,dst=192.168.100.12,sport=<cleared>,dport=<cleared>),zone=$snat_id,protoinfo=(state=<cleared>)
+])
+}
+
+check_dnat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr dnat 192.168.100.100 192.168.200.10
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr dnat
192.168.100.100 192.168.200.20
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr dnat
192.168.100.100 192.168.200.30
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2000])
+ NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2001])
+ NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2002])
+
+ dnat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_dnat | cut
-d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.100.100) |
grep "zone=$dnat_id"], [0], [dnl
+tcp,orig=(src=192.168.100.10,dst=192.168.100.100,sport=<cleared>,dport=<cleared>),reply=(src=192.168.200.10,dst=192.168.100.10,sport=<cleared>,dport=<cleared>),zone=$dnat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.100.10,dst=192.168.100.100,sport=<cleared>,dport=<cleared>),reply=(src=192.168.200.20,dst=192.168.100.10,sport=<cleared>,dport=<cleared>),zone=$dnat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.100.10,dst=192.168.100.100,sport=<cleared>,dport=<cleared>),reply=(src=192.168.200.30,dst=192.168.100.10,sport=<cleared>,dport=<cleared>),zone=$dnat_id,protoinfo=(state=<cleared>)
+])
+}
+
+check ovn-nbctl lrp-set-gateway-chassis lr-pub hv1
+check_snat
+check_dnat
+check ovn-nbctl lrp-del-gateway-chassis lr-pub hv1
+
+check ovn-nbctl set logical_router lr options:chassis=hv1
+check_snat
+check_dnat
+
+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
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([NAT arbitrary match - IPv6])
+AT_KEYWORDS([ovnlb])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+check 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 \
+ -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add lr
+check ovn-nbctl ls-add internal
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 1000::1/64
+check ovn-nbctl lsp-add public pub-lr -- set Logical_Switch_Port pub-lr \
+ type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\"
+
+check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 2000::1/64
+check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port
internal-lr \
+ type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\"
+
+check ovn-nbctl lsp-add internal vm0 \
+ -- lsp-set-addresses vm0 "f0:00:0f:01:02:03 2000::10"
+check ovn-nbctl lsp-add internal vm2 \
+ -- lsp-set-addresses vm2 "f0:00:0f:01:02:04 2000::20"
+check ovn-nbctl lsp-add internal vm3 \
+ -- lsp-set-addresses vm3 "f0:00:0f:01:02:05 2000::30"
+
+ovn-nbctl lsp-add public ln_port \
+ -- lsp-set-addresses ln_port unknown \
+ -- lsp-set-type ln_port localnet \
+ -- lsp-set-options ln_port network_name=phynet
+
+check ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(vm0)
+ADD_VETH(vm0, vm0, br-int, "2000::10/64", "f0:00:0f:01:02:03", "2000::1",
"nodad")
+
+ADD_NAMESPACES(vm1)
+ADD_VETH(vm1, vm1, br-ext, "1000::10/64", "f0:00:00:01:02:03", "1000::1",
"nodad")
+
+ADD_NAMESPACES(vm2)
+ADD_VETH(vm2, vm2, br-int, "2000::20/64", "f0:00:0f:01:02:04", "2000::1",
"nodad")
+
+ADD_NAMESPACES(vm3)
+ADD_VETH(vm3, vm3, br-int, "2000::30/64", "f0:00:0f:01:02:05", "2000::1",
"nodad")
+
+NETNS_DAEMONIZE([vm0], [nc -lk 2000::10 4242 > /dev/null], [server0.pid])
+NETNS_DAEMONIZE([vm1], [nc -lk 1000::10 4242 > /dev/null], [server1.pid])
+NETNS_DAEMONIZE([vm2], [nc -lk 2000::20 4242 > /dev/null], [server2.pid])
+NETNS_DAEMONIZE([vm3], [nc -lk 2000::30 4242 > /dev/null], [server3.pid])
+
+check_snat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr snat 1000::1 2000::/64
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr snat
1000::11 2000::/64
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr snat
1000::12 2000::/64
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2000])
+ NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2001])
+ NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2002])
+
+ snat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_snat | cut
-d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(1000::10) | grep
"zone=$snat_id"], [0], [dnl
+tcp,orig=(src=2000::10,dst=1000::10,sport=<cleared>,dport=<cleared>),reply=(src=1000::10,dst=1000::1,sport=<cleared>,dport=<cleared>),zone=$snat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=2000::10,dst=1000::10,sport=<cleared>,dport=<cleared>),reply=(src=1000::10,dst=1000::11,sport=<cleared>,dport=<cleared>),zone=$snat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=2000::10,dst=1000::10,sport=<cleared>,dport=<cleared>),reply=(src=1000::10,dst=1000::12,sport=<cleared>,dport=<cleared>),zone=$snat_id,protoinfo=(state=<cleared>)
+])
+}
+
+check_dnat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr dnat 1000::100 2000::10
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr dnat
1000::100 2000::20
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr dnat
1000::100 2000::30
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2000])
+ NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2001])
+ NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2002])
+
+ dnat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_dnat | cut
-d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(1000::100) | grep
"zone=$dnat_id"], [0], [dnl
+tcp,orig=(src=1000::10,dst=1000::100,sport=<cleared>,dport=<cleared>),reply=(src=2000::10,dst=1000::10,sport=<cleared>,dport=<cleared>),zone=$dnat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=1000::10,dst=1000::100,sport=<cleared>,dport=<cleared>),reply=(src=2000::20,dst=1000::10,sport=<cleared>,dport=<cleared>),zone=$dnat_id,protoinfo=(state=<cleared>)
+tcp,orig=(src=1000::10,dst=1000::100,sport=<cleared>,dport=<cleared>),reply=(src=2000::30,dst=1000::10,sport=<cleared>,dport=<cleared>),zone=$dnat_id,protoinfo=(state=<cleared>)
+])
+}
+
+check ovn-nbctl lrp-set-gateway-chassis lr-pub hv1
+check_snat
+check_dnat
+check ovn-nbctl lrp-del-gateway-chassis lr-pub hv1
+
+check ovn-nbctl set logical_router lr options:chassis=hv1
+check_snat
+check_dnat
+
+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
+])