@@ -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,
@@ -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);
@@ -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");
}
@@ -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>
@@ -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;
}
}
@@ -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[]
@@ -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);
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(-)