On Fri, Aug 25, 2023 at 10:28 AM Adrian Moreno <amore...@redhat.com> wrote:
>
>
>
> On 7/18/23 21:38, Mike Pattrick wrote:
> > Currently a bridge mirror will collect all packets and tools like
> > ovs-tcpdump can apply additional filters after they have already been
> > duplicated by vswitchd. This can result in inefficient collection.
> >
> > This patch adds support to apply pre-selection to bridge mirrors, which
> > can limit which packets are mirrored based on flow metadata. This
> > significantly improves overall vswitchd performance during mirroring if
> > only a subset of traffic is required.
> >
> > I benchmarked this with two setups. A netlink based test with two veth
> > pairs connected to a single bridge, and a netdev based test involving a
> > mix of DPDK nics, and netdev-linux interfaces. Both tests involved
> > saturating the link with iperf3 and then sending an icmp ping every
> > second. I then measured the throughput on the link with no mirroring,
> > icmp pre-selected mirroring, and full mirroring. The results, below,
> > indicate a significant reduction to impact of mirroring when only a
> > subset of the traffic on a port is selected for collection.
> >
> >   Test     No Mirror | No Filter |   Filter  | No Filter |  Filter  |
> >          +-----------+-----------+-----------+-----------+----------+
> > netlink | 39.0 Gbps | 36.1 Gbps | 38.2 Gbps |     7%    |    2%    |
> > netdev  | 7.39 Gbps | 4.95 Gbps | 6.24 Gbps |    33%    |   15%    |
> >
> > The ratios above are the percent reduction in total throughput when
> > mirroring is used either with or without a filter.
> >
>
> Looks really promising!
>
> > Signed-off-by: Mike Pattrick <m...@redhat.com>
> > ---
> >   Documentation/ref/ovs-tcpdump.8.rst |  4 ++
> >   NEWS                                |  2 +
> >   lib/flow.h                          | 11 ++++++
> >   ofproto/ofproto-dpif-mirror.c       | 60 +++++++++++++++++++++++++++--
> >   ofproto/ofproto-dpif-mirror.h       |  9 ++++-
> >   ofproto/ofproto-dpif-xlate.c        |  6 ++-
> >   ofproto/ofproto-dpif.c              |  2 +-
> >   ofproto/ofproto.h                   |  3 ++
> >   tests/ofproto-dpif.at               | 43 +++++++++++++++++++++
> >   utilities/ovs-tcpdump.in            | 13 ++++++-
> >   vswitchd/bridge.c                   |  8 ++++
> >   vswitchd/vswitch.ovsschema          |  4 +-
> >   vswitchd/vswitch.xml                |  6 +++
> >   13 files changed, 160 insertions(+), 11 deletions(-)
> >
> > diff --git a/Documentation/ref/ovs-tcpdump.8.rst 
> > b/Documentation/ref/ovs-tcpdump.8.rst
> > index b9f8cdf6f..9163c8a5e 100644
> > --- a/Documentation/ref/ovs-tcpdump.8.rst
> > +++ b/Documentation/ref/ovs-tcpdump.8.rst
> > @@ -61,6 +61,10 @@ Options
> >
> >     If specified, mirror all ports (optional).
> >
> > +* ``--filter <flow>``
> > +
> > +  If specified, only mirror flows that match the provided filter.
> > +
> >   See Also
> >   ========
> >
> > diff --git a/NEWS b/NEWS
> > index 7a852427e..26797ca56 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -1,5 +1,7 @@
> >   Post-v3.2.0
> >   --------------------
> > +   - ovs-tcpdump:
> > +     * Added new --filter parameter to apply pre-selection on mirrored 
> > flows.
> >
>
> Maybe also comment on the new column in the Mirror table of the OVSDB?
>
> >
> >   v3.2.0 - xx xxx xxxx
> > diff --git a/lib/flow.h b/lib/flow.h
> > index a9d026e1c..c2e67dfc5 100644
> > --- a/lib/flow.h
> > +++ b/lib/flow.h
> > @@ -939,6 +939,17 @@ flow_union_with_miniflow(struct flow *dst, const 
> > struct miniflow *src)
> >       flow_union_with_miniflow_subset(dst, src, src->map);
> >   }
> >
> > +/* Perform a bitwise OR of minimask 'src' mask data with the equivalent
> > + * fields in 'dst', storing the result in 'dst'. */
> > +static inline void
> > +flow_wildcards_union_with_minimask(struct flow_wildcards *dst,
> > +                                   const struct minimask *src)
> > +{
> > +    flow_union_with_miniflow_subset(&dst->masks,
> > +                                    &src->masks,
> > +                                    src->masks.map);
> > +}
> > +
> >   static inline bool is_ct_valid(const struct flow *flow,
> >                                  const struct flow_wildcards *mask,
> >                                  struct flow_wildcards *wc)
> > diff --git a/ofproto/ofproto-dpif-mirror.c b/ofproto/ofproto-dpif-mirror.c
> > index 343b75f0e..e4174a564 100644
> > --- a/ofproto/ofproto-dpif-mirror.c
> > +++ b/ofproto/ofproto-dpif-mirror.c
> > @@ -21,6 +21,7 @@
> >   #include "cmap.h"
> >   #include "hmapx.h"
> >   #include "ofproto.h"
> > +#include "ofproto-dpif-trace.h"
> >   #include "vlan-bitmap.h"
> >   #include "openvswitch/vlog.h"
> >
> > @@ -57,6 +58,10 @@ struct mirror {
> >       struct hmapx srcs;          /* Contains "struct mbundle*"s. */
> >       struct hmapx dsts;          /* Contains "struct mbundle*"s. */
> >
> > +    /* Filter criteria. */
> > +    struct miniflow *filter;
> > +    struct minimask *mask;
> > +
> >       /* This is accessed by handler threads assuming RCU protection (see
> >        * mirror_get()), but can be manipulated by mirror_set() without any
> >        * explicit synchronization. */
> > @@ -212,7 +217,9 @@ mirror_set(struct mbridge *mbridge, void *aux, const 
> > char *name,
> >              struct ofbundle **dsts, size_t n_dsts,
> >              unsigned long *src_vlans, struct ofbundle *out_bundle,
> >              uint16_t snaplen,
> > -           uint16_t out_vlan)
> > +           uint16_t out_vlan,
> > +           const char *filter,
> > +           const struct ofproto *ofproto)
> >   {
> >       struct mbundle *mbundle, *out;
> >       mirror_mask_t mirror_bit;
> > @@ -285,6 +292,33 @@ mirror_set(struct mbridge *mbridge, void *aux, const 
> > char *name,
> >       mirror->out_vlan = out_vlan;
> >       mirror->snaplen = snaplen;
> >
> > +    if (mirror->filter) {
> > +        ovsrcu_postpone(free, mirror->filter);
> > +        ovsrcu_postpone(free, mirror->mask);
> > +        mirror->filter = NULL;
> > +        mirror->mask = NULL;
> > +    }
> > +    if (filter) {
> > +        struct ofputil_port_map map = OFPUTIL_PORT_MAP_INITIALIZER(&map);
> > +        struct flow_wildcards wc;
> > +        struct flow flow;
> > +        char *err;
> > +
> > +        ofproto_append_ports_to_map(&map, ofproto->ports);
> > +        err = parse_ofp_exact_flow(&flow, &wc,
> > +                                   ofproto_get_tun_tab(ofproto),
> > +                                   filter, &map);
> > +        ofputil_port_map_destroy(&map);
> > +        if (err) {
> > +            VLOG_WARN("filter is invalid: %s", err);
> > +            free(err);
> > +            return EINVAL;
> > +        }
> >
>
> Not sure if this will clean the mirror correctly, it might leave it half-built
> (return value of ofproto_mirror_register() is not checked). Maybe it would be
> safer to parse the filter earlier during bridge.c's mirror_configure().

I think at the very least I should also add a check for the
ofproto_mirror_register() return value and cleanup/log as appropriate.
I'll investigate better locations for this code and the other
suggestions in an updated patch.


Thanks for the review,
M

>
>   +
> > +        mirror->mask = minimask_create(&wc);
> > +        mirror->filter = miniflow_create(&flow);
> > +    }
> > +
> >       /* Update mbundles. */
> >       mirror_bit = MIRROR_MASK_C(1) << mirror->idx;
> >       CMAP_FOR_EACH (mbundle, cmap_node, &mirror->mbridge->mbundles) {
> > @@ -340,6 +374,13 @@ mirror_destroy(struct mbridge *mbridge, void *aux)
> >           ovsrcu_postpone(free, vlans);
> >       }
> >
> > +    if (mirror->filter) {
> > +        ovsrcu_postpone(free, mirror->filter);
> > +        ovsrcu_postpone(free, mirror->mask);
> > +        mirror->filter = NULL;
> > +        mirror->mask = NULL;
> > +    }
> > +
> >       mbridge->mirrors[mirror->idx] = NULL;
> >       /* mirror_get() might have just read the pointer, so we must postpone 
> > the
> >        * free. */
> > @@ -411,14 +452,17 @@ mirror_update_stats(struct mbridge *mbridge, 
> > mirror_mask_t mirrors,
> >    * in which a 1-bit indicates that the mirror includes a particular VLAN,
> >    * '*dup_mirrors' receives a bitmap of mirrors whose output duplicates 
> > mirror
> >    * 'index', '*out' receives the output ofbundle (if any), and '*out_vlan'
> > - * receives the output VLAN (if any).
> > + * receives the output VLAN (if any). In cases where the mirror has a 
> > filter
> > + * configured and the flow matches that filter, '*wc' is modified to 
> > include
> > + * that filters mask.
> >    *
> >    * Everything returned here is assumed to be RCU protected.
> >    */
> >   bool
> >   mirror_get(struct mbridge *mbridge, int index, const unsigned long 
> > **vlans,
> >              mirror_mask_t *dup_mirrors, struct ofbundle **out,
> > -           int *snaplen, int *out_vlan)
> > +           int *snaplen, int *out_vlan, struct flow *flow,
> > +           struct flow_wildcards *wc)
> >   {
> >       struct mirror *mirror;
> >
> > @@ -430,6 +474,16 @@ mirror_get(struct mbridge *mbridge, int index, const 
> > unsigned long **vlans,
> >       if (!mirror) {
> >           return false;
> >       }
> > +
> > +    if (wc && mirror->filter) {
> > +        if (miniflow_equal_flow_in_minimask(mirror->filter, flow,
> > +                                            mirror->mask)) {
> > +            flow_wildcards_union_with_minimask(wc, mirror->mask);
> > +        } else {
> > +            return false;
> > +        }
> > +    }
> > +
>
> I don't think the flow filtering should be done here.
> First, it changes the bevhavior of the function (which used to just lookup the
> mirror based on the index) and the meaning of it's return value. Actually, by
> doing the filter here we're probably not benefiting from the OVS_UNLIKELY 
> macro
> that it's caller uses.
>
> And secondly, it makes it more difficult to understand the relationship 
> between
> flow filtering and vlan filtering (which, btw, should be documented. See 
> below).
>
> I think mirror_get() should just return the mirror information (filter and 
> mask)
> and we should perform the filtering in mirror_packet() (after vlan 
> filtering?).
>
> WDYT?
>
> >       /* Assume 'mirror' is RCU protected, i.e., it will not be freed until 
> > this
> >        * thread quiesces. */
> >
> > diff --git a/ofproto/ofproto-dpif-mirror.h b/ofproto/ofproto-dpif-mirror.h
> > index eed63ec4a..6de742cbf 100644
> > --- a/ofproto/ofproto-dpif-mirror.h
> > +++ b/ofproto/ofproto-dpif-mirror.h
> > @@ -24,6 +24,9 @@ typedef uint32_t mirror_mask_t;
> >
> >   struct ofproto_dpif;
> >   struct ofbundle;
> > +struct ofproto;
> > +struct flow;
> > +struct flow_wildcards;
> >
> >   /* The following functions are used by handler threads without any 
> > locking,
> >    * assuming RCU protection. */
> > @@ -40,7 +43,8 @@ void mirror_update_stats(struct mbridge*, mirror_mask_t, 
> > uint64_t packets,
> >                            uint64_t bytes);
> >   bool mirror_get(struct mbridge *, int index, const unsigned long **vlans,
> >                   mirror_mask_t *dup_mirrors, struct ofbundle **out,
> > -                int *snaplen, int *out_vlan);
> > +                int *snaplen, int *out_vlan, struct flow *flow,
> > +                struct flow_wildcards *wc);
> >
> >   /* The remaining functions are assumed to be called by the main thread 
> > only. */
> >
> > @@ -54,7 +58,8 @@ int mirror_set(struct mbridge *, void *aux, const char 
> > *name,
> >                  struct ofbundle **srcs, size_t n_srcs,
> >                  struct ofbundle **dsts, size_t n_dsts,
> >                  unsigned long *src_vlans, struct ofbundle *out_bundle,
> > -               uint16_t snaplen, uint16_t out_vlan);
> > +               uint16_t snaplen, uint16_t out_vlan, const char *filter,
> > +               const struct ofproto *ofproto);
> >   void mirror_destroy(struct mbridge *, void *aux);
> >   int mirror_get_stats(struct mbridge *, void *aux, uint64_t *packets,
> >                        uint64_t *bytes);
> > diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> > index 4928ea99c..4547caa31 100644
> > --- a/ofproto/ofproto-dpif-xlate.c
> > +++ b/ofproto/ofproto-dpif-xlate.c
> > @@ -2246,8 +2246,10 @@ mirror_packet(struct xlate_ctx *ctx, struct xbundle 
> > *xbundle,
> >           /* Get the details of the mirror represented by the rightmost 
> > 1-bit. */
> >           if (OVS_UNLIKELY(!mirror_get(xbridge->mbridge, raw_ctz(mirrors),
> >                                        &vlans, &dup_mirrors,
> > -                                     &out, &snaplen, &out_vlan))) {
> > -            /* The mirror got reconfigured before we got to read it's
> > +                                     &out, &snaplen, &out_vlan,
> > +                                     &ctx->xin->flow,
> > +                                     ctx->wc))) {
> > +            /* The mirror doesn't currently exist, we cannot retrieve it's
> >                * configuration. */
> >               mirrors = zero_rightmost_1bit(mirrors);
> >               continue;
> > diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> > index fad7342b0..c03bcea60 100644
> > --- a/ofproto/ofproto-dpif.c
> > +++ b/ofproto/ofproto-dpif.c
> > @@ -3662,7 +3662,7 @@ mirror_set__(struct ofproto *ofproto_, void *aux,
> >       error = mirror_set(ofproto->mbridge, aux, s->name, srcs, s->n_srcs, 
> > dsts,
> >                          s->n_dsts, s->src_vlans,
> >                          bundle_lookup(ofproto, s->out_bundle),
> > -                       s->snaplen, s->out_vlan);
> > +                       s->snaplen, s->out_vlan, s->filter, ofproto_);
> >       free(srcs);
> >       free(dsts);
> >       return error;
> > diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
> > index 8efdb20a0..372e0e3fc 100644
> > --- a/ofproto/ofproto.h
> > +++ b/ofproto/ofproto.h
> > @@ -497,6 +497,9 @@ struct ofproto_mirror_settings {
> >       uint16_t out_vlan;          /* Output VLAN, only if out_bundle is 
> > NULL. */
> >       uint16_t snaplen;           /* Max packet size of a mirrored packet
> >                                      in byte, set to 0 equals 65535. */
> > +
> > +    /* Output filter */
> > +    char *filter;
> >   };
> >
> >   int ofproto_mirror_register(struct ofproto *, void *aux,
> > diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> > index 6824ce0bb..e55c480d1 100644
> > --- a/tests/ofproto-dpif.at
> > +++ b/tests/ofproto-dpif.at
> > @@ -5182,6 +5182,49 @@ OVS_VSWITCHD_STOP
> >   AT_CLEANUP
> >
> >
> > +AT_SETUP([ofproto-dpif - mirroring, filter])
> > +AT_KEYWORDS([mirror mirrors mirroring])
> > +OVS_VSWITCHD_START
> > +add_of_ports br0 1 2 3
> > +ovs-vsctl \
> > +        set Bridge br0 mirrors=@m --\
> > +        --id=@p3 get Port p3 --\
> > +        --id=@m create Mirror name=mymirror select_all=true 
> > output_port=@p3 filter="icmp"
> > +
> > +AT_DATA([flows.txt], [dnl
> > +in_port=1 actions=output:2
> > +in_port=2 actions=output:1
> > +])
> > +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: 3,2
> > +])
> > +
> > +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=6,tos=0,ttl=128,frag=no),tcp()"
> > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> > +AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> > +  [Datapath actions: 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: 3,1
> > +])
> > +
> > +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=6,tos=0,ttl=128,frag=no),tcp()"
> > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "$flow"], [0], [stdout])
> > +AT_CHECK_UNQUOTED([tail -1 stdout], [0],
> > +  [Datapath actions: 1
> > +])
> > +
> > +OVS_VSWITCHD_STOP
> > +AT_CLEANUP
> > +
> > +
> >   AT_SETUP([ofproto-dpif - mirroring, select_all])
> >   AT_KEYWORDS([mirror mirrors mirroring])
> >   OVS_VSWITCHD_START
> > diff --git a/utilities/ovs-tcpdump.in b/utilities/ovs-tcpdump.in
> > index 420c11eb8..590f02c77 100755
> > --- a/utilities/ovs-tcpdump.in
> > +++ b/utilities/ovs-tcpdump.in
> > @@ -138,6 +138,7 @@ The following options are available:
> >      --mirror-to                The name for the mirror port to use 
> > (optional)
> >                                 Default 'miINTERFACE'
> >      --span                     If specified, mirror all ports (optional)
> > +   --filter                   Set a preselection filter
> >   """ % {'prog': sys.argv[0]})
> >       sys.exit(0)
> >
> > @@ -350,7 +351,7 @@ class OVSDB(object):
> >           return result
> >
> >       def bridge_mirror(self, intf_name, mirror_intf_name, br_name,
> > -                      mirror_select_all=False):
> > +                      mirror_select_all=False, mirror_filter=None):
> >
> >           txn = self._start_txn()
> >           mirror = txn.insert(self.get_table('Mirror'))
> > @@ -358,6 +359,9 @@ class OVSDB(object):
> >
> >           mirror.select_all = mirror_select_all
> >
> > +        if mirror_filter is not None:
> > +            mirror.filter = mirror_filter
> > +
> >           mirrored_port = self._find_row_by_name('Port', intf_name)
> >
> >           mirror.verify('select_dst_port')
> > @@ -441,6 +445,7 @@ def main():
> >       mirror_interface = None
> >       mirror_select_all = False
> >       dump_cmd = 'tcpdump'
> > +    mirror_filter = None
> >
> >       for cur, nxt in argv_tuples(sys.argv[1:]):
> >           if skip_next:
> > @@ -470,6 +475,10 @@ def main():
> >           elif cur in ['--span']:
> >               mirror_select_all = True
> >               continue
> > +        elif cur in ['--filter']:
> > +            mirror_filter = nxt
> > +            skip_next = True
> > +            continue
> >           tcpdargs.append(cur)
> >
> >       if interface is None:
> > @@ -522,7 +531,7 @@ def main():
> >           ovsdb.make_port(mirror_interface, ovsdb.port_bridge(interface))
> >           ovsdb.bridge_mirror(interface, mirror_interface,
> >                               ovsdb.port_bridge(interface),
> > -                            mirror_select_all)
> > +                            mirror_select_all, mirror_filter=mirror_filter)
> >       except OVSDBException as oe:
> >           print("ERROR: Unable to properly setup the mirror: %s." % str(oe))
> >           sys.exit(1)
> > diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
> > index e9110c1d8..50213a1f9 100644
> > --- a/vswitchd/bridge.c
> > +++ b/vswitchd/bridge.c
> > @@ -5166,6 +5166,13 @@ mirror_configure(struct mirror *m)
> >       /* Get VLAN selection. */
> >       s.src_vlans = vlan_bitmap_from_array(cfg->select_vlan, 
> > cfg->n_select_vlan);
> >
> > +    /* Set filter, this value is not retained after mirror_set() is 
> > called. */
> > +    if (cfg->filter && strlen(cfg->filter)) {
> > +        s.filter = xstrdup(cfg->filter);
> > +    } else {
> > +        s.filter = NULL;
> > +    }
> > +
> >       /* Configure. */
> >       ofproto_mirror_register(m->bridge->ofproto, m, &s);
> >
> > @@ -5175,6 +5182,7 @@ mirror_configure(struct mirror *m)
> >       }
> >       free(s.srcs);
> >       free(s.src_vlans);
> > +    free(s.filter);
> >
> >       return true;
> >   }
> > diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
> > index 2d395ff95..5400a5bed 100644
> > --- a/vswitchd/vswitch.ovsschema
> > +++ b/vswitchd/vswitch.ovsschema
> > @@ -1,6 +1,6 @@
> >   {"name": "Open_vSwitch",
> >    "version": "8.4.0",
> > - "cksum": "2738838700 27127",
> > + "cksum": "59426185 27174",
> >    "tables": {
> >      "Open_vSwitch": {
> >        "columns": {
> > @@ -461,6 +461,8 @@
> >            "type": {"key": "string", "value": "integer",
> >                     "min": 0, "max": "unlimited"},
> >            "ephemeral": true},
> > +       "filter": {
> > +         "type": "string"},
> >          "external_ids": {
> >            "type": {"key": "string", "value": "string",
> >                     "min": 0, "max": "unlimited"}}}},
> > diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
> > index cfcde34ff..3965054d5 100644
> > --- a/vswitchd/vswitch.xml
> > +++ b/vswitchd/vswitch.xml
> > @@ -5237,6 +5237,12 @@ ovs-vsctl add-port br0 p0 -- set Interface p0 
> > type=patch options:peer=p1 \
> >              are not truncated.
> >           </p>
> >         </column>
> > +      <column name="filter">
> > +        <p>Filter to apply to mirror.</p>
> > +        <p>Flows that match <ref column="filter"/> will flow through this
> > +           mirror, flows that do not match will be ignored.
> > +        </p>
> > +      </column> >       </group>
>
> IIUC, this adds "filter" option to "Mirroring Destination Configuration" 
> group.
> I think it's better suited in the "Selecting Packets for Mirroring" group, 
> where
> a sentence could be added explaining the relationship between flow-filtering 
> and
> vlan-filtering.
>
> >
> >       <group title="Statistics: Mirror counters">
>
> Thanks.
> --
> Adrián Moreno
>

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to