Unlike other fields, the tunnel ones don't really need to be
unwildcarded on writes (load, move, set_field, stack_pop).
The main reason for unwildcarding other fields is that we have
optimizations in odp-util that skip generation of set() actions for
packet fields that didn't change. So, we have to match on them to
avoid installing an overly broad flow that will swallow unrelated
traffic that would have the set() action generated.
But this is not the case for tunnel fields, for which the set(tunnel)
action always contains the full set of required fields. The action
can be skipped entirely if the tunnel metadata didn't change at all,
but this is only the case when we're sending a packet to the exact
same tunnel twice, which is not a problem, as only the second
set(tunnel) is skipped and all the correct metadata was already set
by the first one. And sending a packet back to the same tunnel
without changing any metadata is not really possible, as at least the
source port will be zeroed out, and is not a reasonable thing to do
regardless.
Also, all the basic fields that are required to distinguish two tunnel
ports from each other are exact matched by default in tnl_wc_init().
Let's check if the destination field is a tunnel field and avoid
unnecessarily unwildcarding them.
This is needed because when the tunnel family changes from IPv4 to
IPv6, while setting tun_ipv6_src/dst we're unwildcarding those fields
in the match, making it match on both IPv4 and IPv6 addresses. This
is fine from the OpenFlow point of view, since all these fields have
no prerequisites. And the userspace daapath can handle this just fine.
But Linux kernel datapath is using a union to store the tunnel config
and so it rejects flows with both IPv4 and IPv6 fields matched.
This makes it very expensive to bridge IPv4 and IPv6 flow-based
tunnels together with the kernel datapath, as flow put always fails
and every packet has to go through userspace.
The change also allows to avoid matches on other fields that didn't
exist in the original tunnel, e.g., extra geneve metadata TLVs that
are added in transit.
We cannot just fixup the flow match at the xlate_wc_finish() stage,
because we may have extra fields unwildcarded by the classifier and
we do not have enough information to tell which are required and
which are not. Tt may even be possible that it is required by the
OpenFlow rules to match on both IPv4 and IPv6 fields at the same time.
And this can even be a valid configuration to distinguish packets
that are not coming from a tunnel by checking if both metadata fields
are zero. This is a separate and potentially more complex issue to
address and it may require introduction of new fields that would make
it possible to make decisions without looking at both sets of metadata,
e.g. tun_eth_type. This commit is focusing only on matches coming
from the actions like set_field.
Fixes: f74e7df7450d ("ofproto-dpif: Always un-wildcard fields that are being
set.")
Signed-off-by: Ilya Maximets <[email protected]>
---
include/openvswitch/meta-flow.h | 1 +
lib/meta-flow.c | 117 +++++++++++++++++++++++++++++++-
lib/nx-match.c | 20 ++++--
ofproto/ofproto-dpif-xlate.c | 8 ++-
tests/system-traffic.at | 101 +++++++++++++++++++++++++++
tests/tunnel.at | 36 +++++++++-
6 files changed, 273 insertions(+), 10 deletions(-)
diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
index 875f122c5..e65271ab9 100644
--- a/include/openvswitch/meta-flow.h
+++ b/include/openvswitch/meta-flow.h
@@ -2307,6 +2307,7 @@ void mf_set_flow_value_masked(const struct mf_field *,
const union mf_value *value,
const union mf_value *mask,
struct flow *);
+bool mf_is_tunnel_field(const struct mf_field *);
bool mf_is_tun_metadata(const struct mf_field *);
bool mf_is_any_metadata(const struct mf_field *);
bool mf_is_frozen_metadata(const struct mf_field *);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 2595fd634..39076f8fa 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -1783,6 +1783,115 @@ mf_set_flow_value_masked(const struct mf_field *field,
mf_set_flow_value(field, &tmp, flow);
}
+bool
+mf_is_tunnel_field(const struct mf_field *mf)
+{
+ switch (mf->id) {
+ case MFF_TUN_ID:
+ case MFF_TUN_SRC:
+ case MFF_TUN_DST:
+ case MFF_TUN_IPV6_SRC:
+ case MFF_TUN_IPV6_DST:
+ case MFF_TUN_FLAGS:
+ case MFF_TUN_TTL:
+ case MFF_TUN_TOS:
+ case MFF_TUN_GBP_ID:
+ case MFF_TUN_GBP_FLAGS:
+ case MFF_TUN_ERSPAN_VER:
+ case MFF_TUN_ERSPAN_IDX:
+ case MFF_TUN_ERSPAN_DIR:
+ case MFF_TUN_ERSPAN_HWID:
+ case MFF_TUN_GTPU_FLAGS:
+ case MFF_TUN_GTPU_MSGTYPE:
+ CASE_MFF_TUN_METADATA:
+ return true;
+
+ case MFF_DP_HASH:
+ case MFF_RECIRC_ID:
+ case MFF_PACKET_TYPE:
+ case MFF_CONJ_ID:
+ case MFF_METADATA:
+ case MFF_IN_PORT:
+ case MFF_IN_PORT_OXM:
+ case MFF_ACTSET_OUTPUT:
+ case MFF_SKB_PRIORITY:
+ case MFF_PKT_MARK:
+ case MFF_CT_STATE:
+ case MFF_CT_ZONE:
+ case MFF_CT_MARK:
+ case MFF_CT_LABEL:
+ case MFF_CT_NW_PROTO:
+ case MFF_CT_NW_SRC:
+ case MFF_CT_NW_DST:
+ case MFF_CT_IPV6_SRC:
+ case MFF_CT_IPV6_DST:
+ case MFF_CT_TP_SRC:
+ case MFF_CT_TP_DST:
+ CASE_MFF_REGS:
+ CASE_MFF_XREGS:
+ CASE_MFF_XXREGS:
+ case MFF_ETH_SRC:
+ case MFF_ETH_DST:
+ case MFF_ETH_TYPE:
+ case MFF_VLAN_TCI:
+ case MFF_DL_VLAN:
+ case MFF_VLAN_VID:
+ case MFF_DL_VLAN_PCP:
+ case MFF_VLAN_PCP:
+ case MFF_MPLS_LABEL:
+ case MFF_MPLS_TC:
+ case MFF_MPLS_BOS:
+ case MFF_MPLS_TTL:
+ case MFF_IPV4_SRC:
+ case MFF_IPV4_DST:
+ case MFF_IPV6_SRC:
+ case MFF_IPV6_DST:
+ case MFF_IPV6_LABEL:
+ case MFF_IP_PROTO:
+ case MFF_IP_DSCP:
+ case MFF_IP_DSCP_SHIFTED:
+ case MFF_IP_ECN:
+ case MFF_IP_TTL:
+ case MFF_IP_FRAG:
+ case MFF_ARP_OP:
+ case MFF_ARP_SPA:
+ case MFF_ARP_TPA:
+ case MFF_ARP_SHA:
+ case MFF_ARP_THA:
+ case MFF_TCP_SRC:
+ case MFF_TCP_DST:
+ case MFF_TCP_FLAGS:
+ case MFF_UDP_SRC:
+ case MFF_UDP_DST:
+ case MFF_SCTP_SRC:
+ case MFF_SCTP_DST:
+ case MFF_ICMPV4_TYPE:
+ case MFF_ICMPV4_CODE:
+ case MFF_ICMPV6_TYPE:
+ case MFF_ICMPV6_CODE:
+ case MFF_ND_TARGET:
+ case MFF_ND_SLL:
+ case MFF_ND_TLL:
+ case MFF_ND_RESERVED:
+ case MFF_ND_OPTIONS_TYPE:
+ case MFF_NSH_FLAGS:
+ case MFF_NSH_MDTYPE:
+ case MFF_NSH_NP:
+ case MFF_NSH_SPI:
+ case MFF_NSH_SI:
+ case MFF_NSH_C1:
+ case MFF_NSH_C2:
+ case MFF_NSH_C3:
+ case MFF_NSH_C4:
+ case MFF_NSH_TTL:
+ return false;
+
+ case MFF_N_IDS:
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
bool
mf_is_tun_metadata(const struct mf_field *mf)
{
@@ -2767,6 +2876,10 @@ unwildcard_subfield(const struct mf_subfield *sf, struct
flow_wildcards *wc)
/* Copies 'src' into 'dst' within 'flow', and sets all the bits in 'src' and
* 'dst' to 1s in 'wc', if 'wc' is nonnull.
*
+ * Note: doesn't set 'dst' to 1s in case 'dst' is a tunnel field, because it
+ * is not necessary, as ODP library doesn't rely on matching these fields
+ * when they are written but not read.
+ *
* 'src' and 'dst' may overlap. */
void
mf_subfield_copy(const struct mf_subfield *src,
@@ -2777,7 +2890,9 @@ mf_subfield_copy(const struct mf_subfield *src,
if (mf_are_prereqs_ok(dst->field, flow, wc)
&& mf_are_prereqs_ok(src->field, flow, wc)) {
unwildcard_subfield(src, wc);
- unwildcard_subfield(dst, wc);
+ if (!mf_is_tunnel_field(dst->field)) {
+ unwildcard_subfield(dst, wc);
+ }
union mf_value src_value;
union mf_value dst_value;
diff --git a/lib/nx-match.c b/lib/nx-match.c
index cb49185f1..23605fe02 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -1850,8 +1850,12 @@ nxm_reg_load(const struct mf_subfield *dst, uint64_t
src_data,
union mf_subvalue mask_value;
ovs_be64 src_data_be = htonll(src_data);
- memset(&mask_value, 0xff, sizeof mask_value);
- mf_write_subfield_flow(dst, &mask_value, &wc->masks);
+ /* ODP library doesn't rely on tunnel fields to be unwildcarded
+ * when written but not read. No need to unwildcard them here. */
+ if (!mf_is_tunnel_field(dst->field)) {
+ memset(&mask_value, 0xff, sizeof mask_value);
+ mf_write_subfield_flow(dst, &mask_value, &wc->masks);
+ }
bitwise_copy(&src_data_be, sizeof src_data_be, 0,
&src_subvalue, sizeof src_subvalue, 0,
@@ -1967,7 +1971,7 @@ nxm_execute_stack_push(const struct ofpact_stack *push,
union mf_subvalue dst_value;
mf_write_subfield_flow(&push->subfield,
- (union mf_subvalue *)&exact_match_mask,
+ (union mf_subvalue *) &exact_match_mask,
&wc->masks);
mf_read_subfield(&push->subfield, flow, &dst_value);
@@ -1991,9 +1995,13 @@ nxm_execute_stack_pop(const struct ofpact_stack *pop,
dst_bytes - src_bytes);
}
memcpy(&src_value.u8[sizeof src_value - src_bytes], src, src_bytes);
- mf_write_subfield_flow(&pop->subfield,
- (union mf_subvalue *)&exact_match_mask,
- &wc->masks);
+ /* ODP library doesn't rely on tunnel fields to be unwildcarded
+ * when written but not read. No need to unwildcard them here. */
+ if (!mf_is_tunnel_field(pop->subfield.field)) {
+ mf_write_subfield_flow(&pop->subfield,
+ (union mf_subvalue *) &exact_match_mask,
+ &wc->masks);
+ }
mf_write_subfield_flow(&pop->subfield, &src_value, flow);
return true;
} else {
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 920d998e6..4f0099f5f 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -7665,7 +7665,13 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t
ofpacts_len,
/* Set the field only if the packet actually has it. */
if (mf_are_prereqs_ok(mf, flow, wc)) {
mf_set_mask_l3_prereqs(mf, flow, wc);
- mf_mask_field_masked(mf, ofpact_set_field_mask(set_field), wc);
+ /* Tunnel fields do not need to be unwildcarded, as ODP library
+ * doesn't rely on matching these fields when they are written
+ * but not read. */
+ if (!mf_is_tunnel_field(mf)) {
+ mf_mask_field_masked(mf, ofpact_set_field_mask(set_field),
+ wc);
+ }
mf_set_flow_value_masked(mf, set_field->value,
ofpact_set_field_mask(set_field),
flow);
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index 58a46af0a..79ddc8e86 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -1264,6 +1264,107 @@ NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 -W
2 10.1.1.2 | FORMAT_PING
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([datapath - bridging IPv4 and IPv6 flow based geneve tunnels])
+OVS_CHECK_TUNNEL_TSO()
+OVS_CHECK_GENEVE()
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-underlay-0])
+ADD_BR([br-underlay-1])
+
+ADD_NAMESPACES(at_ns0)
+ADD_NAMESPACES(at_ns1)
+
+dnl Set up underlay link from host into the namespaces using veth pairs.
+ADD_VETH(p0, at_ns0, br-underlay-0, "172.31.1.1/24")
+AT_CHECK([ip addr add dev br-underlay-0 "172.31.1.100/24"])
+AT_CHECK([ip link set dev br-underlay-0 up])
+
+ADD_VETH(p1, at_ns1, br-underlay-1, "fc00::1/64", [], [], "nodad")
+AT_CHECK([ip addr add dev br-underlay-1 "fc00::100/64" nodad])
+AT_CHECK([ip link set dev br-underlay-1 up])
+
+dnl Set up a single flow-based OVS tunnel endpoint in a root namespace and
+dnl two native linux devices inside the test namespaces.
+dnl
+dnl ns_gnv0 | ns_gnv1
+dnl ip: 10.1.1.1/24 | ip: 10.1.1.2/24
+dnl remote_ip: 172.31.1.100 | remote_ip: fc00::100
+dnl vni: 4 | vni: 6
+dnl | | |
+dnl | | |
+dnl p0 | p1
+dnl ip: 172.31.1.1/24 | ip: fc00::1/64
+dnl | NS0 | NS1 |
+dnl ---------|------------------------+------------------|--------------------
+dnl | |
+dnl br-underlay-0: br-underlay-1:
+dnl ip: 172.31.1.100/24 ip: fc00::100/64
+dnl ovs-p0 ovs-p1
+dnl | |
+dnl | br0 |
+dnl encap/decap --- ip: 10.1.1.100/24 --------- encap/decap
+dnl at_gnv0
+dnl remote_ip: flow
+dnl local_ip: flow
+dnl key: flow
+dnl
+
+dnl Using v6 macro for OVS tunnel to account for maximum overhead.
+ADD_OVS_TUNNEL6([geneve], [br0], [at_gnv0], [flow], [10.1.1.100/24],
+ [options:local_ip=flow options:key=flow])
+
+dnl Setup Linux native tunnels inside namespaces. Again, using v6 macro for
+dnl the proper MTU configuration.
+ADD_NATIVE_TUNNEL6([geneve], [ns_gnv0], [at_ns0], [172.31.1.100],
[10.1.1.1/24],
+ [vni 4])
+ADD_NATIVE_TUNNEL6([geneve], [ns_gnv1], [at_ns1], [fc00::100], [10.1.1.2/24],
+ [vni 6 udp6zerocsumtx udp6zerocsumrx])
+
+AT_DATA([flows.txt], [dnl
+in_port=at_gnv0,tun_id=4,actions=load:0->tun_src,load:0->tun_dst,set_field:fc00::100->tun_ipv6_src,set_field:fc00::1->tun_ipv6_dst,set_field:6->tun_id,output:in_port
+in_port=at_gnv0,tun_id=6,actions=load:0->tun_ipv6_src,load:0->tun_ipv6_dst,set_field:172.31.1.100->tun_src,set_field:172.31.1.1->tun_dst,set_field:4->tun_id,output:in_port
+])
+
+AT_CHECK([ovs-ofctl -OOpenFlow15 add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl add-flow br-underlay-0 "actions=normal"])
+AT_CHECK([ovs-ofctl add-flow br-underlay-1 "actions=normal"])
+
+dnl It many take longer for IPv6 address to become available.
+OVS_WAIT_UNTIL([ip netns exec at_ns1 ping6 -c 2 fc00::100])
+
+dnl First, check both underlays.
+NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 -W 2 172.31.1.100 | FORMAT_PING],
[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+NS_CHECK_EXEC([at_ns1], [ping6 -q -c 3 -i 0.3 -W 2 fc00::100 | FORMAT_PING],
[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+dnl Now, check the overlay with different packet sizes.
+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
+])
+NS_CHECK_EXEC([at_ns0], [ping -s 1600 -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
+])
+NS_CHECK_EXEC([at_ns0], [ping -s 3200 -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
+])
+
+dnl There should be 6 tunnel flows in the datapath - 3 in v4 to v6 direction
+dnl for different fragment types (no, first, later) and 3 from v6 to v4.
+dnl Only count flows that are used in the datapath to make sure they are
+dnl actually matching the traffic.
+AT_CHECK([ovs-appctl dpctl/dump-flows | grep -v 'packets:0' | dnl
+ grep "eth_type(0x0800)" | grep -c tun_id], [0], [dnl
+6
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
+
AT_SETUP([datapath - handling of geneve corrupted metadata])
OVS_CHECK_GENEVE()
diff --git a/tests/tunnel.at b/tests/tunnel.at
index e1a16138f..5968f4849 100644
--- a/tests/tunnel.at
+++ b/tests/tunnel.at
@@ -993,13 +993,13 @@ AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
AT_CHECK([ovs-appctl ofproto/trace ovs-dummy
'recirc_id(0),tunnel(tun_id=0x0,src=1.1.1.1,dst=1.1.1.2,ttl=64,geneve({class=0xffff,type=0,len=4,0x11112222}),flags(df|key)),in_port(6081),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'],
[0], [stdout])
AT_CHECK([tail -2 stdout], [0],
- [Megaflow:
recirc_id=0,eth,ip,tun_id=0,tun_src=1.1.1.1,tun_dst=1.1.1.2,tun_tos=0,tun_flags=+df-csum+key,tun_metadata0=0x11112222,tun_metadata1=NP,in_port=1,nw_ecn=0,nw_frag=no
+ [Megaflow:
recirc_id=0,eth,ip,tun_id=0,tun_src=1.1.1.1,tun_dst=1.1.1.2,tun_tos=0,tun_flags=+df-csum+key,tun_metadata0=0x11112222,in_port=1,nw_ecn=0,nw_frag=no
Datapath actions:
set(tunnel(dst=1.1.1.1,ttl=64,tp_dst=6081,geneve({class=0xffff,type=0,len=4,0x11112222}{class=0xffff,type=0x1,len=4,0x55556666}),flags(df))),6081
])
AT_CHECK([ovs-appctl ofproto/trace ovs-dummy
'recirc_id(0),tunnel(tun_id=0x0,src=1.1.1.1,dst=1.1.1.2,ttl=64,geneve({class=0xffff,type=0,len=4,0x33334444}),flags(df|key)),in_port(6081),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'],
[0], [stdout])
AT_CHECK([tail -2 stdout], [0],
- [Megaflow:
recirc_id=0,eth,ip,tun_id=0,tun_src=1.1.1.1,tun_dst=1.1.1.2,tun_tos=0,tun_flags=+df-csum+key,tun_metadata0=0x33334444,tun_metadata1=NP,in_port=1,nw_ecn=0,nw_frag=no
+ [Megaflow:
recirc_id=0,eth,ip,tun_id=0,tun_src=1.1.1.1,tun_dst=1.1.1.2,tun_tos=0,tun_flags=+df-csum+key,tun_metadata0=0x33334444,in_port=1,nw_ecn=0,nw_frag=no
Datapath actions:
set(tunnel(dst=1.1.1.1,ttl=64,tp_dst=6081,geneve({class=0xffff,type=0x1,len=4,0x77778888}),flags(df))),6081
])
@@ -1037,6 +1037,38 @@ AT_CHECK([tail -1 stdout], [0],
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([tunnel - concomitant IPv6 and IPv4 flow based tunnels])
+OVS_VSWITCHD_START([dnl
+ add-port br0 p1 -- set Interface p1 type=vxlan options:key=4 \
+ options:local_ip=flow options:remote_ip=flow ofport_request=1 \
+ -- add-port br0 p2 -- set Interface p2 type=vxlan options:key=6 \
+ options:local_ip=flow options:remote_ip=flow ofport_request=2])
+
+AT_DATA([flows.txt], [dnl
+in_port=1,actions=load:0->tun_src,load:0->tun_dst,set_field:2001:cafe::1->tun_ipv6_src,set_field:fc00::2->tun_ipv6_dst,2
+in_port=2,actions=load:0->tun_ipv6_src,load:0->tun_ipv6_dst,set_field:1.1.1.1->tun_src,set_field:4.4.4.4->tun_dst,1
+])
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
+
+AT_CHECK([ovs-ofctl -OOpenFlow15 add-flows br0 flows.txt])
+
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy \
+
'tunnel(tun_id=4,src=4.4.4.4,dst=1.1.1.1,ttl=64,flags(df|csum|key)),in_port(4789)'],
[0], [stdout])
+AT_CHECK([tail -2 stdout], [0], [dnl
+Megaflow:
recirc_id=0,eth,tun_id=0x4,tun_src=4.4.4.4,tun_dst=1.1.1.1,tun_tos=0,tun_flags=+df+csum+key,in_port=1,dl_type=0x05ff
+Datapath actions:
set(tunnel(tun_id=0x6,ipv6_src=2001:cafe::1,ipv6_dst=fc00::2,ttl=64,tp_dst=4789,flags(df|csum|key))),4789
+])
+
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy \
+
'tunnel(tun_id=6,ipv6_src=fc00::2,ipv6_dst=2001:cafe::1,ttl=64,flags(df|csum|key)),in_port(4789)'],
[0], [stdout])
+AT_CHECK([tail -2 stdout], [0], [dnl
+Megaflow:
recirc_id=0,eth,tun_id=0x6,tun_ipv6_src=fc00::2,tun_ipv6_dst=2001:cafe::1,tun_tos=0,tun_flags=+df+csum+key,in_port=2,dl_type=0x05ff
+Datapath actions:
set(tunnel(tun_id=0x4,src=1.1.1.1,dst=4.4.4.4,ttl=64,tp_dst=4789,flags(df|key))),4789
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([tunnel - concomitant incompatible tunnels on the same port])
OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=vxlan \
options:remote_ip=flow ofport_request=1])
--
2.52.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev