One OpenFlow flow's actions are translated into one datapath flow. "resubmit" is followed during that translation, so every action it reaches is appended to the same action list. A pipeline that fans a packet out to many destinations can therefore build an action list that the datapath refuses, because a Netlink attribute carries a 16-bit length.
Recirculation is the only thing that ends one action list and begins another, but a controller has had no way to ask for it. Open vSwitch recirculates on its own behalf for MPLS, for "ct(table=N)", and for a paused "controller" action, and "debug_recirc" exposes it directly, but that action is intentionally undocumented, subject to change, and rejected unless ovs-vswitchd runs with --enable-dummy. Add "recirc", a documented extension action that ends the datapath flow being built and resumes the actions that follow it in a new one. Registers, metadata, the stack, and the remaining actions cross the boundary, so a pipeline may be divided across several datapath flows without changing what it computes. Translation reuses the existing freeze path, which already advances past the action before freezing so that the continuation makes progress. Assisted-by: Claude Opus 5.0, Cursor Signed-off-by: Han Zhou <[email protected]> --- Documentation/ref/ovs-actions.7.rst | 31 +++++++++++++++++++ NEWS | 6 ++++ include/openvswitch/ofp-actions.h | 1 + lib/ofp-actions.c | 47 +++++++++++++++++++++++++++++ ofproto/ofproto-dpif-xlate.c | 4 +++ tests/ofp-actions.at | 6 ++++ tests/ofproto-dpif.at | 33 ++++++++++++++++++++ 7 files changed, 128 insertions(+) diff --git a/Documentation/ref/ovs-actions.7.rst b/Documentation/ref/ovs-actions.7.rst index 7bf21f6bfb42..a10630f0aae7 100644 --- a/Documentation/ref/ovs-actions.7.rst +++ b/Documentation/ref/ovs-actions.7.rst @@ -2022,6 +2022,37 @@ overall pipeline. Actions in the action set are still executed (specify ``clear_actions`` before ``exit`` to discard them). +The ``recirc`` action +--------------------- +.. + name: RECIRC + +**Syntax**: + | ``recirc`` + +Ends the datapath flow that Open vSwitch is currently building and resumes the +actions that follow this one in a new one, by recirculating the packet. +Registers, metadata, the stack, and the remaining actions are carried across +the boundary, so the actions after ``recirc`` observe the state that the +actions before it left behind. + +``resubmit`` does not do this. Open vSwitch follows a ``resubmit`` while it is +still building the same datapath flow, so every action it reaches is appended +to a single action list. Recirculation is the only way to begin a new one. A +pipeline too large for one action list, which a datapath limits to the 16-bit +length of a Netlink attribute, can therefore be divided across several flows +with ``recirc``. + +Recirculation has a cost: the packet is looked up in the datapath a second +time, and a datapath limits how many times one packet may be recirculated. Use +this action only where a single datapath flow will not do. If no actions +follow ``recirc``, the packet is recirculated only to find nothing left to +execute, and is dropped. + +**Conformance** + Open vSwitch 4.1 introduced ``recirc`` as an OpenFlow extension action. + + The ``multipath`` action ------------------------ .. diff --git a/NEWS b/NEWS index 33ab0585bd47..a2624c2142f7 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,11 @@ Post-v4.0.0 -------------------- + - OpenFlow: + * New "recirc" action, which ends the datapath flow being built and + resumes the actions that follow it in a new one. Unlike "resubmit", + which is followed while building a single datapath flow, this allows a + pipeline to be divided across several flows and so stay within the + datapath limit on the size of one action list. - ovs-vswitchd: * Mirror filters now accept masked fields, e.g. "ip,nw_src=10.0.0.0/24" in the "filter" column of the Mirror table. diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h index 56dc2c1476c5..18fa0cdfaba2 100644 --- a/include/openvswitch/ofp-actions.h +++ b/include/openvswitch/ofp-actions.h @@ -126,6 +126,7 @@ struct vl_mff_map; OFPACT(CLONE, ofpact_nest, actions, "clone") \ OFPACT(CHECK_PKT_LARGER, ofpact_check_pkt_larger, ofpact, \ "check_pkt_larger") \ + OFPACT(RECIRC, ofpact_null, ofpact, "recirc") \ \ /* Debugging actions. \ * \ diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index 16de931bbc38..17cafb5251ba 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -366,6 +366,9 @@ enum ofp_raw_action_type { /* NX1.0+(50): struct nx_action_delete_field. VLMFF */ NXAST_RAW_DELETE_FIELD, + /* NX1.0+(52): void. */ + NXAST_RAW_RECIRC, + /* ## ------------------ ## */ /* ## Debugging actions. ## */ /* ## ------------------ ## */ @@ -495,6 +498,7 @@ ofpact_next_flattened(const struct ofpact *ofpact) case OFPACT_SAMPLE: case OFPACT_UNROLL_XLATE: case OFPACT_CT_CLEAR: + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_SLOW: case OFPACT_METER: @@ -5997,6 +6001,46 @@ check_EXIT(const struct ofpact_null *a OVS_UNUSED, return 0; } +/* Recirc action. + * + * Ends the datapath flow being built and resumes the actions that follow in a + * new one. */ + +static enum ofperr +decode_NXAST_RAW_RECIRC(struct ofpbuf *out) +{ + ofpact_put_RECIRC(out); + return 0; +} + +static void +encode_RECIRC(const struct ofpact_null *null OVS_UNUSED, + enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out) +{ + put_NXAST_RECIRC(out); +} + +static char * OVS_WARN_UNUSED_RESULT +parse_RECIRC(char *arg OVS_UNUSED, const struct ofpact_parse_params *pp) +{ + ofpact_put_RECIRC(pp->ofpacts); + return NULL; +} + +static void +format_RECIRC(const struct ofpact_null *a OVS_UNUSED, + const struct ofpact_format_params *fp) +{ + ds_put_format(fp->s, "%srecirc%s", colors.special, colors.end); +} + +static enum ofperr +check_RECIRC(const struct ofpact_null *a OVS_UNUSED, + const struct ofpact_check_params *cp OVS_UNUSED) +{ + return 0; +} + /* Unroll xlate action. */ static void @@ -8239,6 +8283,7 @@ action_set_classify(const struct ofpact *a) case OFPACT_STACK_PUSH: case OFPACT_WRITE_ACTIONS: case OFPACT_WRITE_METADATA: + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_SLOW: case OFPACT_CHECK_PKT_LARGER: @@ -8437,6 +8482,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type, case OFPACT_EXIT: case OFPACT_UNROLL_XLATE: case OFPACT_SAMPLE: + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_SLOW: case OFPACT_CT: @@ -9349,6 +9395,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port) case OFPACT_GOTO_TABLE: case OFPACT_METER: case OFPACT_GROUP: + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_SLOW: case OFPACT_CT: diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 4e7d6fb401f7..2c51a45eab2d 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -6541,6 +6541,7 @@ freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end, case OFPACT_CLONE: case OFPACT_ENCAP: case OFPACT_DECAP: + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_SLOW: case OFPACT_CT: @@ -7325,6 +7326,7 @@ recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx) case OFPACT_CT: case OFPACT_CT_CLEAR: case OFPACT_NAT: + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_SLOW: case OFPACT_METER: @@ -7457,6 +7459,7 @@ reset_mirror_ctx(struct xlate_ctx *ctx, const struct flow *flow, case OFPACT_METER: case OFPACT_SAMPLE: case OFPACT_CLONE: + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: case OFPACT_DEBUG_SLOW: case OFPACT_CT: @@ -7919,6 +7922,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, ctx->ct_nat_action = ofpact_get_NAT(a); break; + case OFPACT_RECIRC: case OFPACT_DEBUG_RECIRC: ctx_trigger_freeze(ctx); a = ofpact_next(a); diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at index 8a0504b3cb3c..093fcd86b8d2 100644 --- a/tests/ofp-actions.at +++ b/tests/ofp-actions.at @@ -139,6 +139,9 @@ ffff 0020 00002320 0029 3039 00005BA0 00008707 0000B26E DDD50200 00000000 # actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=NXM_OF_IN_PORT[],obs_point_id=NXM_NX_CT_LABEL[32..63],sampling_port=0) ffff 0028 00002320 0033 3039 00005ba0 00000002 000f0000 0001d810 081f0000 0000 000000000000 +# actions=recirc +ffff 0010 00002320 0034 000000000000 + # bad OpenFlow10 actions: OFPBAC_BAD_LEN & ofp_actions|WARN|OpenFlow action OFPAT_OUTPUT length 240 exceeds action buffer length 8 & ofp_actions|WARN|bad action at offset 0 (OFPBAC_BAD_LEN): @@ -495,6 +498,9 @@ ffff 0018 00002320 001d 3039 00005BA0 00008707 0000B26E # actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=NXM_OF_IN_PORT[],obs_point_id=NXM_NX_CT_LABEL[32..63],sampling_port=0) ffff 0028 00002320 0033 3039 00005ba0 00000002 000f0000 0001d810 081f0000 0000 000000000000 +# actions=recirc +ffff 0010 00002320 0034 000000000000 + # bad OpenFlow11 actions: OFPBAC_BAD_OUT_PORT & ofp_actions|WARN|bad action at offset 0 (OFPBAC_BAD_OUT_PORT): & 00000000 00 00 00 10 ff ff ff ff-00 00 00 00 00 00 00 00 diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index cd8991814c11..39e09c0e8a54 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -6654,6 +6654,39 @@ AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: 2 OVS_VSWITCHD_STOP AT_CLEANUP + +# This test verifies that "recirc" ends the current datapath flow and +# resumes the actions that follow it in a new one. "resubmit" is +# followed during translation instead, so everything it reaches lands in +# a single datapath flow; the test covers both in one pipeline. +AT_SETUP([ofproto-dpif - recirc action]) +OVS_VSWITCHD_START +add_of_ports br0 1 2 3 4 + +AT_DATA([flows.txt], [dnl +table=0 in_port=1 actions=2,load:0xabcd->NXM_NX_REG0[[]],recirc,resubmit(,1) +table=1 reg0=0xabcd actions=3,resubmit(,2) +table=2 actions=4 +]) +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +flow="in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)" + +# Only the actions before "recirc" are in this datapath flow. +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow" -generate], [0], [stdout]) +AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: 2,recirc(0x1) +]) + +# The continuation is a separate datapath flow. reg0 survives the +# boundary, so table 1 matches, and its "resubmit" is followed during +# this translation rather than starting a third flow. +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow,recirc_id(1)" -generate], [0], [stdout]) +AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: 3,4 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + # Checks for regression against a bug in which OVS dropped packets # with in_port=CONTROLLER when they were recirculated (because # CONTROLLER isn't a real port and could not be looked up). -- 2.38.1 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
