diff mbox

[ovs-dev,v10,4/8] ovn: add egress_loopback action

Message ID 1484646309-16980-5-git-send-email-mickeys.dev@gmail.com
State Superseded
Headers show

Commit Message

Mickey Spiegel Jan. 17, 2017, 9:45 a.m. UTC
This patch adds an action that loops a clone of the packet back to the
beginning of the ingress pipeline with logical inport equal to the value
of the current logical outport.  The following actions are executed on
the clone:

    clears the connection tracking state
    in_port = 0
    inport = outport
    outport = 0
    flags = 0
    reg0 ... reg9 = 0
    nested actions from inside "{ ... }"
        for example "reg9[1] = 1" to indicate that egress loopback has
        occurred
    executes the ingress pipeline as a subroutine

This action is expected to be executed in the egress pipeline.  No
changes are made to the logical datapath or to the connection tracking
zones, which will continue to be correct when carrying out loopback
from the egress pipeline to the ingress pipeline.

This capability is needed in order to implement some of the east/west
distributed NAT flows.

Signed-off-by: Mickey Spiegel <mickeys.dev@gmail.com>
---
 include/ovn/actions.h     |  5 +++-
 ovn/controller/lflow.c    |  1 +
 ovn/lib/actions.c         | 71 +++++++++++++++++++++++++++++++++++++++++++++--
 ovn/ovn-sb.xml            | 35 +++++++++++++++++++++++
 ovn/utilities/ovn-trace.c | 49 ++++++++++++++++++++++++++++++++
 tests/ovn.at              |  4 +++
 tests/test-ovn.c          |  1 +
 7 files changed, 163 insertions(+), 3 deletions(-)

Comments

Ben Pfaff Jan. 20, 2017, 5:18 p.m. UTC | #1
On Tue, Jan 17, 2017 at 01:45:05AM -0800, Mickey Spiegel wrote:
> This patch adds an action that loops a clone of the packet back to the
> beginning of the ingress pipeline with logical inport equal to the value
> of the current logical outport.  The following actions are executed on
> the clone:
> 
>     clears the connection tracking state
>     in_port = 0
>     inport = outport
>     outport = 0
>     flags = 0
>     reg0 ... reg9 = 0
>     nested actions from inside "{ ... }"
>         for example "reg9[1] = 1" to indicate that egress loopback has
>         occurred
>     executes the ingress pipeline as a subroutine

I think that we can get something equivalent by defining finer-grained
primitives, which is usually my own preference.  I posted a series of
patches to enable that:
        https://mail.openvswitch.org/pipermail/ovs-dev/2017-January/327820.html

I believe that, with these patches, egress loopback explained above can
be implemented with:

    clone { inport = outport; outport = ""; flags.loopback = 0; 
	    reg0 = 0; reg1 = 0; ... regN = 0;
	    next(pipeline=ingress, table=0); }

What do you think?

Thanks,

Ben.
diff mbox

Patch

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 0bf6145..5f9d203 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -68,7 +68,8 @@  struct simap;
     OVNACT(PUT_ND,        ovnact_put_mac_bind)      \
     OVNACT(PUT_DHCPV4_OPTS, ovnact_put_dhcp_opts)   \
     OVNACT(PUT_DHCPV6_OPTS, ovnact_put_dhcp_opts)   \
-    OVNACT(SET_QUEUE,       ovnact_set_queue)
+    OVNACT(SET_QUEUE,       ovnact_set_queue)       \
+    OVNACT(EGRESS_LOOPBACK, ovnact_nest)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -444,6 +445,8 @@  struct ovnact_encode_params {
     uint8_t output_ptable;      /* OpenFlow table for 'output' to resubmit. */
     uint8_t mac_bind_ptable;    /* OpenFlow table for 'get_arp'/'get_nd' to
                                    resubmit. */
+    uint8_t ingress_ptable;     /* OpenFlow table for 'egress_loopback' to
+                                   resubmit. */
 };
 
 void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 3d7633e..c41368f 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -226,6 +226,7 @@  consider_logical_flow(const struct lport_index *lports,
         .first_ptable = first_ptable,
         .output_ptable = output_ptable,
         .mac_bind_ptable = OFTABLE_MAC_BINDING,
+        .ingress_ptable = OFTABLE_LOG_INGRESS_PIPELINE,
     };
     ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts);
     ovnacts_free(ovnacts.data, ovnacts.size);
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 686ecc5..dda675b 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -169,6 +169,21 @@  put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
     bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits);
     bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
 }
+
+static void
+put_move(enum mf_field_id src, int src_ofs,
+         enum mf_field_id dst, int dst_ofs,
+         int n_bits,
+         struct ofpbuf *ofpacts)
+{
+    struct ofpact_reg_move *move = ofpact_put_REG_MOVE(ofpacts);
+    move->src.field = mf_from_id(src);
+    move->src.ofs = src_ofs;
+    move->src.n_bits = n_bits;
+    move->dst.field = mf_from_id(dst);
+    move->dst.ofs = dst_ofs;
+    move->dst.n_bits = n_bits;
+}
 
 /* Context maintained during ovnacts_parse(). */
 struct action_context {
@@ -1021,7 +1036,10 @@  free_CT_LB(struct ovnact_ct_lb *ct_lb)
 }
 
 /* Implements the "arp" and "nd_na" actions, which execute nested actions on a
- * packet derived from the one being processed. */
+ * packet derived from the one being processed.  Also implements the
+ * "egress_loopback" action, which executes nested actions after clearing
+ * registers and connection state, then loops the packet back to the
+ * beginning of the ingress pipeline. */
 static void
 parse_nested_action(struct action_context *ctx, enum ovnact_type type,
                     const char *prereq)
@@ -1055,7 +1073,9 @@  parse_nested_action(struct action_context *ctx, enum ovnact_type type,
         return;
     }
 
-    add_prerequisite(ctx, prereq);
+    if (prereq) {
+        add_prerequisite(ctx, prereq);
+    }
 
     struct ovnact_nest *on = ovnact_put(ctx->ovnacts, type, sizeof *on);
     on->nested_len = nested.size;
@@ -1075,6 +1095,12 @@  parse_ND_NA(struct action_context *ctx)
 }
 
 static void
+parse_EGRESS_LOOPBACK(struct action_context *ctx)
+{
+    parse_nested_action(ctx, OVNACT_EGRESS_LOOPBACK, NULL);
+}
+
+static void
 format_nested_action(const struct ovnact_nest *on, const char *name,
                      struct ds *s)
 {
@@ -1096,6 +1122,12 @@  format_ND_NA(const struct ovnact_nest *nest, struct ds *s)
 }
 
 static void
+format_EGRESS_LOOPBACK(const struct ovnact_nest *nest, struct ds *s)
+{
+    format_nested_action(nest, "egress_loopback", s);
+}
+
+static void
 encode_nested_actions(const struct ovnact_nest *on,
                       const struct ovnact_encode_params *ep,
                       enum action_opcode opcode,
@@ -1136,6 +1168,33 @@  encode_ND_NA(const struct ovnact_nest *on,
 }
 
 static void
+encode_EGRESS_LOOPBACK(const struct ovnact_nest *on,
+                       const struct ovnact_encode_params *ep,
+                       struct ofpbuf *ofpacts)
+{
+    /* Add a "clone" action with the following actions inside:
+     * clear conntrack state, clear in_port, move logical outport to
+     * logical inport, clear logical outport, clear flags, clear logical
+     * registers, add actions nested inside "{...}", and resubmit to the
+     * beginning of the ingress pipeline. */
+    size_t clone_ofs = ofpacts->size;
+    struct ofpact_nest *clone = ofpact_put_CLONE(ofpacts);
+    ofpact_put_CT_CLEAR(ofpacts);
+    put_load(0, MFF_IN_PORT, 0, 16, ofpacts);
+    put_move(MFF_LOG_OUTPORT, 0, MFF_LOG_INPORT, 0, 32, ofpacts);
+    put_load(0, MFF_LOG_OUTPORT, 0, 32, ofpacts);
+    put_load(0, MFF_LOG_FLAGS, 0, 32, ofpacts);
+    for (int i = 0; i < MFF_N_LOG_REGS; i++) {
+        put_load(0, MFF_LOG_REG0 + i, 0, 32, ofpacts);
+    }
+    ovnacts_encode(on->nested, on->nested_len, ep, ofpacts);
+    emit_resubmit(ofpacts, ep->ingress_ptable);
+    clone = ofpbuf_at_assert(ofpacts, clone_ofs, sizeof *clone);
+    ofpacts->header = clone;
+    ofpact_finish_CLONE(ofpacts, &clone);
+}
+
+static void
 free_nested_actions(struct ovnact_nest *on)
 {
     ovnacts_free(on->nested, on->nested_len);
@@ -1153,6 +1212,12 @@  free_ND_NA(struct ovnact_nest *nest)
 {
     free_nested_actions(nest);
 }
+
+static void
+free_EGRESS_LOOPBACK(struct ovnact_nest *nest)
+{
+    free_nested_actions(nest);
+}
 
 static void
 parse_get_mac_bind(struct action_context *ctx, int width,
@@ -1733,6 +1798,8 @@  parse_action(struct action_context *ctx)
         parse_put_mac_bind(ctx, 128, ovnact_put_PUT_ND(ctx->ovnacts));
     } else if (lexer_match_id(ctx->lexer, "set_queue")) {
         parse_SET_QUEUE(ctx);
+    } else if (lexer_match_id(ctx->lexer, "egress_loopback")) {
+        parse_EGRESS_LOOPBACK(ctx);
     } else {
         lexer_syntax_error(ctx->lexer, "expecting action");
     }
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 5704f41..a7c29c3 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1424,6 +1424,41 @@ 
             packet in that connection.
           </p>
         </dd>
+
+        <dt>
+          <code>egress_loopback { <var>action</var>; </code>...<code> };</code>
+        </dt>
+        <dd>
+          <p>
+            Loops a clone of the packet back to the beginning of the ingress
+            pipeline with logical inport equal to the value of the current
+            logical outport.  The following actions are executed on the clone:
+          </p>
+
+          <ul>
+            <li>clears the connection tracking state</li>
+            <li><code>in_port = 0</code></li>
+            <li><code>inport = outport</code></li>
+            <li><code>outport = 0</code></li>
+            <li><code>flags = 0</code></li>
+            <li><code>reg0 ... reg9 = 0</code></li>
+            <li>nested actions, for example <code>reg9[1] = 1</code> to
+                indicate that egress loopback has occurred</li>
+            <li>executes the ingress pipeline as a subroutine</li>
+          </ul>
+
+          <p>
+            This action is expected to be executed in the egress pipeline.
+            No changes are made to the logical datapath or to the connection
+            tracking zones, which will continue to be correct when carrying
+            out loopback from the egress pipeline to the ingress pipeline.
+          </p>
+
+          <p>
+            Actions following the <var>egress_loopback</var> action, if any,
+            apply to the original, unmodified packet.
+          </p>
+        </dd>
       </dl>
 
       <p>
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 9487b1f..73adc42 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -1353,6 +1353,50 @@  execute_put_dhcp_opts(const struct ovnact_put_dhcp_opts *pdo,
 }
 
 static void
+execute_egress_loopback(const struct ovnact_nest *on,
+                        const struct ovntrace_datapath *dp,
+                        const struct flow *uflow, uint8_t table_id,
+                        enum ovntrace_pipeline pipeline,
+                        struct ovs_list *super)
+{
+    uint16_t key = uflow->regs[MFF_LOG_OUTPORT - MFF_REG0];
+    if (!key) {
+        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
+                             "*** egress loopback on null logical port");
+        return;
+    }
+
+    const struct ovntrace_port *port = ovntrace_port_find_by_key(dp, key);
+    const char *out_name = (port ? port->name : "(unnamed)");
+    if (!port) {
+        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
+                             "*** unknown outport %"PRIu16, key);
+    }
+
+    struct ovntrace_node *node = ovntrace_node_append(
+        super, OVNTRACE_NODE_OUTPUT,
+        "/* egress_loopback on \"%s\", type \"%s\" */",
+        out_name, port ? port->type : "");
+    if (port) {
+        struct flow new_uflow = *uflow;
+        for (int i = 0; i < FLOW_N_REGS; i++) {
+            if (i != MFF_LOG_INPORT - MFF_REG0) {
+                new_uflow.regs[i] = 0;
+            }
+        }
+        new_uflow.regs[MFF_LOG_INPORT - MFF_REG0] = port->tunnel_key;
+        trace_actions(on->nested, on->nested_len, dp, &new_uflow,
+                      table_id, pipeline, &node->subs);
+
+        node = ovntrace_node_append(super, OVNTRACE_NODE_PIPELINE,
+                                    "ingress(dp=\"%s\", inport=\"%s\")",
+                                    dp->name, port->name);
+        trace__(dp, &new_uflow, 0, P_INGRESS, &node->subs);
+    }
+    return;
+}
+
+static void
 trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
               const struct ovntrace_datapath *dp, struct flow *uflow,
               uint8_t table_id, enum ovntrace_pipeline pipeline,
@@ -1460,6 +1504,11 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
              * though, it would be easy enough to track the queue information
              * by adjusting uflow->skb_priority. */
             break;
+
+        case OVNACT_EGRESS_LOOPBACK:
+            execute_egress_loopback(ovnact_get_EGRESS_LOOPBACK(a), dp, uflow,
+                                    table_id, pipeline, super);
+            break;
         }
 
     }
diff --git a/tests/ovn.at b/tests/ovn.at
index 1792956..1c3183f 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -994,6 +994,10 @@  set_queue(61440);
 set_queue(65535);
     Queue ID 65535 for set_queue is not in valid range 0 to 61440.
 
+# egress_loopback
+egress_loopback { reg9[1] = 1; };
+    encodes as clone(ct_clear,set_field:0->in_port,move:NXM_NX_REG15[]->NXM_NX_REG14[],set_field:0->reg15,set_field:0->reg10,set_field:0->reg0,set_field:0->reg1,set_field:0->reg2,set_field:0->reg3,set_field:0->reg4,set_field:0->reg5,set_field:0->reg6,set_field:0->reg7,set_field:0->reg8,set_field:0->reg9,set_field:0x2/0x2->xreg4,resubmit(,16))
+
 # Contradictionary prerequisites (allowed but not useful):
 ip4.src = ip6.src[0..31];
     encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[]
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index e64f12f..6a0d55f 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1249,6 +1249,7 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
                 .first_ptable = 16,
                 .output_ptable = 64,
                 .mac_bind_ptable = 65,
+                .ingress_ptable = 16,
             };
             struct ofpbuf ofpacts;
             ofpbuf_init(&ofpacts, 0);