diff mbox series

[ovs-dev,v4,1/2] actions: Add new action called ct_commit_nat

Message ID 20221122092933.291120-2-amusil@redhat.com
State Accepted
Delegated to: Dumitru Ceara
Headers show
Series Allow related traffic for LB | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Ales Musil Nov. 22, 2022, 9:29 a.m. UTC
Add action called ct_commit_nat, that performs
NAT while committing the connection. This is
useful for related traffic on which we need
to perform NAT, mainly ICMP. We need to
commit due to design decision of OvS[0]:

"Connections identified as rel are separate from
the originating connection and must be committed separately."

[0] http://www.openvswitch.org/support/dist-docs/ovs-fields.7.txt

Reported-at: https://bugzilla.redhat.com/2126083
Acked-by: Mark Michelson <mmichels@redhat.com>
Signed-off-by: Ales Musil <amusil@redhat.com>
---
v3: Rebase on current main.
---
 include/ovn/actions.h |  3 +++
 lib/actions.c         | 42 +++++++++++++++++++++++++++++++++++++++++-
 ovn-sb.xml            | 12 ++++++++++++
 tests/ovn.at          |  5 +++++
 utilities/ovn-trace.c | 34 ++++++++++++++++++++++++++++++++++
 5 files changed, 95 insertions(+), 1 deletion(-)

Comments

Dumitru Ceara Nov. 25, 2022, 11:08 a.m. UTC | #1
On 11/22/22 10:29, Ales Musil wrote:
> Add action called ct_commit_nat, that performs
> NAT while committing the connection. This is
> useful for related traffic on which we need
> to perform NAT, mainly ICMP. We need to
> commit due to design decision of OvS[0]:
> 
> "Connections identified as rel are separate from
> the originating connection and must be committed separately."
> 
> [0] http://www.openvswitch.org/support/dist-docs/ovs-fields.7.txt
> 
> Reported-at: https://bugzilla.redhat.com/2126083
> Acked-by: Mark Michelson <mmichels@redhat.com>
> Signed-off-by: Ales Musil <amusil@redhat.com>
> ---
> v3: Rebase on current main.
> ---

Hi Ales,

The change looks mostly OK to me.  I just have a few comments below.

>  include/ovn/actions.h |  3 +++
>  lib/actions.c         | 42 +++++++++++++++++++++++++++++++++++++++++-
>  ovn-sb.xml            | 12 ++++++++++++
>  tests/ovn.at          |  5 +++++
>  utilities/ovn-trace.c | 34 ++++++++++++++++++++++++++++++++++
>  5 files changed, 95 insertions(+), 1 deletion(-)
> 
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index fdb6ab08b..d1776d684 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -74,6 +74,7 @@ struct ovn_extend_table;
>      OVNACT(CT_LB_MARK,        ovnact_ct_lb)           \
>      OVNACT(SELECT,            ovnact_select)          \
>      OVNACT(CT_CLEAR,          ovnact_null)            \
> +    OVNACT(CT_COMMIT_NAT,     ovnact_ct_nat)          \
>      OVNACT(CLONE,             ovnact_nest)            \
>      OVNACT(ARP,               ovnact_nest)            \
>      OVNACT(ICMP4,             ovnact_nest)            \
> @@ -277,6 +278,8 @@ struct ovnact_ct_nat {

Can you please add OVNACT_CT_COMMIT_NAT to the comment just before
'struct ovnact_ct_nat {'?

>         uint16_t port_hi;
>      } port_range;
>  
> +    bool commit;                /* Explicit commit action. */
> +
>      uint8_t ltable;             /* Logical table ID of next table. */
>  };
>  
> diff --git a/lib/actions.c b/lib/actions.c
> index b59f364bf..fb688eeb1 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -920,6 +920,7 @@ parse_ct_nat(struct action_context *ctx, const char *name,
>          return;
>      }
>      cn->ltable = ctx->pp->cur_ltable + 1;
> +    cn->commit = false;
>  
>      if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
>          if (ctx->lexer->token.type != LEX_T_INTEGER
> @@ -929,9 +930,11 @@ parse_ct_nat(struct action_context *ctx, const char *name,
>              return;
>          }
>          if (ctx->lexer->token.format == LEX_F_IPV4) {
> +            cn->commit = true;
>              cn->family = AF_INET;
>              cn->ipv4 = ctx->lexer->token.value.ipv4;
>          } else if (ctx->lexer->token.format == LEX_F_IPV6) {
> +            cn->commit = true;
>              cn->family = AF_INET6;
>              cn->ipv6 = ctx->lexer->token.value.ipv6;
>          }
> @@ -1004,6 +1007,24 @@ parse_CT_SNAT_IN_CZONE(struct action_context *ctx)
>                   ovnact_put_CT_SNAT_IN_CZONE(ctx->ovnacts));
>  }
>  
> +static void
> +parse_CT_COMMIT_NAT(struct action_context *ctx)
> +{
> +    add_prerequisite(ctx, "ip");
> +
> +    if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
> +        lexer_error(ctx->lexer,
> +                    "\"ct_commit_related\" action not allowed in last table.");
> +        return;
> +    }
> +
> +    struct ovnact_ct_nat *cn = ovnact_put_CT_COMMIT_NAT(ctx->ovnacts);
> +    cn->commit = true;
> +    cn->ltable = ctx->pp->cur_ltable + 1;
> +    cn->family = AF_UNSPEC;
> +    cn->port_range.exists = false;
> +}
> +
>  static void
>  format_ct_nat(const struct ovnact_ct_nat *cn, const char *name, struct ds *s)
>  {
> @@ -1053,6 +1074,12 @@ format_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn, struct ds *s)
>      format_ct_nat(cn, "ct_snat_in_czone", s);
>  }
>  
> +static void
> +format_CT_COMMIT_NAT(const struct ovnact_ct_nat *cn OVS_UNUSED, struct ds *s)
> +{
> +    ds_put_cstr(s, "ct_commit_nat;");
> +}
> +
>  static void
>  encode_ct_nat(const struct ovnact_ct_nat *cn,
>                const struct ovnact_encode_params *ep,
> @@ -1104,7 +1131,7 @@ encode_ct_nat(const struct ovnact_ct_nat *cn,
>  
>      ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
>      ct = ofpacts->header;
> -    if (cn->family == AF_INET || cn->family == AF_INET6) {
> +    if (cn->commit) {
>          ct->flags |= NX_CT_F_COMMIT;
>      }
>      ofpact_finish(ofpacts, &ct->ofpact);
> @@ -1143,6 +1170,17 @@ encode_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn,
>      encode_ct_nat(cn, ep, true, ep->common_nat_ct_zone, ofpacts);
>  }
>  
> +static void
> +encode_CT_COMMIT_NAT(const struct ovnact_ct_nat *cn,
> +                         const struct ovnact_encode_params *ep,
> +                         struct ofpbuf *ofpacts)
> +{
> +    enum mf_field_id zone = ep->is_switch
> +                            ? MFF_LOG_CT_ZONE
> +                            : MFF_LOG_DNAT_ZONE;
> +    encode_ct_nat(cn, ep, false, zone, ofpacts);

We only deal with DNAT (third argument is 'false').  I'm pretty sure
there can also be a case when we need to allow and commit related SNATed
traffic.  Or am I missing something?

> +}

Thanks,
Dumitru
Dumitru Ceara Nov. 28, 2022, 3:41 p.m. UTC | #2
On 11/25/22 12:08, Dumitru Ceara wrote:
> On 11/22/22 10:29, Ales Musil wrote:
>> Add action called ct_commit_nat, that performs
>> NAT while committing the connection. This is
>> useful for related traffic on which we need
>> to perform NAT, mainly ICMP. We need to
>> commit due to design decision of OvS[0]:
>>
>> "Connections identified as rel are separate from
>> the originating connection and must be committed separately."
>>
>> [0] http://www.openvswitch.org/support/dist-docs/ovs-fields.7.txt
>>
>> Reported-at: https://bugzilla.redhat.com/2126083
>> Acked-by: Mark Michelson <mmichels@redhat.com>
>> Signed-off-by: Ales Musil <amusil@redhat.com>
>> ---
>> v3: Rebase on current main.
>> ---

[...]

>> @@ -1143,6 +1170,17 @@ encode_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn,
>>      encode_ct_nat(cn, ep, true, ep->common_nat_ct_zone, ofpacts);
>>  }
>>  
>> +static void
>> +encode_CT_COMMIT_NAT(const struct ovnact_ct_nat *cn,
>> +                         const struct ovnact_encode_params *ep,
>> +                         struct ofpbuf *ofpacts)
>> +{
>> +    enum mf_field_id zone = ep->is_switch
>> +                            ? MFF_LOG_CT_ZONE
>> +                            : MFF_LOG_DNAT_ZONE;
>> +    encode_ct_nat(cn, ep, false, zone, ofpacts);
> 
> We only deal with DNAT (third argument is 'false').  I'm pretty sure
> there can also be a case when we need to allow and commit related SNATed
> traffic.  Or am I missing something?
> 


Ales pointed out privately that we only use the value of the third
argument of echode_ct_nat() if the address family is unspecified.
That's not the case.  So we will be committing both SNATed and DNATed
traffic.  It should be fine as is.

Regards,
Dumitru
diff mbox series

Patch

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index fdb6ab08b..d1776d684 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -74,6 +74,7 @@  struct ovn_extend_table;
     OVNACT(CT_LB_MARK,        ovnact_ct_lb)           \
     OVNACT(SELECT,            ovnact_select)          \
     OVNACT(CT_CLEAR,          ovnact_null)            \
+    OVNACT(CT_COMMIT_NAT,     ovnact_ct_nat)          \
     OVNACT(CLONE,             ovnact_nest)            \
     OVNACT(ARP,               ovnact_nest)            \
     OVNACT(ICMP4,             ovnact_nest)            \
@@ -277,6 +278,8 @@  struct ovnact_ct_nat {
        uint16_t port_hi;
     } port_range;
 
+    bool commit;                /* Explicit commit action. */
+
     uint8_t ltable;             /* Logical table ID of next table. */
 };
 
diff --git a/lib/actions.c b/lib/actions.c
index b59f364bf..fb688eeb1 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -920,6 +920,7 @@  parse_ct_nat(struct action_context *ctx, const char *name,
         return;
     }
     cn->ltable = ctx->pp->cur_ltable + 1;
+    cn->commit = false;
 
     if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
         if (ctx->lexer->token.type != LEX_T_INTEGER
@@ -929,9 +930,11 @@  parse_ct_nat(struct action_context *ctx, const char *name,
             return;
         }
         if (ctx->lexer->token.format == LEX_F_IPV4) {
+            cn->commit = true;
             cn->family = AF_INET;
             cn->ipv4 = ctx->lexer->token.value.ipv4;
         } else if (ctx->lexer->token.format == LEX_F_IPV6) {
+            cn->commit = true;
             cn->family = AF_INET6;
             cn->ipv6 = ctx->lexer->token.value.ipv6;
         }
@@ -1004,6 +1007,24 @@  parse_CT_SNAT_IN_CZONE(struct action_context *ctx)
                  ovnact_put_CT_SNAT_IN_CZONE(ctx->ovnacts));
 }
 
+static void
+parse_CT_COMMIT_NAT(struct action_context *ctx)
+{
+    add_prerequisite(ctx, "ip");
+
+    if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
+        lexer_error(ctx->lexer,
+                    "\"ct_commit_related\" action not allowed in last table.");
+        return;
+    }
+
+    struct ovnact_ct_nat *cn = ovnact_put_CT_COMMIT_NAT(ctx->ovnacts);
+    cn->commit = true;
+    cn->ltable = ctx->pp->cur_ltable + 1;
+    cn->family = AF_UNSPEC;
+    cn->port_range.exists = false;
+}
+
 static void
 format_ct_nat(const struct ovnact_ct_nat *cn, const char *name, struct ds *s)
 {
@@ -1053,6 +1074,12 @@  format_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn, struct ds *s)
     format_ct_nat(cn, "ct_snat_in_czone", s);
 }
 
+static void
+format_CT_COMMIT_NAT(const struct ovnact_ct_nat *cn OVS_UNUSED, struct ds *s)
+{
+    ds_put_cstr(s, "ct_commit_nat;");
+}
+
 static void
 encode_ct_nat(const struct ovnact_ct_nat *cn,
               const struct ovnact_encode_params *ep,
@@ -1104,7 +1131,7 @@  encode_ct_nat(const struct ovnact_ct_nat *cn,
 
     ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
     ct = ofpacts->header;
-    if (cn->family == AF_INET || cn->family == AF_INET6) {
+    if (cn->commit) {
         ct->flags |= NX_CT_F_COMMIT;
     }
     ofpact_finish(ofpacts, &ct->ofpact);
@@ -1143,6 +1170,17 @@  encode_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn,
     encode_ct_nat(cn, ep, true, ep->common_nat_ct_zone, ofpacts);
 }
 
+static void
+encode_CT_COMMIT_NAT(const struct ovnact_ct_nat *cn,
+                         const struct ovnact_encode_params *ep,
+                         struct ofpbuf *ofpacts)
+{
+    enum mf_field_id zone = ep->is_switch
+                            ? MFF_LOG_CT_ZONE
+                            : MFF_LOG_DNAT_ZONE;
+    encode_ct_nat(cn, ep, false, zone, ofpacts);
+}
+
 static void
 ovnact_ct_nat_free(struct ovnact_ct_nat *ct_nat OVS_UNUSED)
 {
@@ -5143,6 +5181,8 @@  parse_action(struct action_context *ctx)
         parse_ct_lb_action(ctx, true);
     } else if (lexer_match_id(ctx->lexer, "ct_clear")) {
         ovnact_put_CT_CLEAR(ctx->ovnacts);
+    } else if (lexer_match_id(ctx->lexer, "ct_commit_nat")) {
+        parse_CT_COMMIT_NAT(ctx);
     } else if (lexer_match_id(ctx->lexer, "clone")) {
         parse_CLONE(ctx);
     } else if (lexer_match_id(ctx->lexer, "arp")) {
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 4fe466134..0f093349e 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -1469,6 +1469,18 @@ 
           Clears connection tracking state.
         </dd>
 
+        <dt><code>ct_commit_nat;</code></dt>
+        <dd>
+          <p>
+            Applies NAT and commits the connection to the CT. Automatically
+            moves on to the next table, as if followed by
+            <code>next</code>.
+            This is very useful for connections that are in related state for
+            already existing connections and allows the NAT to be applied
+            to them as well.
+          </p>
+        </dd>
+
         <dt><code>clone { <var>action</var>; </code>...<code> };</code></dt>
         <dd>
           Makes a copy of the packet being processed and executes each
diff --git a/tests/ovn.at b/tests/ovn.at
index dde0f582b..66f5cb137 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1418,6 +1418,11 @@  ct_snat_in_czone(192.168.1.2, 1000-100);
 ct_clear;
     encodes as ct_clear
 
+# ct_commit_nat
+ct_commit_nat;
+    encodes as ct(commit,table=19,zone=NXM_NX_REG13[0..15],nat)
+    has prereqs ip
+
 # clone
 clone { ip4.dst = 255.255.255.255; output; }; next;
     encodes as clone(set_field:255.255.255.255->ip_dst,resubmit(,64)),resubmit(,19)
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 8e3a8d9ca..5d76ddb53 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -2438,6 +2438,35 @@  execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
      * flow, not ct_flow. */
 }
 
+static void
+execute_ct_commit_nat(const struct ovnact_ct_nat *ct_nat,
+                      const struct ovntrace_datapath *dp, struct flow *uflow,
+                      enum ovnact_pipeline pipeline, struct ovs_list *super)
+{
+    struct flow ct_flow = *uflow;
+    struct ds s = DS_EMPTY_INITIALIZER;
+
+    ds_put_cstr(&s, "ct_commit_nat /* assuming no"
+                    " un-nat entry, so no change */");
+
+    /* ct(nat) implies ct(). */
+    if (!(ct_flow.ct_state & CS_TRACKED)) {
+        ct_flow.ct_state |= next_ct_state(&s);
+    }
+
+    struct ovntrace_node *node = ovntrace_node_append(
+        super, OVNTRACE_NODE_TRANSFORMATION, "%s", ds_cstr(&s));
+    ds_destroy(&s);
+
+    /* Trace the actions in the next table. */
+    trace__(dp, &ct_flow, ct_nat->ltable, pipeline, &node->subs);
+
+    /* Upon return, we will trace the actions following the ct action in the
+     * original table.  The pipeline forked, so we're using the original
+     * flow, not ct_flow. */
+}
+
+
 static void
 execute_ct_lb(const struct ovnact_ct_lb *ct_lb,
               const struct ovntrace_datapath *dp, struct flow *uflow,
@@ -3095,6 +3124,11 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
             flow_clear_conntrack(uflow);
             break;
 
+        case OVNACT_CT_COMMIT_NAT:
+            execute_ct_commit_nat(ovnact_get_CT_COMMIT_NAT(a), dp, uflow,
+                                  pipeline, super);
+            break;
+
         case OVNACT_CLONE:
             execute_clone(ovnact_get_CLONE(a), dp, uflow, table_id, pipeline,
                           super);