In case traffic that gets load balanced is DNAT-ed to a backend IP that
happens to be the source of the traffic then OVN performs an additional
SNAT to ensure that return traffic is directed through OVN.

Until now the load balancer VIP was chosen as SNAT IP.  However, in
specific scenarios, the CMS may prefer a different IP, e.g., a single
cluster-wide IP.  This commit adds support, through the newly added
Load_Balancer.option 'hairpin_snat_ip', to allow the CMS to explicitly
chose a SNAT IP.

Due to the fact that now traffic that was hairpinned might need to be
SNAT-ed to different IPs for different load balancers that share the
same VIP address value we need to also explicitly match on L4 protocol
and ports in the 'OFTABLE_CT_SNAT_FOR_VIP' table.

Signed-off-by: Dumitru Ceara <[email protected]>
---
 NEWS                |   3 +
 controller/lflow.c  |  53 +++++++++------
 lib/lb.c            |  26 ++++++++
 lib/lb.h            |  11 ++++
 lib/ovn-util.c      |  21 ++++++
 lib/ovn-util.h      |   1 +
 northd/ovn-northd.c |   9 +--
 ovn-nb.xml          |   8 +++
 ovn-sb.ovsschema    |   9 ++-
 ovn-sb.xml          |   8 +++
 tests/ovn-northd.at |   6 ++
 tests/ovn.at        | 182 ++++++++++++++++++++++++++++++++++++++--------------
 12 files changed, 261 insertions(+), 76 deletions(-)

diff --git a/NEWS b/NEWS
index e89c5f4..57a9ba9 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,9 @@ Post-v20.12.0
     "ovn-installed".  This external-id is set by ovn-controller only after all
     openflow operations corresponding to the OVS interface being added have
     been processed.
+  - Add a new option to Load_Balancer.options, "hairpin_snat_ip", to allow
+    users to explicitly select which source IP should be used for load
+    balancer hairpin traffic.
 
 OVN v20.12.0 - 18 Dec 2020
 --------------------------
diff --git a/controller/lflow.c b/controller/lflow.c
index c02585b..5c53b0d 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -1191,26 +1191,30 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
     struct match hairpin_reply_match = MATCH_CATCHALL_INITIALIZER;
 
     if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-        ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip);
+        ovs_be32 bip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip);
+        ovs_be32 vip4 = lb->hairpin_snat_ips.n_ipv4_addrs
+                        ? lb->hairpin_snat_ips.ipv4_addrs[0].addr
+                        : in6_addr_get_mapped_ipv4(&lb_vip->vip);
 
         match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IP));
-        match_set_nw_src(&hairpin_match, ip4);
-        match_set_nw_dst(&hairpin_match, ip4);
-
-        match_set_dl_type(&hairpin_reply_match,
-                          htons(ETH_TYPE_IP));
-        match_set_nw_src(&hairpin_reply_match, ip4);
-        match_set_nw_dst(&hairpin_reply_match,
-                         in6_addr_get_mapped_ipv4(&lb_vip->vip));
+        match_set_nw_src(&hairpin_match, bip4);
+        match_set_nw_dst(&hairpin_match, bip4);
+
+        match_set_dl_type(&hairpin_reply_match, htons(ETH_TYPE_IP));
+        match_set_nw_src(&hairpin_reply_match, bip4);
+        match_set_nw_dst(&hairpin_reply_match, vip4);
     } else {
+        struct in6_addr *bip6 = &lb_backend->ip;
+        struct in6_addr *vip6 = lb->hairpin_snat_ips.n_ipv6_addrs
+                                ? &lb->hairpin_snat_ips.ipv6_addrs[0].addr
+                                : &lb_vip->vip;
         match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IPV6));
-        match_set_ipv6_src(&hairpin_match, &lb_backend->ip);
-        match_set_ipv6_dst(&hairpin_match, &lb_backend->ip);
+        match_set_ipv6_src(&hairpin_match, bip6);
+        match_set_ipv6_dst(&hairpin_match, bip6);
 
-        match_set_dl_type(&hairpin_reply_match,
-                          htons(ETH_TYPE_IPV6));
-        match_set_ipv6_src(&hairpin_reply_match, &lb_backend->ip);
-        match_set_ipv6_dst(&hairpin_reply_match, &lb_vip->vip);
+        match_set_dl_type(&hairpin_reply_match, htons(ETH_TYPE_IPV6));
+        match_set_ipv6_src(&hairpin_reply_match, bip6);
+        match_set_ipv6_dst(&hairpin_reply_match, vip6);
     }
 
     if (lb_backend->port) {
@@ -1256,6 +1260,7 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
 static void
 add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
                          struct ovn_lb_vip *lb_vip,
+                         uint8_t lb_proto,
                          struct ovn_desired_flow_table *flow_table)
 {
     uint64_t stub[1024 / 8];
@@ -1279,10 +1284,16 @@ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
 
     if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
         nat->range_af = AF_INET;
-        nat->range.addr.ipv4.min = in6_addr_get_mapped_ipv4(&lb_vip->vip);
+        nat->range.addr.ipv4.min =
+            lb->hairpin_snat_ips.n_ipv4_addrs
+            ? lb->hairpin_snat_ips.ipv4_addrs[0].addr
+            : in6_addr_get_mapped_ipv4(&lb_vip->vip);
     } else {
         nat->range_af = AF_INET6;
-        nat->range.addr.ipv6.min = lb_vip->vip;
+        nat->range.addr.ipv6.min
+            = lb->hairpin_snat_ips.n_ipv6_addrs
+            ? lb->hairpin_snat_ips.ipv6_addrs[0].addr
+            : lb_vip->vip;
     }
     ofpacts.header = ofpbuf_push_uninit(&ofpacts, nat_offset);
     ofpact_finish(&ofpacts, &ct->ofpact);
@@ -1290,12 +1301,16 @@ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
     struct match match = MATCH_CATCHALL_INITIALIZER;
     if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
         match_set_dl_type(&match, htons(ETH_TYPE_IP));
-        match_set_ct_nw_dst(&match, nat->range.addr.ipv4.min);
+        match_set_ct_nw_dst(&match, in6_addr_get_mapped_ipv4(&lb_vip->vip));
     } else {
         match_set_dl_type(&match, htons(ETH_TYPE_IPV6));
         match_set_ct_ipv6_dst(&match, &lb_vip->vip);
     }
 
+    match_set_nw_proto(&match, lb_proto);
+    match_set_ct_nw_proto(&match, lb_proto);
+    match_set_ct_tp_dst(&match, htons(lb_vip->vip_port));
+
     uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
     match_set_ct_state_masked(&match, ct_state, ct_state);
 
@@ -1351,7 +1366,7 @@ consider_lb_hairpin_flows(const struct 
sbrec_load_balancer *sbrec_lb,
                                      flow_table);
         }
 
-        add_lb_ct_snat_vip_flows(lb, lb_vip, flow_table);
+        add_lb_ct_snat_vip_flows(lb, lb_vip, lb_proto, flow_table);
     }
 
     ovn_controller_lb_destroy(lb);
diff --git a/lib/lb.c b/lib/lb.c
index 2517c02..e11ac00 100644
--- a/lib/lb.c
+++ b/lib/lb.c
@@ -170,6 +170,24 @@ void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip 
*vip)
     free(vip->backends_nb);
 }
 
+static void
+ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
+                           const struct smap *lb_options,
+                           struct lport_addresses *hairpin_addrs)
+{
+    const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
+
+    if (!addresses) {
+        return;
+    }
+
+    if (!extract_ip_address(addresses, hairpin_addrs)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
+                     addresses, UUID_ARGS(lb_uuid));
+    }
+}
+
 struct ovn_northd_lb *
 ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb,
                      struct hmap *ports,
@@ -224,6 +242,9 @@ ovn_northd_lb_create(const struct nbrec_load_balancer 
*nbrec_lb,
         ds_chomp(&sel_fields, ',');
         lb->selection_fields = ds_steal_cstr(&sel_fields);
     }
+
+    ovn_lb_get_hairpin_snat_ip(&nbrec_lb->header_.uuid, &nbrec_lb->options,
+                               &lb->hairpin_snat_ips);
     return lb;
 }
 
@@ -260,6 +281,7 @@ ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
     free(lb->vips);
     free(lb->vips_nb);
     free(lb->selection_fields);
+    destroy_lport_addresses(&lb->hairpin_snat_ips);
     free(lb->dps);
     free(lb);
 }
@@ -289,6 +311,9 @@ ovn_controller_lb_create(const struct sbrec_load_balancer 
*sbrec_lb)
      * correct value.
      */
     lb->n_vips = n_vips;
+
+    ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
+                               &lb->hairpin_snat_ips);
     return lb;
 }
 
@@ -299,5 +324,6 @@ ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
         ovn_lb_vip_destroy(&lb->vips[i]);
     }
     free(lb->vips);
+    destroy_lport_addresses(&lb->hairpin_snat_ips);
     free(lb);
 }
diff --git a/lib/lb.h b/lib/lb.h
index 42c580b..dfce51c 100644
--- a/lib/lb.h
+++ b/lib/lb.h
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <netinet/in.h>
 #include "openvswitch/hmap.h"
+#include "ovn-util.h"
 
 struct nbrec_load_balancer;
 struct sbrec_load_balancer;
@@ -37,6 +38,11 @@ struct ovn_northd_lb {
     struct ovn_northd_lb_vip *vips_nb;
     size_t n_vips;
 
+    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
+                                              * as source for hairpinned
+                                              * traffic.
+                                              */
+
     size_t n_dps;
     size_t n_allocated_dps;
     const struct sbrec_datapath_binding **dps;
@@ -89,6 +95,11 @@ struct ovn_controller_lb {
 
     struct ovn_lb_vip *vips;
     size_t n_vips;
+
+    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
+                                              * as source for hairpinned
+                                              * traffic.
+                                              */
 };
 
 struct ovn_controller_lb *ovn_controller_lb_create(
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index 2136f90..b647106 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -232,6 +232,27 @@ extract_ip_addresses(const char *address, struct 
lport_addresses *laddrs)
     return false;
 }
 
+/* Extracts at most one IPv4 and at most one IPv6 address from 'address'
+ * which should be of the format 'IP1 [IP2]'.
+ *
+ * Return true if at most one IPv4 address and at most one IPv6 address
+ * is found in 'address'.  IPs must be host IPs, i.e., no unmasked bits.
+ *
+ * The caller must call destroy_lport_addresses().
+ */
+bool extract_ip_address(const char *address, struct lport_addresses *laddrs)
+{
+    if (!extract_ip_addresses(address, 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)) {
+        destroy_lport_addresses(laddrs);
+        return false;
+    }
+    return true;
+}
+
 /* Extracts the mac, IPv4 and IPv6 addresses from the
  * "nbrec_logical_router_port" parameter 'lrp'.  Stores the IPv4 and
  * IPv6 addresses in the 'ipv4_addrs' and 'ipv6_addrs' fields of
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index b604b07..0823461 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -72,6 +72,7 @@ bool extract_addresses(const char *address, struct 
lport_addresses *,
                        int *ofs);
 bool extract_lsp_addresses(const char *address, struct lport_addresses *);
 bool extract_ip_addresses(const char *address, struct lport_addresses *);
+bool extract_ip_address(const char *address, struct lport_addresses *);
 bool extract_lrp_networks(const struct nbrec_logical_router_port *,
                           struct lport_addresses *);
 bool extract_sbrec_binding_first_mac(const struct sbrec_port_binding *binding,
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 969fbe1..1f9dc65 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -3347,6 +3347,7 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap 
*datapaths,
         sbrec_load_balancer_set_name(lb->slb, lb->nlb->name);
         sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips);
         sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol);
+        sbrec_load_balancer_set_options(lb->slb, &lb->nlb->options);
         sbrec_load_balancer_set_datapaths(
             lb->slb, (struct sbrec_datapath_binding **)lb->dps,
             lb->n_dps);
@@ -8332,15 +8333,10 @@ get_force_snat_ip(struct ovn_datapath *od, const char 
*key_type,
         return false;
     }
 
-    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)) {
+    if (!extract_ip_address(addresses, laddrs)) {
         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;
     }
 
@@ -13566,6 +13562,7 @@ main(int argc, char *argv[])
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_name);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_vips);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_protocol);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_options);
     add_column_noalert(ovnsb_idl_loop.idl,
                        &sbrec_load_balancer_col_external_ids);
 
diff --git a/ovn-nb.xml b/ovn-nb.xml
index c2c58d8..58083f1 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1644,6 +1644,14 @@
         Please note using <code>--reject</code> option will disable empty_lb
         SB controller event for this load balancer.
       </column>
+
+      <column name="options" key="hairpin_snat_ip">
+        IP to be used as source IP for packets that have been hair-pinned
+        after load balancing.  The default behavior when the option is not set
+        is to use the load balancer VIP as source IP.  This option may have
+        exactly one IPv4 and/or one IPv6 address on it, separated by a space
+        character.
+      </column>
     </group>
   </table>
 
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index b418434..0d20f08 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "20.14.0",
-    "cksum": "1412040198 25748",
+    "version": "20.15.0",
+    "cksum": "539683023 25965",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -482,6 +482,11 @@
                     "type": {"key": {"type": "uuid",
                                      "refTable": "Datapath_Binding"},
                              "min": 0, "max": "unlimited"}},
+                "options": {
+                     "type": {"key": "string",
+                              "value": "string",
+                              "min": 0,
+                              "max": "unlimited"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 4c82d51..53fdc15 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -4233,6 +4233,14 @@ tcp.flags = RST;
       Datapaths to which this load balancer applies to.
     </column>
 
+    <group title="Load_Balancer options">
+    <column name="options" key="hairpin_snat_ip">
+      IP to be used as source IP for packets that have been hair-pinned after
+      load balancing.  This value is automatically populated by
+      <code>ovn-northd</code>.
+    </column>
+    </group>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 91eb9a3..9fce973 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2117,6 +2117,12 @@ echo
 echo "__file__:__line__: check that datapath sw1 has lb0 and lb1 set in the 
load_balancers column."
 check_column "$lb0_uuid $lb1_uuid" sb:datapath_binding load_balancers 
external_ids:name=sw1
 
+
+echo
+echo "__file__:__line__: Set hairpin_snat_ip on lb1 and check that SB DB is 
updated."
+check ovn-nbctl --wait=sb set Load_Balancer lb1 
options:hairpin_snat_ip="42.42.42.42 4242::4242"
+check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1 
options='{hairpin_snat_ip="42.42.42.42 4242::4242"}'
+
 echo
 echo "__file__:__line__: Delete load balancer lb1 an check that datapath sw1's 
load_balancers are updated accordingly."
 
diff --git a/tests/ovn.at b/tests/ovn.at
index 9bac94b..c1a972e 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -20814,8 +20814,9 @@ build_tcp_syn() {
 
 send_ipv4_pkt() {
     local hv=$1 inport=$2 eth_src=$3 eth_dst=$4
-    local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 ip_chksum=$9
-    local l4_payload=${10}
+    local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8
+    local l4_payload=$9
+    local hp_ip_src=${10}
     local hp_l4_payload=${11}
     local outfile=${12}
 
@@ -20823,8 +20824,10 @@ send_ipv4_pkt() {
 
     local eth=${eth_dst}${eth_src}0800
     local hp_eth=${eth_src}${eth_dst}0800
-    local 
ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_src}${ip_dst}
-    local 
hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_dst}${ip_src}
+    local ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${ip_src}${ip_dst}
+    ip=$(ip4_csum_inplace $ip)
+    local 
hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${hp_ip_src}${ip_src}
+    hp_ip=$(ip4_csum_inplace ${hp_ip})
     local packet=${eth}${ip}${l4_payload}
     local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload}
 
@@ -20836,15 +20839,16 @@ send_ipv6_pkt() {
     local hv=$1 inport=$2 eth_src=$3 eth_dst=$4
     local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8
     local l4_payload=$9
-    local hp_l4_payload=${10}
-    local outfile=${11}
+    local hp_ip_src=${10}
+    local hp_l4_payload=${11}
+    local outfile=${12}
 
     local ip_ttl=40
 
     local eth=${eth_dst}${eth_src}86dd
     local hp_eth=${eth_src}${eth_dst}86dd
     local ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_src}${ip_dst}
-    local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_dst}${ip_src}
+    local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${hp_ip_src}${ip_src}
     local packet=${eth}${ip}${l4_payload}
     local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload}
 
@@ -20886,18 +20890,35 @@ ovn-nbctl lsp-add sw sw-rtr                       \
 
 ovn-nbctl --wait=hv sync
 
-# Inject IPv4 TCP packet from lsp.
+ovn-sbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
 > expected
+
+# Inject IPv4 TCP packet from lsp.
 tcp_payload=$(build_tcp_syn 84d0 1f90 05a7)
 hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156e)
 send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
     $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
-    06 0028 35f5 \
-    ${tcp_payload} ${hp_tcp_payload} \
+    06 0028 \
+    ${tcp_payload} \
+    $(ip_to_hex 88 88 88 88) ${hp_tcp_payload} \
     expected
 
-ovn-sbctl dump-flows > sbflows
-AT_CAPTURE_FILE([sbflows])
+# Check that traffic is hairpinned.
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+# Change LB Hairpin SNAT IP.
+# Also flush conntrack to avoid reusing an existing entry.
+as hv1 ovs-appctl dpctl/flush-conntrack
+ovn-nbctl --wait=hv set load_balancer lb-ipv4-tcp 
options:hairpin_snat_ip="88.88.88.87"
+# Inject IPv4 TCP packet from lsp.
+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156f)
+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+    $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
+    06 0028 \
+    ${tcp_payload} \
+    $(ip_to_hex 88 88 88 87) ${hp_tcp_payload} \
+    expected
 
 # Check that traffic is hairpinned.
 OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
@@ -20907,8 +20928,25 @@ udp_payload=$(build_udp 84d0 0fc8 6666)
 hp_udp_payload=$(build_udp 84d0 07e5 6e49)
 send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
     $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
-    11 001e 35f4 \
-    ${udp_payload} ${hp_udp_payload} \
+    11 001e \
+    ${udp_payload} \
+    $(ip_to_hex 88 88 88 88) ${hp_udp_payload} \
+    expected
+
+# Check that traffic is hairpinned.
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+# Change LB Hairpin SNAT IP.
+# Also flush conntrack to avoid reusing an existing entry.
+as hv1 ovs-appctl dpctl/flush-conntrack
+ovn-nbctl --wait=hv set load_balancer lb-ipv4-udp 
options:hairpin_snat_ip="88.88.88.87"
+# Inject IPv4 UDP packet from lsp.
+hp_udp_payload=$(build_udp 84d0 07e5 6e4a)
+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+    $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
+    11 001e \
+    ${udp_payload} \
+    $(ip_to_hex 88 88 88 87) ${hp_udp_payload} \
     expected
 
 # Check that traffic is hairpinned.
@@ -20920,7 +20958,25 @@ hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0)
 send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
     42000000000000000000000000000001 88000000000000000000000000000088 \
     06 0014 \
-    ${tcp_payload} ${hp_tcp_payload} \
+    ${tcp_payload} \
+    88000000000000000000000000000088 ${hp_tcp_payload} \
+    expected
+
+# Check that traffic is hairpinned.
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+# Change LB Hairpin SNAT IP.
+# Also flush conntrack to avoid reusing an existing entry.
+as hv1 ovs-appctl dpctl/flush-conntrack
+ovn-nbctl --wait=hv set load_balancer lb-ipv6-tcp 
options:hairpin_snat_ip="8800::0087"
+
+# Inject IPv6 TCP packet from lsp.
+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc1)
+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+    42000000000000000000000000000001 88000000000000000000000000000088 \
+    06 0014 \
+    ${tcp_payload} \
+    88000000000000000000000000000087 ${hp_tcp_payload} \
     expected
 
 # Check that traffic is hairpinned.
@@ -20932,12 +20988,27 @@ hp_udp_payload=$(build_udp 84d0 07e5 a89b)
 send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
     42000000000000000000000000000001 88000000000000000000000000000088 \
     11 000a \
-    ${udp_payload} ${hp_udp_payload} \
+    ${udp_payload} \
+    88000000000000000000000000000088 ${hp_udp_payload} \
     expected
 
-# Check that traffic is hairpinned.
+Check that traffic is hairpinned.
 OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
 
+# Change LB Hairpin SNAT IP.
+# Also flush conntrack to avoid reusing an existing entry.
+as hv1 ovs-appctl dpctl/flush-conntrack
+ovn-nbctl --wait=hv set load_balancer lb-ipv6-udp 
options:hairpin_snat_ip="8800::0087"
+
+# Inject IPv6 UDP packet from lsp.
+hp_udp_payload=$(build_udp 84d0 07e5 a89b)
+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+    42000000000000000000000000000001 88000000000000000000000000000088 \
+    11 000a \
+    ${udp_payload} \
+    88000000000000000000000000000087 ${hp_udp_payload} \
+    expected
+
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 
@@ -23157,7 +23228,7 @@ 
priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 a
 ])
 
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8-], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
@@ -23191,8 +23262,8 @@ 
priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042
 ])
 
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8-], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
@@ -23228,8 +23299,8 @@ 
priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8-], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-udp
@@ -23257,8 +23328,9 @@ 
priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a
 ])
 
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
@@ -23276,8 +23348,9 @@ 
priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-tcp
@@ -23307,9 +23380,10 @@ 
priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a
 ])
 
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
@@ -23329,9 +23403,10 @@ 
priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 a
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-udp
@@ -23363,9 +23438,11 @@ 
priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac
 ])
 
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
@@ -23387,9 +23464,11 @@ 
priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp
@@ -23424,10 +23503,12 @@ 
priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac
 ])
 
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
@@ -23450,10 +23531,12 @@ 
priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac
 ])
 
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
 ])
 
 as hv2 ovs-vsctl del-port hv2-vif1
@@ -23502,9 +23585,10 @@ 
priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 ac
 ])
 
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d 
' ' -f8- | sort], [0], [dnl
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
 ])
 
 check ovn-nbctl --wait=hv ls-del sw0
-- 
1.8.3.1

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

Reply via email to