diff --git a/northd/northd.c b/northd/northd.c
index c5f69f469..8e5642c57 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -11566,10 +11566,17 @@ lrouter_dnat_and_snat_is_stateless(const struct nbrec_nat *nat)
            !strcmp(nat->type, "dnat_and_snat");
 }
 
+#define NAT_PRIORITY_MATCH_OFFSET 300
+
 static inline uint16_t
-lrouter_nat_get_priority(const struct ovn_datapath *od, bool is_dnat,
+lrouter_nat_get_priority(const struct ovn_datapath *od,
+                         const struct nbrec_nat *nat, bool is_dnat,
                          uint16_t prefix_len)
 {
+    if (nat->match[0]) {
+        return NAT_PRIORITY_MATCH_OFFSET + nat->priority;
+    }
+
     if (is_dnat) {
         return 100;
     }
@@ -11631,7 +11638,7 @@ lrouter_nat_add_ext_ip_match(const struct ovn_datapath *od,
          *
          */
         uint16_t priority =
-                lrouter_nat_get_priority(od, is_src, cidr_bits) + 2;
+                lrouter_nat_get_priority(od, nat, is_src, cidr_bits) + 2;
 
         ds_clone(&match_exempt, match);
         ds_put_format(&match_exempt, " && ip%s.%s == $%s",
@@ -14600,6 +14607,7 @@ build_lrouter_in_dnat_flow(struct lflow_table *lflows,
     const char *nat_action = lrouter_use_common_zone(od)
                              ? "ct_dnat_in_czone"
                              : "ct_dnat";
+    uint16_t priority = lrouter_nat_get_priority(od, nat, true, cidr_bits);
 
     ds_put_format(match, "ip && ip%c.dst == %s", is_v6 ? '6' : '4',
                   nat->external_ip);
@@ -14646,8 +14654,11 @@ build_lrouter_in_dnat_flow(struct lflow_table *lflows,
         ds_put_format(actions, ");");
     }
 
-    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT,
-                            lrouter_nat_get_priority(od, true, cidr_bits),
+    if (nat->match[0]) {
+        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);
 }
@@ -14772,6 +14783,10 @@ build_lrouter_out_snat_match(struct lflow_table *lflows,
                                      is_v6, is_reverse, cidr_bits,
                                      lflow_ref);
     }
+
+    if (nat->match[0]) {
+        ds_put_format(match, " && (%s)", nat->match);
+    }
 }
 
 static void
@@ -14790,7 +14805,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);
@@ -14825,7 +14840,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,
@@ -14884,7 +14899,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,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 973e8718e..3deaaa142 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -3794,6 +3794,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 &amp;&amp; ip4.dst ==
+          <var>A</var></code> or <code>ip &amp;&amp; ip6.dst == <var>A</var>
+          &amp;&amp; (<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>
@@ -5162,6 +5179,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 &amp;&amp;
+          ip4.src == <var>A</var> &amp;&amp; (!ct.trk || !ct.rpl) &amp;&amp;
+          (<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 f3ffb4a6d..9cc993bbe 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
 ])
@@ -12612,3 +12636,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") && (udp) && (!ct.trk || !ct.rpl)), 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 && (udp) && (!ct.trk || !ct.rpl)), 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 f49330a1e..229b09fc0 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -12735,3 +12735,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
+])
