[ovs-dev] [PATCH 22/26] ofproto-dpif-xlate: Rewrite mirroring to better fit flow translation.

Jarno Rajahalme jrajahalme at nicira.com
Fri Jul 31 21:47:48 UTC 2015


Always learn something new when reviewing code ;-)

Acked-by: Jarno Rajahalme <jrajahalme at nicira.com>

> On Jul 29, 2015, at 11:42 PM, Ben Pfaff <blp at nicira.com> wrote:
> 
> Until now, mirroring has been implemented by accumulating, across the whole
> translation process, a set of mirrors that should receive a mirrored
> packet.  After translation was complete, mirroring restored the original
> version of the packet and sent that version to the mirrors.
> 
> That implementation was ugly for multiple reasons.  First, it means that
> we have to keep a copy of the original packet (or its headers, actually),
> which is expensive.  Second, it doesn't really make sense to mirror a
> version of a packet that is different from the one originally output.
> Third, it interacted with recirculation; mirroring needed to happen only
> after recirculation was complete, but this was never properly implemented,
> so that (I think) mirroring never happened for packets that were
> recirculated.
> 
> This commit changes how mirroring works.  Now, a packet is mirrored at the
> point in translation when it becomes eligible for it: for mirrors based on
> ingress port, this is at ingress; for mirrors based on egress port, this
> is at egress.  (Duplicates are dropped.)  Mirroring happens on the version
> of the packet as it exists when it becomes eligible.  Finally, since
> mirroring happens immediately, it interacts better with recirculation
> (it still isn't perfect, since duplicate mirroring will occur if a packet
> is eligible for mirroring both before and after recirculation; this is
> not difficult to fix and an upcoming commit later in this series will do so).
> 
> Finally, this commit removes more code from xlate_actions() than it adds,
> which in my opinion makes it easier to understand.
> 
> Signed-off-by: Ben Pfaff <blp at nicira.com>
> ---
> ofproto/ofproto-dpif-xlate.c | 113 +++++++++++++++++++------------------------
> tests/ofproto-dpif.at        |  12 ++---
> vswitchd/vswitch.xml         |  26 ++++++++++
> 3 files changed, 82 insertions(+), 69 deletions(-)
> 
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index 8c8da9a..cbcd2a3 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -1530,56 +1530,55 @@ lookup_input_bundle(const struct xbridge *xbridge, ofp_port_t in_port,
> }
> 
> static void
> -add_mirror_actions(struct xlate_ctx *ctx, const struct flow *orig_flow)
> +mirror_packet(struct xlate_ctx *ctx, struct xbundle *xbundle,
> +              mirror_mask_t mirrors)
> {
> -    const struct xbridge *xbridge = ctx->xbridge;
> -    mirror_mask_t mirrors;
> -    struct xbundle *in_xbundle;
> -    uint16_t vlan;
> -    uint16_t vid;
> -
> -    mirrors = ctx->mirrors;
> -    ctx->mirrors = 0;
> -
> -    in_xbundle = lookup_input_bundle(xbridge, orig_flow->in_port.ofp_port,
> -                                     ctx->xin->packet != NULL, NULL);
> -    if (!in_xbundle) {
> +    bool warn = ctx->xin->packet != NULL;
> +    uint16_t vid = vlan_tci_to_vid(ctx->xin->flow.vlan_tci);
> +    if (!input_vid_is_valid(vid, xbundle, warn)) {
>         return;
>     }
> -    mirrors |= xbundle_mirror_src(xbridge, in_xbundle);
> +    uint16_t vlan = input_vid_to_vlan(xbundle, vid);
> 
> -    /* Check VLAN. */
> -    vid = vlan_tci_to_vid(orig_flow->vlan_tci);
> -    if (!input_vid_is_valid(vid, in_xbundle, ctx->xin->packet != NULL)) {
> -        return;
> -    }
> -    vlan = input_vid_to_vlan(in_xbundle, vid);
> +    const struct xbridge *xbridge = ctx->xbridge;
> 
> +    /* Don't mirror to destinations that we've already mirrored to. */
> +    mirrors &= ~ctx->mirrors;
>     if (!mirrors) {
>         return;
>     }
> 
> -    /* Restore the original packet before adding the mirror actions. */
> -    ctx->xin->flow = *orig_flow;
> +    /* Record these mirrors so that we don't mirror to them again. */
> +    ctx->mirrors |= mirrors;
> +
> +    if (ctx->xin->resubmit_stats) {
> +        mirror_update_stats(xbridge->mbridge, mirrors,
> +                            ctx->xin->resubmit_stats->n_packets,
> +                            ctx->xin->resubmit_stats->n_bytes);
> +    }
> +    if (ctx->xin->xcache) {
> +        struct xc_entry *entry;
> +
> +        entry = xlate_cache_add_entry(ctx->xin->xcache, XC_MIRROR);
> +        entry->u.mirror.mbridge = mbridge_ref(xbridge->mbridge);
> +        entry->u.mirror.mirrors = mirrors;
> +    }
> 
>     while (mirrors) {
> +        const unsigned long *vlans;
>         mirror_mask_t dup_mirrors;
>         struct ofbundle *out;
> -        const unsigned long *vlans;
> -        bool vlan_mirrored;
> -        bool has_mirror;
>         int out_vlan;
> 
> -        has_mirror = mirror_get(xbridge->mbridge, raw_ctz(mirrors),
> -                                &vlans, &dup_mirrors, &out, &out_vlan);
> +        bool has_mirror = mirror_get(xbridge->mbridge, raw_ctz(mirrors),
> +                                     &vlans, &dup_mirrors, &out, &out_vlan);
>         ovs_assert(has_mirror);
> 
>         if (vlans) {
>             ctx->wc->masks.vlan_tci |= htons(VLAN_CFI | VLAN_VID_MASK);
>         }
> -        vlan_mirrored = !vlans || bitmap_is_set(vlans, vlan);
> 
> -        if (!vlan_mirrored) {
> +        if (vlans && !bitmap_is_set(vlans, vlan)) {
>             mirrors = zero_rightmost_1bit(mirrors);
>             continue;
>         }
> @@ -1593,7 +1592,7 @@ add_mirror_actions(struct xlate_ctx *ctx, const struct flow *orig_flow)
>                 output_normal(ctx, out_xbundle, vlan);
>             }
>         } else if (vlan != out_vlan
> -                   && !eth_addr_is_reserved(orig_flow->dl_dst)) {
> +                   && !eth_addr_is_reserved(ctx->xin->flow.dl_dst)) {
>             struct xbundle *xbundle;
> 
>             LIST_FOR_EACH (xbundle, list_node, &xbridge->xbundles) {
> @@ -1606,6 +1605,20 @@ add_mirror_actions(struct xlate_ctx *ctx, const struct flow *orig_flow)
>     }
> }
> 
> +static void
> +mirror_ingress_packet(struct xlate_ctx *ctx)
> +{
> +    if (mbridge_has_mirrors(ctx->xbridge->mbridge)) {
> +        bool warn = ctx->xin->packet != NULL;
> +        struct xbundle *xbundle = lookup_input_bundle(
> +            ctx->xbridge, ctx->xin->flow.in_port.ofp_port, warn, NULL);
> +        if (xbundle) {
> +            mirror_packet(ctx, xbundle,
> +                          xbundle_mirror_src(ctx->xbridge, xbundle));
> +        }
> +    }
> +}
> +
> /* Given 'vid', the VID obtained from the 802.1Q header that was received as
>  * part of a packet (specify 0 if there was no 802.1Q header), and 'in_xbundle',
>  * the bundle on which the packet was received, returns the VLAN to which the
> @@ -2812,11 +2825,6 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
>         }
>     }
> 
> -    if (mbridge_has_mirrors(ctx->xbridge->mbridge) && xport->xbundle) {
> -        ctx->mirrors |= xbundle_mirror_dst(xport->xbundle->xbridge,
> -                                           xport->xbundle);
> -    }
> -
>     if (xport->peer) {
>         const struct xport *peer = xport->peer;
>         struct flow old_flow = ctx->xin->flow;
> @@ -3032,6 +3040,12 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
>         ctx->nf_output_iface = ofp_port;
>     }
> 
> +    if (mbridge_has_mirrors(ctx->xbridge->mbridge) && xport->xbundle) {
> +        mirror_packet(ctx, xport->xbundle,
> +                      xbundle_mirror_dst(xport->xbundle->xbridge,
> +                                         xport->xbundle));
> +    }
> +
>  out:
>     /* Restore flow */
>     flow->vlan_tci = flow_vlan_tci;
> @@ -4878,13 +4892,6 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
>     }
>     xout->fail_open = ctx.rule && rule_dpif_is_fail_open(ctx.rule);
> 
> -    struct flow orig_flow;
> -    if (mbridge_has_mirrors(xbridge->mbridge)) {
> -        /* Do this conditionally because the copy is expensive enough that it
> -         * shows up in profiles. */
> -        orig_flow = *flow;
> -    }
> -
>     /* Get the proximate input port of the packet.  (If xin->recirc,
>      * flow->in_port is the ultimate input port of the packet.) */
>     struct xport *in_port = get_ofp_port(xbridge,
> @@ -4947,6 +4954,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
>                 OVS_NOT_REACHED();
>             }
> 
> +            mirror_ingress_packet(&ctx);
>             do_xlate_actions(ofpacts, ofpacts_len, &ctx);
> 
>             /* We've let OFPP_NORMAL and the learning action look at the
> @@ -4986,11 +4994,6 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
>         if (user_cookie_offset) {
>             fix_sflow_action(&ctx, user_cookie_offset);
>         }
> -        /* Only mirror fully processed packets. */
> -        if (!exit_recirculates(&ctx)
> -            && mbridge_has_mirrors(xbridge->mbridge)) {
> -            add_mirror_actions(&ctx, &orig_flow);
> -        }
>     }
> 
>     if (nl_attr_oversized(ctx.odp_actions->size)) {
> @@ -5005,22 +5008,6 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
>         ctx.xout->slow |= SLOW_ACTION;
>     }
> 
> -    /* Update mirror stats only for packets really received by the bridge. */
> -    if (!xin->recirc && mbridge_has_mirrors(xbridge->mbridge)) {
> -        if (ctx.xin->resubmit_stats) {
> -            mirror_update_stats(xbridge->mbridge, ctx.mirrors,
> -                                ctx.xin->resubmit_stats->n_packets,
> -                                ctx.xin->resubmit_stats->n_bytes);
> -        }
> -        if (ctx.xin->xcache) {
> -            struct xc_entry *entry;
> -
> -            entry = xlate_cache_add_entry(ctx.xin->xcache, XC_MIRROR);
> -            entry->u.mirror.mbridge = mbridge_ref(xbridge->mbridge);
> -            entry->u.mirror.mirrors = ctx.mirrors;
> -        }
> -    }
> -
>     /* Do netflow only for packets really received by the bridge and not sent
>      * to the controller.  We consider packets sent to the controller to be
>      * part of the control plane rather than the data plane. */
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 2656ca7..9bfb794 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -3954,13 +3954,13 @@ 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)"
> AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> -  [Datapath actions: 2,3
> +  [Datapath actions: 3,2
> ])
> 
> flow="in_port(2),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)"
> AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> -  [Datapath actions: 1,3
> +  [Datapath actions: 3,1
> ])
> 
> OVS_VSWITCHD_STOP
> @@ -3984,7 +3984,7 @@ 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)"
> AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> -  [Datapath actions: 2,3
> +  [Datapath actions: 3,2
> ])
> 
> flow="in_port(2),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)"
> @@ -4074,7 +4074,7 @@ AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> flow="in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x8100),vlan(vid=11,pcp=0),encap(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))"
> AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> -  [Datapath actions: 2,3
> +  [Datapath actions: 3,2
> ])
> 
> OVS_VSWITCHD_STOP
> @@ -4098,13 +4098,13 @@ 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)"
> AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> -  [Datapath actions: push_vlan(vid=17,pcp=0),2,pop_vlan,3
> +  [Datapath actions: 3,push_vlan(vid=17,pcp=0),2
> ])
> 
> flow="in_port(2),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)"
> AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> -  [Datapath actions: 1,3
> +  [Datapath actions: 3,1
> ])
> 
> OVS_VSWITCHD_STOP
> diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
> index 483a9de..17035bb 100644
> --- a/vswitchd/vswitch.xml
> +++ b/vswitchd/vswitch.xml
> @@ -3424,6 +3424,32 @@
>     traffic may also be referred to as SPAN or RSPAN, depending on how
>     the mirrored traffic is sent.</p>
> 
> +    <p>
> +      When a packet enters an Open vSwitch bridge, it becomes eligible for
> +      mirroring based on its ingress port and VLAN.  As the packet travels
> +      through the flow tables, each time it is output to a port, it becomes
> +      eligible for mirroring based on the egress port and VLAN.  In Open
> +      vSwitch 2.5 and later, mirroring occurs just after a packet first becomes
> +      eligible, using the packet as it exists at that point; in Open vSwitch
> +      2.4 and earlier, mirroring occurs only after a packet has traversed all
> +      the flow tables, using the original packet as it entered the bridge.
> +      This makes a difference only when the flow table modifies the packet: in
> +      Open vSwitch 2.4, the modifications are never visible to mirrors, whereas
> +      in Open vSwitch 2.5 and later modifications made before the first output
> +      that makes it eligible for mirroring to a particular destination are
> +      visible.
> +    </p>
> +
> +    <p>
> +      A packet that enters an Open vSwitch bridge is mirrored to a particular
> +      destination only once, even if it is eligible for multiple reasons.  For
> +      example, a packet would be mirrored to a particular <ref
> +      column="output_port"/> only once, even if it is selected for mirroring to
> +      that port by <ref column="select_dst_port"/> and <ref
> +      column="select_src_port"/> in the same or different <ref table="Mirror"/>
> +      records.
> +    </p>
> +
>     <column name="name">
>       Arbitrary identifier for the <ref table="Mirror"/>.
>     </column>
> -- 
> 2.1.3
> 
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev




More information about the dev mailing list