On Thu, Dec 1, 2016 at 6:25 PM, Gao Zhenyu <sysugaozhe...@gmail.com> wrote: > Thanks for working on it! > > Can we have nested clone? > > actions=clone(push_vlan(1), resubmit(1,2), clone(mod_dl_src:<mac1>, > output:5) ) >
I tested it for a couple of simple cases, it seems ok. But it might complicate things a lot. > BTW, I believe the port 3 will get a 104byte size packet with this action, > right? > actions= clone(truncate(100), push_vlan, output:3) > Yes. Regards, William > > Thanks > Zhenyu Gao > > 2016-12-01 5:35 GMT+08:00 William Tu <u9012...@gmail.com>: >> >> This patch adds OpenFlow clone action with syntax as below: >> "clone([action][,action...])". The clone() action makes a copy of the >> current packet and executes the list of actions against the packet, >> without affecting the packet after the "clone(...)" action. In other >> word, the packet before the clone() and after the clone() is the same, >> no matter what actions executed inside the clone(). >> >> The patch reuses the dapatah SAMPLE action, with probability of 100%. >> The kernel datapath 'sample()' clones the skb and its flow key under this >> circumstance, and actions specified in the clone() are executed against >> the cloned skb. Note that there is no performance overhead of clone, >> since >> if eventually the packet is output, it will also clone the packet. Here >> we >> simply move the copy at the beginning. >> >> The complexity of adding clone might outweight its use cases. I'm looking >> for comments as well as listing some cases below: >> Use case 1: >> Set different fields and output to different ports without unset >> actions= >> clone(mod_dl_src:<mac1>, output:1), clone(mod_dl_dst:<mac2>, output:2), >> output:3 >> Since each clone() has independent packet, output:1 has only dl_src >> modified, >> output:2 has only dl_dst modified, output:3 has original packet. >> >> Similar to case1 >> actions= >> push_vlan(...), output:2, pop_vlan, push_vlan(...), output:3 >> can be changed to >> actions= >> clone(push_vlan(...), output:2),clone(push_vlan(...), output:3) >> without having to add pop_vlan. >> >> case 2: resubmit to another table without worrying packet being modified >> actions=clone(resubmit(1,2)), ... >> >> case 3: truncate in the clone action >> Currently OVS can truncate packet by 'output(port=1,max_len=100)', which >> ties truncate and output together. However, sometimes the layer decides >> to truncate is separate from the layer to output. One proposal is to >> introduce actions s.t like truncate_set() and truncate_unset(), where >> only the action in between sees truncated packet. Another approach is >> to use clone() as below: >> actions= >> clone(truncate(100), push_vlan, resubmit, ...) >> where we don't need to worry about missing the truncate_unset() because >> truncated packet is not visible outside the clone(). >> >> We definitely should put some limit on the action types available inside >> clone(). For this patch, there is no restriction. >> >> Signed-off-by: William Tu <u9012...@gmail.com> >> --- >> v1->v2 >> - rebase and change NX1.0+(41) to 42 >> --- >> include/openvswitch/ofp-actions.h | 23 +++++++++ >> lib/ofp-actions.c | 101 >> +++++++++++++++++++++++++++++++++++++- >> ofproto/ofproto-dpif-xlate.c | 35 +++++++++++++ >> tests/ofproto-dpif.at | 19 +++++++ >> tests/system-traffic.at | 29 +++++++++++ >> 5 files changed, 206 insertions(+), 1 deletion(-) >> >> diff --git a/include/openvswitch/ofp-actions.h >> b/include/openvswitch/ofp-actions.h >> index 2999261..be4a991 100644 >> --- a/include/openvswitch/ofp-actions.h >> +++ b/include/openvswitch/ofp-actions.h >> @@ -109,6 +109,7 @@ >> OFPACT(CT, ofpact_conntrack, ofpact, "ct") \ >> OFPACT(NAT, ofpact_nat, ofpact, "nat") \ >> OFPACT(OUTPUT_TRUNC, ofpact_output_trunc,ofpact, "output_trunc") \ >> + OFPACT(CLONE, ofpact_clone, ofpact, "clone") \ >> \ >> /* Debugging actions. \ >> * \ >> @@ -868,6 +869,28 @@ struct ofpact_sample { >> enum nx_action_sample_direction direction; >> }; >> >> +/* OFPACT_CLONE. >> + * >> + * Used for cloning the packet and execute actions >> + * in the clone envelope. */ >> +struct ofpact_clone { >> + OFPACT_PADDED_MEMBERS( >> + struct ofpact ofpact; >> + ); >> + struct ofpact actions[0]; >> +}; >> + >> +BUILD_ASSERT_DECL(offsetof(struct ofpact_clone, actions) >> + % OFPACT_ALIGNTO == 0); >> +BUILD_ASSERT_DECL(offsetof(struct ofpact_clone, actions) >> + == sizeof(struct ofpact_clone)); >> + >> +static inline size_t >> +ofpact_clone_get_action_len(const struct ofpact_clone *oc) >> +{ >> + return oc->ofpact.len - offsetof(struct ofpact_clone, actions); >> +} >> + >> /* OFPACT_DEC_TTL. >> * >> * Used for OFPAT11_DEC_NW_TTL, NXAST_DEC_TTL and NXAST_DEC_TTL_CNT_IDS. >> */ >> diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c >> index 7507558..227ecb2 100644 >> --- a/lib/ofp-actions.c >> +++ b/lib/ofp-actions.c >> @@ -306,6 +306,9 @@ enum ofp_raw_action_type { >> /* NX1.0+(39): struct nx_action_output_trunc. */ >> NXAST_RAW_OUTPUT_TRUNC, >> >> + /* NX1.0+(42): struct nx_action_clone, ... */ >> + NXAST_RAW_CLONE, >> + >> /* ## ------------------ ## */ >> /* ## Debugging actions. ## */ >> /* ## ------------------ ## */ >> @@ -386,6 +389,7 @@ ofpact_next_flattened(const struct ofpact *ofpact) >> switch (ofpact->type) { >> case OFPACT_OUTPUT: >> case OFPACT_GROUP: >> + case OFPACT_CLONE: >> case OFPACT_CONTROLLER: >> case OFPACT_ENQUEUE: >> case OFPACT_OUTPUT_REG: >> @@ -4801,6 +4805,90 @@ format_UNROLL_XLATE(const struct >> ofpact_unroll_xlate *a, struct ds *s) >> colors.paren, colors.end); >> } >> >> + >> +struct nx_action_clone { >> + ovs_be16 type; /* OFPAT_VENDOR. */ >> + ovs_be16 len; /* Length is at least 16. */ >> + ovs_be32 vendor; /* NX_VENDOR_ID. */ >> + ovs_be16 subtype; /* NXAST_CLONE. */ >> + ovs_be16 pad0; >> + ovs_be32 pad1; >> + /* Followed by a sequence of zero or more OpenFlow actions. The >> length >> + * of these is included in 'len'. */ >> +}; >> +OFP_ASSERT(sizeof(struct nx_action_clone) == 16); >> + >> +static enum ofperr >> +decode_NXAST_RAW_CLONE(const struct nx_action_clone *nac, >> + enum ofp_version ofp_version, >> + struct ofpbuf *out) >> +{ >> + int error; >> + struct ofpbuf openflow; >> + const size_t clone_offset = ofpacts_pull(out); >> + struct ofpact_clone *clone = ofpact_put_CLONE(out); >> + >> + /* decode action list */ >> + ofpbuf_pull(out, sizeof(*clone)); >> + openflow = ofpbuf_const_initializer( >> + nac + 1, ntohs(nac->len) - sizeof(*nac)); >> + error = ofpacts_pull_openflow_actions__(&openflow, openflow.size, >> + ofp_version, >> + 1u << >> OVSINST_OFPIT11_APPLY_ACTIONS, >> + out, OFPACT_CLONE); >> + clone = ofpbuf_push_uninit(out, sizeof(*clone)); >> + out->header = &clone->ofpact; >> + ofpact_finish_CLONE(out, &clone); >> + ofpbuf_push_uninit(out, clone_offset); >> + return error; >> +} >> + >> +static void >> +encode_CLONE(const struct ofpact_clone *clone, >> + enum ofp_version ofp_version, struct ofpbuf *out) >> +{ >> + size_t len; >> + const size_t ofs = out->size; >> + struct nx_action_clone *nac; >> + >> + nac = put_NXAST_CLONE(out); >> + len = ofpacts_put_openflow_actions(clone->actions, >> + ofpact_clone_get_action_len(clone), >> + out, ofp_version); >> + len += sizeof(*nac); >> + nac = ofpbuf_at(out, ofs, sizeof(*nac)); >> + nac->len = htons(len); >> +} >> + >> +static char * OVS_WARN_UNUSED_RESULT >> +parse_CLONE(char *arg, struct ofpbuf *ofpacts, >> + enum ofputil_protocol *usable_protocols) >> +{ >> + >> + const size_t ct_offset = ofpacts_pull(ofpacts); >> + struct ofpact_clone *clone = ofpact_put_CLONE(ofpacts); >> + char *error; >> + >> + ofpbuf_pull(ofpacts, sizeof(*clone)); >> + error = ofpacts_parse_copy(arg, ofpacts, usable_protocols, >> + false, OFPACT_CLONE); >> + /* header points to the action list */ >> + ofpacts->header = ofpbuf_push_uninit(ofpacts, sizeof(*clone)); >> + clone = ofpacts->header; >> + >> + ofpact_finish_CLONE(ofpacts, &clone); >> + ofpbuf_push_uninit(ofpacts, ct_offset); >> + return error; >> +} >> + >> +static void >> +format_CLONE(const struct ofpact_clone *a, struct ds *s) >> +{ >> + ds_put_format(s, "%sclone(%s", colors.paren, colors.end); >> + ofpacts_format(a->actions, ofpact_clone_get_action_len(a), s); >> + ds_put_format(s, "%s)%s", colors.paren, colors.end); >> +} >> + >> /* Action structure for NXAST_SAMPLE. >> * >> * Samples matching packets with the given probability and sends them >> @@ -6212,6 +6300,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a) >> case OFPACT_BUNDLE: >> case OFPACT_CLEAR_ACTIONS: >> case OFPACT_CT: >> + case OFPACT_CLONE: >> case OFPACT_NAT: >> case OFPACT_CONTROLLER: >> case OFPACT_DEC_MPLS_TTL: >> @@ -6288,6 +6377,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact >> *a) >> * the specification. Thus the order in which they should be applied >> * in the action set is undefined. */ >> case OFPACT_BUNDLE: >> + case OFPACT_CLONE: >> case OFPACT_CONTROLLER: >> case OFPACT_CT: >> case OFPACT_NAT: >> @@ -6480,6 +6570,7 @@ ovs_instruction_type_from_ofpact_type(enum >> ofpact_type type) >> return OVSINST_OFPIT11_GOTO_TABLE; >> case OFPACT_OUTPUT: >> case OFPACT_GROUP: >> + case OFPACT_CLONE: >> case OFPACT_CONTROLLER: >> case OFPACT_ENQUEUE: >> case OFPACT_OUTPUT_REG: >> @@ -7080,6 +7171,9 @@ ofpact_check__(enum ofputil_protocol >> *usable_protocols, struct ofpact *a, >> case OFPACT_SAMPLE: >> return 0; >> >> + case OFPACT_CLONE: >> + return 0; >> + >> case OFPACT_CT: { >> struct ofpact_conntrack *oc = ofpact_get_CT(a); >> >> @@ -7271,7 +7365,7 @@ ofpacts_verify_nested(const struct ofpact *a, enum >> ofpact_type outer_action) >> >> if (outer_action) { >> ovs_assert(outer_action == OFPACT_WRITE_ACTIONS >> - || outer_action == OFPACT_CT); >> + || outer_action == OFPACT_CT || outer_action == >> OFPACT_CLONE); >> >> if (outer_action == OFPACT_CT) { >> if (!field) { >> @@ -7282,6 +7376,10 @@ ofpacts_verify_nested(const struct ofpact *a, enum >> ofpact_type outer_action) >> return OFPERR_OFPBAC_BAD_ARGUMENT; >> } >> } >> + if (outer_action == OFPACT_CLONE) { >> + /* add some constraints. */ >> + return 0; >> + } >> } >> >> return 0; >> @@ -7635,6 +7733,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, >> ofp_port_t port) >> case OFPACT_POP_MPLS: >> case OFPACT_SAMPLE: >> case OFPACT_CLEAR_ACTIONS: >> + case OFPACT_CLONE: >> case OFPACT_WRITE_ACTIONS: >> case OFPACT_GOTO_TABLE: >> case OFPACT_METER: >> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c >> index 55ac371..b534b31 100644 >> --- a/ofproto/ofproto-dpif-xlate.c >> +++ b/ofproto/ofproto-dpif-xlate.c >> @@ -4289,6 +4289,35 @@ xlate_sample_action(struct xlate_ctx *ctx, >> tunnel_out_port, false); >> } >> >> +static void >> +compose_clone_action(struct xlate_ctx *ctx, >> + const struct ofpact_clone *oc) >> +{ >> + struct flow old_flow, old_base_flow; >> + size_t actions_offset; >> + size_t sample_offset = nl_msg_start_nested(ctx->odp_actions, >> + OVS_ACTION_ATTR_SAMPLE); >> + >> + /* Ensure that any prior actions are applied. */ >> + xlate_commit_actions(ctx); >> + >> + /* clone might change the flow key, store the original. */ >> + old_flow = ctx->xin->flow; >> + old_base_flow = ctx->base_flow; >> + >> + /* Sample with 100% Probability */ >> + nl_msg_put_u32(ctx->odp_actions, OVS_SAMPLE_ATTR_PROBABILITY, >> UINT32_MAX); >> + actions_offset = nl_msg_start_nested(ctx->odp_actions, >> + OVS_SAMPLE_ATTR_ACTIONS); >> + do_xlate_actions(oc->actions, ofpact_clone_get_action_len(oc), ctx); >> + nl_msg_end_nested(ctx->odp_actions, actions_offset); >> + nl_msg_end_nested(ctx->odp_actions, sample_offset); >> + >> + /* restore flow key */ >> + ctx->xin->flow = old_flow; >> + ctx->base_flow = old_base_flow; >> +} >> + >> static bool >> may_receive(const struct xport *xport, struct xlate_ctx *ctx) >> { >> @@ -4447,6 +4476,7 @@ freeze_unroll_actions(const struct ofpact *a, const >> struct ofpact *end, >> case OFPACT_WRITE_ACTIONS: >> case OFPACT_METER: >> case OFPACT_SAMPLE: >> + case OFPACT_CLONE: >> case OFPACT_DEBUG_RECIRC: >> case OFPACT_CT: >> case OFPACT_NAT: >> @@ -4695,6 +4725,7 @@ recirc_for_mpls(const struct ofpact *a, struct >> xlate_ctx *ctx) >> case OFPACT_NOTE: >> case OFPACT_EXIT: >> case OFPACT_SAMPLE: >> + case OFPACT_CLONE: >> case OFPACT_UNROLL_XLATE: >> case OFPACT_CT: >> case OFPACT_NAT: >> @@ -5054,6 +5085,10 @@ do_xlate_actions(const struct ofpact *ofpacts, >> size_t ofpacts_len, >> xlate_sample_action(ctx, ofpact_get_SAMPLE(a)); >> break; >> >> + case OFPACT_CLONE: >> + compose_clone_action(ctx, ofpact_get_CLONE(a)); >> + break; >> + >> case OFPACT_CT: >> compose_conntrack_action(ctx, ofpact_get_CT(a)); >> break; >> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at >> index ec7bd60..999f9fd 100644 >> --- a/tests/ofproto-dpif.at >> +++ b/tests/ofproto-dpif.at >> @@ -6388,6 +6388,25 @@ AT_CHECK([ovs-vsctl destroy >> Flow_Sample_Collector_Set 1], [0], [ignore]) >> OVS_VSWITCHD_STOP >> AT_CLEANUP >> >> +dnl OpenFlow Clone action using Sample datapath action >> +AT_SETUP([ofproto-dpif - clone action]) >> +OVS_VSWITCHD_START >> +add_of_ports br0 1 2 3 4 >> + >> +AT_DATA([flows.txt], [dnl >> +in_port=1, >> actions=clone(set_field:192.168.3.3->ip_src),clone(set_field:192.168.4.4->ip_dst,output:2),clone(mod_dl_src:80:81:81:81:81:81,set_field:192.168.5.5->ip_dst,output:3),output:4 >> +]) >> +AT_CHECK([ovs-ofctl add-flows br0 flows.txt], [0], [ignore]) >> + >> +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy >> 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.10.10.2,dst=10.10.10.1,proto=1,tos=1,ttl=128,frag=no),icmp(type=8,code=0)'], >> [0], [stdout]) >> + >> +AT_CHECK([tail -1 stdout], [0], [dnl >> +Datapath actions: >> sample(sample=100.0%,actions(drop)),sample(sample=100.0%,actions(set(ipv4(src=10.10.10.2,dst=192.168.4.4)),2)),sample(sample=100.0%,actions(set(eth(src=80:81:81:81:81:81)),set(ipv4(src=10.10.10.2,dst=192.168.5.5)),3)),4 >> +]) >> + >> +OVS_VSWITCHD_STOP >> +AT_CLEANUP >> + >> dnl Flow based IPFIX statistics check >> AT_SETUP([ofproto-dpif - Flow IPFIX statistics check]) >> OVS_VSWITCHD_START >> diff --git a/tests/system-traffic.at b/tests/system-traffic.at >> index 801dfe3..31e5a16 100644 >> --- a/tests/system-traffic.at >> +++ b/tests/system-traffic.at >> @@ -337,6 +337,35 @@ NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 >> -w 2 10.1.1.100 | FORMAT_PI >> OVS_TRAFFIC_VSWITCHD_STOP >> AT_CLEANUP >> >> +AT_SETUP([datapath - clone action]) >> +OVS_TRAFFIC_VSWITCHD_START() >> + >> +ADD_NAMESPACES(at_ns0, at_ns1, at_ns2) >> + >> +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24") >> +ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24") >> +ADD_VETH(p2, at_ns2, br0, "10.1.1.3/24") >> + >> +AT_CHECK([ovs-vsctl -- set interface ovs-p0 ofport_request=1]) >> +AT_CHECK([ovs-vsctl -- set interface ovs-p1 ofport_request=2]) >> +AT_CHECK([ovs-vsctl -- set interface ovs-p2 ofport_request=3]) >> + >> +dnl verify that the clone(...) won't affect the original packet, so ping >> still works OK >> +dnl without 'output' in 'clone()' >> +AT_CHECK([ovs-ofctl add-flow br0 >> "in_port=1,actions=clone(mod_dl_dst(50:54:00:00:00:0a),set_field:192.168.3.3->ip_dst), >> output:2"]) >> +dnl with 'output' in 'clone()' >> +AT_CHECK([ovs-ofctl add-flow br0 >> "in_port=2,actions=clone(mod_dl_dst(50:54:00:00:00:0b),set_field:192.168.4.4->ip_dst, >> output:3), output:1"]) >> + >> +NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 -w 2 10.1.1.2 | >> FORMAT_PING], [0], [dnl >> +3 packets transmitted, 3 received, 0% packet loss, time 0ms >> +]) >> + >> +NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py]], [http0.pid]) >> +NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v >> -o wget0.log]) >> + >> +OVS_TRAFFIC_VSWITCHD_STOP >> +AT_CLEANUP >> + >> AT_SETUP([datapath - basic truncate action]) >> OVS_TRAFFIC_VSWITCHD_START() >> AT_CHECK([ovs-ofctl del-flows br0]) >> -- >> 2.5.0 >> >> _______________________________________________ >> dev mailing list >> d...@openvswitch.org >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev > > _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev