diff mbox series

[ovs-dev,v3] northd: introduce per-lb lb_skip_snat option

Message ID eae604c0fcaf7c7e6caf3189173eb2d9a16e680f.1617983315.git.lorenzo.bianconi@redhat.com
State Accepted
Headers show
Series [ovs-dev,v3] northd: introduce per-lb lb_skip_snat option | expand

Commit Message

Lorenzo Bianconi April 9, 2021, 3:50 p.m. UTC
Introduce skip_snat in load balancer option column in order to not
force_snat traffic that is hitting a given load balancer applied on a
logical router where lb_force_snat has been configured

https://bugzilla.redhat.com/show_bug.cgi?id=1927540

Tested-by: Andrew Stoycos <astoycos@redhat.com>
Acked-by: Mark Michelson <mmichels@redhat.com>
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
Changes since v2:
- fix typo in ovn-nb.xml

Changes since v1:
- rename lb_skip_snat in skip_snat
- add skip_snat documentation in ovn-nb.xml
- fix typos
- introduce enum for return value of snat_for_lb/
---
 include/ovn/logical-fields.h |  5 ++++
 lib/logical-fields.c         |  4 +++
 northd/lrouter.dl            | 14 ++++++++--
 northd/ovn-northd.8.xml      | 24 ++++++++++++++++-
 northd/ovn-northd.c          | 51 +++++++++++++++++++++++++-----------
 northd/ovn_northd.dl         | 32 ++++++++++++++++------
 ovn-nb.xml                   |  6 +++++
 ovs                          |  2 +-
 tests/ovn-northd.at          | 25 +++++++++++++++++-
 9 files changed, 135 insertions(+), 28 deletions(-)

Comments

Mark Michelson April 12, 2021, 8:42 p.m. UTC | #1
Thanks Lorenzo,
I pushed this to master.

On 4/9/21 11:50 AM, Lorenzo Bianconi wrote:
> Introduce skip_snat in load balancer option column in order to not
> force_snat traffic that is hitting a given load balancer applied on a
> logical router where lb_force_snat has been configured
> 
> https://bugzilla.redhat.com/show_bug.cgi?id=1927540
> 
> Tested-by: Andrew Stoycos <astoycos@redhat.com>
> Acked-by: Mark Michelson <mmichels@redhat.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> ---
> Changes since v2:
> - fix typo in ovn-nb.xml
> 
> Changes since v1:
> - rename lb_skip_snat in skip_snat
> - add skip_snat documentation in ovn-nb.xml
> - fix typos
> - introduce enum for return value of snat_for_lb/
> ---
>   include/ovn/logical-fields.h |  5 ++++
>   lib/logical-fields.c         |  4 +++
>   northd/lrouter.dl            | 14 ++++++++--
>   northd/ovn-northd.8.xml      | 24 ++++++++++++++++-
>   northd/ovn-northd.c          | 51 +++++++++++++++++++++++++-----------
>   northd/ovn_northd.dl         | 32 ++++++++++++++++------
>   ovn-nb.xml                   |  6 +++++
>   ovs                          |  2 +-
>   tests/ovn-northd.at          | 25 +++++++++++++++++-
>   9 files changed, 135 insertions(+), 28 deletions(-)
> 
> diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
> index 017176f98..d44b30b30 100644
> --- a/include/ovn/logical-fields.h
> +++ b/include/ovn/logical-fields.h
> @@ -66,6 +66,7 @@ enum mff_log_flags_bits {
>       MLF_LOOKUP_MAC_BIT = 6,
>       MLF_LOOKUP_LB_HAIRPIN_BIT = 7,
>       MLF_LOOKUP_FDB_BIT = 8,
> +    MLF_SKIP_SNAT_FOR_LB_BIT = 9,
>   };
>   
>   /* MFF_LOG_FLAGS_REG flag assignments */
> @@ -102,6 +103,10 @@ enum mff_log_flags {
>   
>       /* Indicate that the lookup in the fdb table was successful. */
>       MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT),
> +
> +    /* Indicate that a packet must not SNAT in the gateway router when
> +     * load-balancing has taken place. */
> +    MLF_SKIP_SNAT_FOR_LB = (1 << MLF_SKIP_SNAT_FOR_LB_BIT),
>   };
>   
>   /* OVN logical fields
> diff --git a/lib/logical-fields.c b/lib/logical-fields.c
> index 9d08b44c2..72853013e 100644
> --- a/lib/logical-fields.c
> +++ b/lib/logical-fields.c
> @@ -121,6 +121,10 @@ ovn_init_symtab(struct shash *symtab)
>                MLF_FORCE_SNAT_FOR_LB_BIT);
>       expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL,
>                                flags_str);
> +    snprintf(flags_str, sizeof flags_str, "flags[%d]",
> +             MLF_SKIP_SNAT_FOR_LB_BIT);
> +    expr_symtab_add_subfield(symtab, "flags.skip_snat_for_lb", NULL,
> +                             flags_str);
>   
>       /* Connection tracking state. */
>       expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
> diff --git a/northd/lrouter.dl b/northd/lrouter.dl
> index 36cedd2dc..312ceb9fc 100644
> --- a/northd/lrouter.dl
> +++ b/northd/lrouter.dl
> @@ -355,8 +355,18 @@ function lb_force_snat_router_ip(lr_options: Map<string, string>): bool {
>       lr_options.contains_key("chassis")
>   }
>   
> -function force_snat_for_lb(lr: nb::Logical_Router): bool {
> -    not get_force_snat_ip(lr, "lb").is_empty() or lb_force_snat_router_ip(lr.options)
> +typedef LBForceSNAT = NoForceSNAT
> +                    | ForceSNAT
> +                    | SkipSNAT
> +
> +function snat_for_lb(lr: nb::Logical_Router, lb: Ref<nb::Load_Balancer>): LBForceSNAT {
> +    if (lb.options.get_bool_def("skip_snat", false)) {
> +        return SkipSNAT
> +    };
> +    if (not get_force_snat_ip(lr, "lb").is_empty() or lb_force_snat_router_ip(lr.options)) {
> +        return ForceSNAT
> +    };
> +    return NoForceSNAT
>   }
>   
>   /* For each router, collect the set of IPv4 and IPv6 addresses used for SNAT,
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index a62f5c057..8197aa513 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -2754,7 +2754,11 @@ icmp6 {
>           (and optional port numbers) to load balance to.  If the router is
>           configured to force SNAT any load-balanced packets, the above action
>           will be replaced by <code>flags.force_snat_for_lb = 1;
> -        ct_lb(<var>args</var>);</code>. If health check is enabled, then
> +        ct_lb(<var>args</var>);</code>.
> +        If the load balancing rule is configured with <code>skip_snat</code>
> +        set to true, the above action will be replaced by
> +        <code>flags.skip_snat_for_lb = 1; ct_lb(<var>args</var>);</code>.
> +        If health check is enabled, then
>           <var>args</var> will only contain those endpoints whose service
>           monitor status entry in <code>OVN_Southbound</code> db is
>           either <code>online</code> or empty.
> @@ -2771,6 +2775,9 @@ icmp6 {
>           with an action of <code>ct_dnat;</code>. If the router is
>           configured to force SNAT any load-balanced packets, the above action
>           will be replaced by <code>flags.force_snat_for_lb = 1; ct_dnat;</code>.
> +        If the load balancing rule is configured with <code>skip_snat</code>
> +        set to true, the above action will be replaced by
> +        <code>flags.skip_snat_for_lb = 1; ct_dnat;</code>.
>         </li>
>   
>         <li>
> @@ -2785,6 +2792,9 @@ icmp6 {
>           to force SNAT any load-balanced packets, the above action will be
>           replaced by <code>flags.force_snat_for_lb = 1;
>           ct_lb(<var>args</var>);</code>.
> +        If the load balancing rule is configured with <code>skip_snat</code>
> +        set to true, the above action will be replaced by
> +        <code>flags.skip_snat_for_lb = 1; ct_lb(<var>args</var>);</code>.
>         </li>
>   
>         <li>
> @@ -2797,6 +2807,9 @@ icmp6 {
>           If the router is configured to force SNAT any load-balanced
>           packets, the above action will be replaced by
>           <code>flags.force_snat_for_lb = 1; ct_dnat;</code>.
> +        If the load balancing rule is configured with <code>skip_snat</code>
> +        set to true, the above action will be replaced by
> +        <code>flags.skip_snat_for_lb = 1; ct_dnat;</code>.
>         </li>
>   
>         <li>
> @@ -3829,6 +3842,15 @@ nd_ns {
>           </p>
>         </li>
>   
> +      <li>
> +        <p>
> +          If a load balancer configured to skip snat has been applied to
> +          the Gateway router pipeline, a priority-120 flow matches
> +          <code>flags.skip_snat_for_lb == 1 &amp;&amp; ip</code> with an
> +          action <code>next;</code>.
> +        </p>
> +      </li>
> +
>         <li>
>           <p>
>             If the Gateway router in the OVN Northbound database has been
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 9839b8c4f..8de106c84 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -8573,10 +8573,16 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
>       return true;
>   }
>   
> +enum lb_snat_type {
> +    NO_FORCE_SNAT,
> +    FORCE_SNAT,
> +    SKIP_SNAT,
> +};
> +
>   static void
>   add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
>                      struct ds *match, struct ds *actions, int priority,
> -                   bool force_snat_for_lb, struct ovn_lb_vip *lb_vip,
> +                   enum lb_snat_type snat_type, struct ovn_lb_vip *lb_vip,
>                      const char *proto, struct nbrec_load_balancer *lb,
>                      struct shash *meter_groups, struct sset *nat_entries)
>   {
> @@ -8585,9 +8591,10 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
>   
>       /* A match and actions for new connections. */
>       char *new_match = xasprintf("ct.new && %s", ds_cstr(match));
> -    if (force_snat_for_lb) {
> -        char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
> -                                      ds_cstr(actions));
> +    if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
> +        char *new_actions = xasprintf("flags.%s_snat_for_lb = 1; %s",
> +                snat_type == SKIP_SNAT ? "skip" : "force",
> +                ds_cstr(actions));
>           ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
>                                   new_match, new_actions, &lb->header_);
>           free(new_actions);
> @@ -8598,11 +8605,12 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
>   
>       /* A match and actions for established connections. */
>       char *est_match = xasprintf("ct.est && %s", ds_cstr(match));
> -    if (force_snat_for_lb) {
> +    if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
> +        char *est_actions = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
> +                snat_type == SKIP_SNAT ? "skip" : "force");
>           ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
> -                                est_match,
> -                                "flags.force_snat_for_lb = 1; ct_dnat;",
> -                                &lb->header_);
> +                                est_match, est_actions, &lb->header_);
> +        free(est_actions);
>       } else {
>           ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
>                                   est_match, "ct_dnat;", &lb->header_);
> @@ -8675,11 +8683,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
>       ds_put_format(&undnat_match, ") && outport == %s && "
>                    "is_chassis_resident(%s)", od->l3dgw_port->json_key,
>                    od->l3redirect_port->json_key);
> -    if (force_snat_for_lb) {
> +    if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
> +        char *action = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
> +                                 snat_type == SKIP_SNAT ? "skip" : "force");
>           ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
> -                                ds_cstr(&undnat_match),
> -                                "flags.force_snat_for_lb = 1; ct_dnat;",
> +                                ds_cstr(&undnat_match), action,
>                                   &lb->header_);
> +        free(action);
>       } else {
>           ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
>                                   ds_cstr(&undnat_match), "ct_dnat;",
> @@ -8706,6 +8716,12 @@ build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od,
>               ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
>           ovs_assert(lb);
>   
> +        bool lb_skip_snat = smap_get_bool(&nb_lb->options, "skip_snat", false);
> +        if (lb_skip_snat) {
> +            ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120,
> +                          "flags.skip_snat_for_lb == 1 && ip", "next;");
> +        }
> +
>           for (size_t j = 0; j < lb->n_vips; j++) {
>               struct ovn_lb_vip *lb_vip = &lb->vips[j];
>               struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
> @@ -8767,11 +8783,16 @@ build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od,
>                   ds_put_format(match, " && is_chassis_resident(%s)",
>                                 od->l3redirect_port->json_key);
>               }
> -            bool force_snat_for_lb =
> -                lb_force_snat_ip || od->lb_force_snat_router_ip;
> +
> +            enum lb_snat_type snat_type = NO_FORCE_SNAT;
> +            if (lb_skip_snat) {
> +                snat_type = SKIP_SNAT;
> +            } else if (lb_force_snat_ip || od->lb_force_snat_router_ip) {
> +                snat_type = FORCE_SNAT;
> +            }
>               add_router_lb_flow(lflows, od, match, actions, prio,
> -                               force_snat_for_lb, lb_vip, proto,
> -                               nb_lb, meter_groups, nat_entries);
> +                               snat_type, lb_vip, proto, nb_lb,
> +                               meter_groups, nat_entries);
>           }
>       }
>       sset_destroy(&all_ips);
> diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
> index 0063021e1..4c02e2d5a 100644
> --- a/northd/ovn_northd.dl
> +++ b/northd/ovn_northd.dl
> @@ -5367,6 +5367,16 @@ for (&Router(.lr = lr)) {
>            .external_ids     = map_empty())
>   }
>   
> +Flow(.logical_datapath = lr,
> +     .stage            = s_ROUTER_OUT_SNAT(),
> +     .priority         = 120,
> +     .__match          = "flags.skip_snat_for_lb == 1 && ip",
> +     .actions          = "next;",
> +     .external_ids     = stage_hint(lb._uuid)) :-
> +    LogicalRouterLB(lr, lb),
> +    lb.options.get_bool_def("skip_snat", false)
> +    .
> +
>   function lrouter_nat_is_stateless(nat: NAT): bool = {
>       Some{"true"} == nat.nat.options.get("stateless")
>   }
> @@ -6010,14 +6020,15 @@ for (RouterLBVIP(
>                   (Some{gwport}, true) -> " && is_chassis_resident(${redirect_port_name})",
>                   _ -> ""
>               } in
> -        var force_snat_for_lb = force_snat_for_lb(lr) in
> +        var snat_for_lb = snat_for_lb(lr, lb) in
>           {
>               /* A match and actions for established connections. */
>               var est_match = "ct.est && " ++ __match in
>               var actions =
> -                match (force_snat_for_lb) {
> -                    true -> "flags.force_snat_for_lb = 1; ct_dnat;",
> -                    false -> "ct_dnat;"
> +                match (snat_for_lb) {
> +                    SkipSNAT -> "flags.skip_snat_for_lb = 1; ct_dnat;",
> +                    ForceSNAT -> "flags.force_snat_for_lb = 1; ct_dnat;",
> +                    _ -> "ct_dnat;"
>                   } in
>               Flow(.logical_datapath = lr._uuid,
>                    .stage            = s_ROUTER_IN_DNAT(),
> @@ -6076,9 +6087,10 @@ for (RouterLBVIP(
>                   ") && outport == ${json_string_escape(gwport.name)} && "
>                   "is_chassis_resident(${redirect_port_name})" in
>               var action =
> -                match (force_snat_for_lb) {
> -                    true -> "flags.force_snat_for_lb = 1; ct_dnat;",
> -                    false -> "ct_dnat;"
> +                match (snat_for_lb) {
> +                    SkipSNAT -> "flags.skip_snat_for_lb = 1; ct_dnat;",
> +                    ForceSNAT -> "flags.force_snat_for_lb = 1; ct_dnat;",
> +                    _ -> "ct_dnat;"
>                   } in
>               Flow(.logical_datapath = lr._uuid,
>                    .stage            = s_ROUTER_OUT_UNDNAT(),
> @@ -6113,7 +6125,11 @@ Flow(.logical_datapath = r.lr._uuid,
>                 _ -> ""
>             },
>       var priority = if (lbvip.vip_port != 0) 120 else 110,
> -    var force_snat = if (force_snat_for_lb(r.lr)) "flags.force_snat_for_lb = 1; " else "",
> +    var force_snat = match (snat_for_lb(r.lr, lb)) {
> +        SkipSNAT -> "flags.skip_snat_for_lb = 1; ",
> +        ForceSNAT -> "flags.force_snat_for_lb = 1; ",
> +        _ -> ""
> +    },
>       var actions = build_lb_vip_actions(lbvip, s_ROUTER_OUT_SNAT(), force_snat).
>   
>   
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index b0a4adffe..408c98090 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1653,6 +1653,12 @@
>           exactly one IPv4 and/or one IPv6 address on it, separated by a space
>           character.
>         </column>
> +
> +      <column name="options" key="skip_snat">
> +        If the load balancing rule is configured with <code>skip_snat</code>
> +        option, the force_snat_for_lb option configured for the router
> +        pipeline will not be applied for this load balancer.
> +      </column>
>       </group>
>     </table>
>   
> diff --git a/ovs b/ovs
> index ac85cdb38..ac09cbfcb 160000
> --- a/ovs
> +++ b/ovs
> @@ -1 +1 @@
> -Subproject commit ac85cdb38c1f33e7952bc4c0347d6c7873fb56a1
> +Subproject commit ac09cbfcb70ac6f443f039d5934448bd80f74493
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 96476497d..037ea98c5 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -2847,6 +2847,29 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
>     table=1 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
>   ])
>   
> +check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080
> +check ovn-nbctl --wait=sb set load_balancer lb2 options:skip_snat=true
> +check ovn-nbctl lr-lb-add lr0 lb2
> +check ovn-nbctl --wait=sb lb-del lb1
> +ovn-sbctl dump-flows lr0 > lr0flows
> +
> +AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
> +  table=5 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
> +  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;)
> +  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
> +  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
> +  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;)
> +])
> +
> +AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
> +  table=6 (lr_in_dnat         ), priority=120  , match=(ct.est && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_dnat;)
> +  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
> +])
> +
> +AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
> +  table=1 (lr_out_snat        ), priority=120  , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;)
> +])
> +
>   AT_CLEANUP
>   ])
>   
> @@ -2950,4 +2973,4 @@ wait_row_count FDB 0
>   ovn-sbctl list FDB
>   
>   AT_CLEANUP
> -])
> \ No newline at end of file
> +])
>
diff mbox series

Patch

diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index 017176f98..d44b30b30 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -66,6 +66,7 @@  enum mff_log_flags_bits {
     MLF_LOOKUP_MAC_BIT = 6,
     MLF_LOOKUP_LB_HAIRPIN_BIT = 7,
     MLF_LOOKUP_FDB_BIT = 8,
+    MLF_SKIP_SNAT_FOR_LB_BIT = 9,
 };
 
 /* MFF_LOG_FLAGS_REG flag assignments */
@@ -102,6 +103,10 @@  enum mff_log_flags {
 
     /* Indicate that the lookup in the fdb table was successful. */
     MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT),
+
+    /* Indicate that a packet must not SNAT in the gateway router when
+     * load-balancing has taken place. */
+    MLF_SKIP_SNAT_FOR_LB = (1 << MLF_SKIP_SNAT_FOR_LB_BIT),
 };
 
 /* OVN logical fields
diff --git a/lib/logical-fields.c b/lib/logical-fields.c
index 9d08b44c2..72853013e 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -121,6 +121,10 @@  ovn_init_symtab(struct shash *symtab)
              MLF_FORCE_SNAT_FOR_LB_BIT);
     expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL,
                              flags_str);
+    snprintf(flags_str, sizeof flags_str, "flags[%d]",
+             MLF_SKIP_SNAT_FOR_LB_BIT);
+    expr_symtab_add_subfield(symtab, "flags.skip_snat_for_lb", NULL,
+                             flags_str);
 
     /* Connection tracking state. */
     expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
diff --git a/northd/lrouter.dl b/northd/lrouter.dl
index 36cedd2dc..312ceb9fc 100644
--- a/northd/lrouter.dl
+++ b/northd/lrouter.dl
@@ -355,8 +355,18 @@  function lb_force_snat_router_ip(lr_options: Map<string, string>): bool {
     lr_options.contains_key("chassis")
 }
 
-function force_snat_for_lb(lr: nb::Logical_Router): bool {
-    not get_force_snat_ip(lr, "lb").is_empty() or lb_force_snat_router_ip(lr.options)
+typedef LBForceSNAT = NoForceSNAT
+                    | ForceSNAT
+                    | SkipSNAT
+
+function snat_for_lb(lr: nb::Logical_Router, lb: Ref<nb::Load_Balancer>): LBForceSNAT {
+    if (lb.options.get_bool_def("skip_snat", false)) {
+        return SkipSNAT
+    };
+    if (not get_force_snat_ip(lr, "lb").is_empty() or lb_force_snat_router_ip(lr.options)) {
+        return ForceSNAT
+    };
+    return NoForceSNAT
 }
 
 /* For each router, collect the set of IPv4 and IPv6 addresses used for SNAT,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index a62f5c057..8197aa513 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -2754,7 +2754,11 @@  icmp6 {
         (and optional port numbers) to load balance to.  If the router is
         configured to force SNAT any load-balanced packets, the above action
         will be replaced by <code>flags.force_snat_for_lb = 1;
-        ct_lb(<var>args</var>);</code>. If health check is enabled, then
+        ct_lb(<var>args</var>);</code>.
+        If the load balancing rule is configured with <code>skip_snat</code>
+        set to true, the above action will be replaced by
+        <code>flags.skip_snat_for_lb = 1; ct_lb(<var>args</var>);</code>.
+        If health check is enabled, then
         <var>args</var> will only contain those endpoints whose service
         monitor status entry in <code>OVN_Southbound</code> db is
         either <code>online</code> or empty.
@@ -2771,6 +2775,9 @@  icmp6 {
         with an action of <code>ct_dnat;</code>. If the router is
         configured to force SNAT any load-balanced packets, the above action
         will be replaced by <code>flags.force_snat_for_lb = 1; ct_dnat;</code>.
+        If the load balancing rule is configured with <code>skip_snat</code>
+        set to true, the above action will be replaced by
+        <code>flags.skip_snat_for_lb = 1; ct_dnat;</code>.
       </li>
 
       <li>
@@ -2785,6 +2792,9 @@  icmp6 {
         to force SNAT any load-balanced packets, the above action will be
         replaced by <code>flags.force_snat_for_lb = 1;
         ct_lb(<var>args</var>);</code>.
+        If the load balancing rule is configured with <code>skip_snat</code>
+        set to true, the above action will be replaced by
+        <code>flags.skip_snat_for_lb = 1; ct_lb(<var>args</var>);</code>.
       </li>
 
       <li>
@@ -2797,6 +2807,9 @@  icmp6 {
         If the router is configured to force SNAT any load-balanced
         packets, the above action will be replaced by
         <code>flags.force_snat_for_lb = 1; ct_dnat;</code>.
+        If the load balancing rule is configured with <code>skip_snat</code>
+        set to true, the above action will be replaced by
+        <code>flags.skip_snat_for_lb = 1; ct_dnat;</code>.
       </li>
 
       <li>
@@ -3829,6 +3842,15 @@  nd_ns {
         </p>
       </li>
 
+      <li>
+        <p>
+          If a load balancer configured to skip snat has been applied to
+          the Gateway router pipeline, a priority-120 flow matches
+          <code>flags.skip_snat_for_lb == 1 &amp;&amp; ip</code> with an
+          action <code>next;</code>.
+        </p>
+      </li>
+
       <li>
         <p>
           If the Gateway router in the OVN Northbound database has been
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 9839b8c4f..8de106c84 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -8573,10 +8573,16 @@  get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
     return true;
 }
 
+enum lb_snat_type {
+    NO_FORCE_SNAT,
+    FORCE_SNAT,
+    SKIP_SNAT,
+};
+
 static void
 add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
                    struct ds *match, struct ds *actions, int priority,
-                   bool force_snat_for_lb, struct ovn_lb_vip *lb_vip,
+                   enum lb_snat_type snat_type, struct ovn_lb_vip *lb_vip,
                    const char *proto, struct nbrec_load_balancer *lb,
                    struct shash *meter_groups, struct sset *nat_entries)
 {
@@ -8585,9 +8591,10 @@  add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
 
     /* A match and actions for new connections. */
     char *new_match = xasprintf("ct.new && %s", ds_cstr(match));
-    if (force_snat_for_lb) {
-        char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
-                                      ds_cstr(actions));
+    if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
+        char *new_actions = xasprintf("flags.%s_snat_for_lb = 1; %s",
+                snat_type == SKIP_SNAT ? "skip" : "force",
+                ds_cstr(actions));
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
                                 new_match, new_actions, &lb->header_);
         free(new_actions);
@@ -8598,11 +8605,12 @@  add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
 
     /* A match and actions for established connections. */
     char *est_match = xasprintf("ct.est && %s", ds_cstr(match));
-    if (force_snat_for_lb) {
+    if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
+        char *est_actions = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
+                snat_type == SKIP_SNAT ? "skip" : "force");
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
-                                est_match,
-                                "flags.force_snat_for_lb = 1; ct_dnat;",
-                                &lb->header_);
+                                est_match, est_actions, &lb->header_);
+        free(est_actions);
     } else {
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
                                 est_match, "ct_dnat;", &lb->header_);
@@ -8675,11 +8683,13 @@  add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
     ds_put_format(&undnat_match, ") && outport == %s && "
                  "is_chassis_resident(%s)", od->l3dgw_port->json_key,
                  od->l3redirect_port->json_key);
-    if (force_snat_for_lb) {
+    if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
+        char *action = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
+                                 snat_type == SKIP_SNAT ? "skip" : "force");
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
-                                ds_cstr(&undnat_match),
-                                "flags.force_snat_for_lb = 1; ct_dnat;",
+                                ds_cstr(&undnat_match), action,
                                 &lb->header_);
+        free(action);
     } else {
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
                                 ds_cstr(&undnat_match), "ct_dnat;",
@@ -8706,6 +8716,12 @@  build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od,
             ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
         ovs_assert(lb);
 
+        bool lb_skip_snat = smap_get_bool(&nb_lb->options, "skip_snat", false);
+        if (lb_skip_snat) {
+            ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120,
+                          "flags.skip_snat_for_lb == 1 && ip", "next;");
+        }
+
         for (size_t j = 0; j < lb->n_vips; j++) {
             struct ovn_lb_vip *lb_vip = &lb->vips[j];
             struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
@@ -8767,11 +8783,16 @@  build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od,
                 ds_put_format(match, " && is_chassis_resident(%s)",
                               od->l3redirect_port->json_key);
             }
-            bool force_snat_for_lb =
-                lb_force_snat_ip || od->lb_force_snat_router_ip;
+
+            enum lb_snat_type snat_type = NO_FORCE_SNAT;
+            if (lb_skip_snat) {
+                snat_type = SKIP_SNAT;
+            } else if (lb_force_snat_ip || od->lb_force_snat_router_ip) {
+                snat_type = FORCE_SNAT;
+            }
             add_router_lb_flow(lflows, od, match, actions, prio,
-                               force_snat_for_lb, lb_vip, proto,
-                               nb_lb, meter_groups, nat_entries);
+                               snat_type, lb_vip, proto, nb_lb,
+                               meter_groups, nat_entries);
         }
     }
     sset_destroy(&all_ips);
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 0063021e1..4c02e2d5a 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -5367,6 +5367,16 @@  for (&Router(.lr = lr)) {
          .external_ids     = map_empty())
 }
 
+Flow(.logical_datapath = lr,
+     .stage            = s_ROUTER_OUT_SNAT(),
+     .priority         = 120,
+     .__match          = "flags.skip_snat_for_lb == 1 && ip",
+     .actions          = "next;",
+     .external_ids     = stage_hint(lb._uuid)) :-
+    LogicalRouterLB(lr, lb),
+    lb.options.get_bool_def("skip_snat", false)
+    .
+
 function lrouter_nat_is_stateless(nat: NAT): bool = {
     Some{"true"} == nat.nat.options.get("stateless")
 }
@@ -6010,14 +6020,15 @@  for (RouterLBVIP(
                 (Some{gwport}, true) -> " && is_chassis_resident(${redirect_port_name})",
                 _ -> ""
             } in
-        var force_snat_for_lb = force_snat_for_lb(lr) in
+        var snat_for_lb = snat_for_lb(lr, lb) in
         {
             /* A match and actions for established connections. */
             var est_match = "ct.est && " ++ __match in
             var actions =
-                match (force_snat_for_lb) {
-                    true -> "flags.force_snat_for_lb = 1; ct_dnat;",
-                    false -> "ct_dnat;"
+                match (snat_for_lb) {
+                    SkipSNAT -> "flags.skip_snat_for_lb = 1; ct_dnat;",
+                    ForceSNAT -> "flags.force_snat_for_lb = 1; ct_dnat;",
+                    _ -> "ct_dnat;"
                 } in
             Flow(.logical_datapath = lr._uuid,
                  .stage            = s_ROUTER_IN_DNAT(),
@@ -6076,9 +6087,10 @@  for (RouterLBVIP(
                 ") && outport == ${json_string_escape(gwport.name)} && "
                 "is_chassis_resident(${redirect_port_name})" in
             var action =
-                match (force_snat_for_lb) {
-                    true -> "flags.force_snat_for_lb = 1; ct_dnat;",
-                    false -> "ct_dnat;"
+                match (snat_for_lb) {
+                    SkipSNAT -> "flags.skip_snat_for_lb = 1; ct_dnat;",
+                    ForceSNAT -> "flags.force_snat_for_lb = 1; ct_dnat;",
+                    _ -> "ct_dnat;"
                 } in
             Flow(.logical_datapath = lr._uuid,
                  .stage            = s_ROUTER_OUT_UNDNAT(),
@@ -6113,7 +6125,11 @@  Flow(.logical_datapath = r.lr._uuid,
               _ -> ""
           },
     var priority = if (lbvip.vip_port != 0) 120 else 110,
-    var force_snat = if (force_snat_for_lb(r.lr)) "flags.force_snat_for_lb = 1; " else "",
+    var force_snat = match (snat_for_lb(r.lr, lb)) {
+        SkipSNAT -> "flags.skip_snat_for_lb = 1; ",
+        ForceSNAT -> "flags.force_snat_for_lb = 1; ",
+        _ -> ""
+    },
     var actions = build_lb_vip_actions(lbvip, s_ROUTER_OUT_SNAT(), force_snat).
 
 
diff --git a/ovn-nb.xml b/ovn-nb.xml
index b0a4adffe..408c98090 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1653,6 +1653,12 @@ 
         exactly one IPv4 and/or one IPv6 address on it, separated by a space
         character.
       </column>
+
+      <column name="options" key="skip_snat">
+        If the load balancing rule is configured with <code>skip_snat</code>
+        option, the force_snat_for_lb option configured for the router
+        pipeline will not be applied for this load balancer.
+      </column>
     </group>
   </table>
 
diff --git a/ovs b/ovs
index ac85cdb38..ac09cbfcb 160000
--- a/ovs
+++ b/ovs
@@ -1 +1 @@ 
-Subproject commit ac85cdb38c1f33e7952bc4c0347d6c7873fb56a1
+Subproject commit ac09cbfcb70ac6f443f039d5934448bd80f74493
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 96476497d..037ea98c5 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2847,6 +2847,29 @@  AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
   table=1 (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
 ])
 
+check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080
+check ovn-nbctl --wait=sb set load_balancer lb2 options:skip_snat=true
+check ovn-nbctl lr-lb-add lr0 lb2
+check ovn-nbctl --wait=sb lb-del lb1
+ovn-sbctl dump-flows lr0 > lr0flows
+
+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+  table=5 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
+  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;)
+  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
+  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
+  table=5 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;)
+])
+
+AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
+  table=6 (lr_in_dnat         ), priority=120  , match=(ct.est && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_dnat;)
+  table=6 (lr_in_dnat         ), priority=120  , match=(ct.new && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
+])
+
+AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
+  table=1 (lr_out_snat        ), priority=120  , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;)
+])
+
 AT_CLEANUP
 ])
 
@@ -2950,4 +2973,4 @@  wait_row_count FDB 0
 ovn-sbctl list FDB
 
 AT_CLEANUP
-])
\ No newline at end of file
+])