[ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-10-18 Thread Eelco Chaudron
The datapath supports installing wider flows, and OVS relies on
this behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed
to be added.

However, if we try to add a wildcard rule, the installation fails:

# ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
  ipv4(src=1.1.1.1/192.0.0.0,dst=1.1.1.2/192.0.0.0,frag=no)" 2
# ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
  ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.2/0.0.0.0,frag=no)" 2
ovs-vswitchd: updating flow table (File exists)

The reason is that the key used to determine if the flow is already
present in the system uses the original key ANDed with the mask.
This results in the IP address not being part of the (miniflow) key,
i.e., being substituted with an all-zero value. When doing the actual
lookup, this results in the key wrongfully matching the first flow,
and therefore the flow does not get installed. The solution is to use
the unmasked key for the existence check, the same way this is handled
in the userspace datapath.

Signed-off-by: Eelco Chaudron 
---
 lib/dpif-netdev.c|   33 +
 tests/dpif-netdev.at |   14 ++
 2 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index a45b46014..daa00aa2f 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -3321,6 +3321,28 @@ netdev_flow_key_init_masked(struct netdev_flow_key *dst,
 (dst_u64 - miniflow_get_values(&dst->mf)) * 8);
 }
 
+/* Initializes 'dst' as a copy of 'flow'. */
+static inline void
+netdev_flow_key_init(struct netdev_flow_key *key,
+ const struct flow *flow)
+{
+uint64_t *dst = miniflow_values(&key->mf);
+uint32_t hash = 0;
+uint64_t value;
+
+miniflow_map_init(&key->mf, flow);
+miniflow_init(&key->mf, flow);
+
+size_t n = dst - miniflow_get_values(&key->mf);
+
+FLOW_FOR_EACH_IN_MAPS (value, flow, key->mf.map) {
+hash = hash_add64(hash, value);
+}
+
+key->hash = hash_finish(hash, n * 8);
+key->len = netdev_flow_key_size(n);
+}
+
 static inline void
 emc_change_entry(struct emc_entry *ce, struct dp_netdev_flow *flow,
  const struct netdev_flow_key *key)
@@ -4195,7 +4217,7 @@ static int
 dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
 {
 struct dp_netdev *dp = get_dp_netdev(dpif);
-struct netdev_flow_key key, mask;
+struct netdev_flow_key key;
 struct dp_netdev_pmd_thread *pmd;
 struct match match;
 ovs_u128 ufid;
@@ -4244,9 +4266,12 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct 
dpif_flow_put *put)
 
 /* Must produce a netdev_flow_key for lookup.
  * Use the same method as employed to create the key when adding
- * the flow to the dplcs to make sure they match. */
-netdev_flow_mask_init(&mask, &match);
-netdev_flow_key_init_masked(&key, &match.flow, &mask);
+ * the flow to the dplcs to make sure they match.
+ * We need to put in the unmasked key as flow_put_on_pmd() will first try
+ * to see if an entry exists doing a packet type lookup. As masked-out
+ * fields are interpreted as zeros, they could falsely match a wider IP
+ * address mask. Installation of the flow will use the match variable. */
+netdev_flow_key_init(&key, &match.flow);
 
 if (put->pmd_id == PMD_ID_NULL) {
 if (cmap_count(&dp->poll_threads) == 0) {
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 3179e1645..32054c52e 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -636,6 +636,20 @@ OVS_VSWITCHD_STOP(["/flow: in_port is not an exact match/d
 /failed to put/d"])
 AT_CLEANUP
 
+AT_SETUP([dpif-netdev - check dpctl/add-flow wider ip match])
+OVS_VSWITCHD_START(
+  [add-port br0 p1 \
+   -- set interface p1 type=dummy options:pstream=punix:$OVS_RUNDIR/p0.sock \
+   -- set bridge br0 datapath-type=dummy])
+
+AT_CHECK([ovs-appctl revalidator/pause])
+AT_CHECK([ovs-appctl dpctl/add-flow 
"in_port(1),eth_type(0x0800),ipv4(src=0.0.0.0/192.0.0.0,dst=0.0.0.0/192.0.0.0,frag=no)"
 "3"])
+AT_CHECK([ovs-appctl dpctl/add-flow 
"in_port(1),eth_type(0x0800),ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.1/0.0.0.0,frag=no)"
 "3"])
+AT_CHECK([ovs-appctl revalidator/resume])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 # SEND_UDP_PKTS([p_name], [p_ofport])
 #
 # Sends 128 packets to port 'p_name' with different UDP destination ports.

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


Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-10-31 Thread Eelco Chaudron


On 20 Oct 2022, at 9:43, Eelco Chaudron wrote:

> On 19 Oct 2022, at 11:31, Eelco Chaudron wrote:
>
>> On 19 Oct 2022, at 1:08, Ilya Maximets wrote:
>>
>>> On 10/19/22 00:07, Jan Scheurich wrote:
 Hi guys,

 I am afraid that commit is too long ago that would remember any details 
 that caused us to change the code in beb75a40fdc2 ("userspace: Switching 
 of L3 packets in L2 pipeline"). What I vaguely remember was that I 
 couldn’t comprehend the original code and it was not working correctly in 
 some of the cases we needed/tested. But perhaps the changes we introduced 
 also had corner cases we didn't consider.
>>
>> First of all, thanks Ilya, for following this up while I was asleep and 
>> digging into who made the change :) I was trying to get the patch out 
>> quickly for some feedback and forgot this step :(
>>
>> My change does not seem to cause any failures in the test cases you changed. 
>> But it looks like you did not have any specific test cases for the problem 
>> you were trying to solve.
>
> I took another look at the original code before Jan’s patch and replaced 
> netdev_flow_key_init() with the original function, 
> netdev_flow_key_from_flow(), from before commit beb75a40fdc2 I see two tunnel 
> cases fail:
>
> 794: tunnel_push_pop - actionFAILED 
> (tunnel-push-pop.at:660)
> 803: tunnel_push_pop_ipv6 - action   FAILED 
> (tunnel-push-pop-ipv6.at:528)
>
> This is due to the odd way of transforming the key to a packet and back to a 
> flow, where my implementation uses the key as supplied to create the 
> miniflow, which seems the right approach to me.
>
 Question though:

>> The datapath supports installing wider flows, and OVS relies on this
>> behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
>> dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
>> ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed to be
>> added.

 That sounds strange to me. I always believed the datapath only supports 
 non-overlapping flows, i.e. a packet can match at most one datapath flow. 
 Only with that pre-requisite the dpcls classifier can work without 
 priorities. Have I been wrong in this? What would be the semantics of 
 adding a wider flow to the datapath? To my knowledge there is no guarantee 
 that the dpcls subtables are visited in any specific order that would 
 honor the mask width. And the first match will win.
>>>
>>> Yes, you're right that classifier is always choosing the first match,
>>> so it can not work correctly in general if there are multiple flows
>>> that match the same packet.
>>>
>>> What this change is trying to achieve, IIUC, is a bit of an odd case
>>> where we have multiple flows that match on the same packet, but one
>>> of them is a superset of another.  And these flows should have identical
>>> actions, of course, if we want the system to still function correctly.
>>> (Should we actually compare actions to be identical and matches to
>>> be a superset?  If actions are not correct, the flow must be soon
>>> revalidated, so that might not be a big concern.  If the mask is not
>>> a superset, the flow_wildcards_has_extra() check should catch that
>>> and revalidate one of them, so should not be a problem either, I guess.
>>> But we need to check that flow mods are always using ufid generated
>>> from a full key and not updating actions on an incorrect flow.  Same
>>> for flow deletions, we need to be sure that while removing flows we're
>>> actually finding and removing the one that we need.)
>>
>> Let me take another look at this delete code, as the mod code uses the same 
>> path as add, so that should be taken care of.
>
> I did some experiments and reviewed the code, and we should be good with the 
> delete part in both userspace and the kernel (with both the ufid and key 
> delete methods).
>
>>> We should actually re-check the classier and the caches to make sure
>>> that there will not be any surprises and they are capable of dealing
>>> with possible duplicates...
>>
>> I did take a brief look at it and could not find any red flags, but if you 
>> have something specific in mind please let me know and I recheck.
>>
>> Also as the current PMD datapath code already uses it in this way (see 
>> below), we might have caught issues earlier if the exist.
>>
 Please clarify this. And in which sense OVS relies on this behavior?
>>>
>>> The problem is a revalidation process.  During revalidation OVS removes
>>> too generic flows from the datapath to avoid incorrect matches, but
>>> allows too narrow flows to stay in the datapath to avoid the dataplane
>>> disruption and also to avoid constant flow deletions if the datapath
>>> ignores wildcards on certain fields/bits.  See flow_wildcards_has_extra()
>>> check in the revalidate_ukey__() function.
>>>
>>> The problem here is that we have too narrow flow installed and now

Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-11-25 Thread Adrian Moreno



On 10/18/22 18:42, Eelco Chaudron wrote:

The datapath supports installing wider flows, and OVS relies on
this behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed
to be added.

However, if we try to add a wildcard rule, the installation fails:

# ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
   ipv4(src=1.1.1.1/192.0.0.0,dst=1.1.1.2/192.0.0.0,frag=no)" 2
# ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
   ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.2/0.0.0.0,frag=no)" 2
ovs-vswitchd: updating flow table (File exists)

The reason is that the key used to determine if the flow is already
present in the system uses the original key ANDed with the mask.
This results in the IP address not being part of the (miniflow) key,
i.e., being substituted with an all-zero value. When doing the actual
lookup, this results in the key wrongfully matching the first flow,
and therefore the flow does not get installed. The solution is to use
the unmasked key for the existence check, the same way this is handled
in the userspace datapath.

Signed-off-by: Eelco Chaudron 
---
  lib/dpif-netdev.c|   33 +
  tests/dpif-netdev.at |   14 ++
  2 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index a45b46014..daa00aa2f 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -3321,6 +3321,28 @@ netdev_flow_key_init_masked(struct netdev_flow_key *dst,
  (dst_u64 - miniflow_get_values(&dst->mf)) * 8);
  }
  
+/* Initializes 'dst' as a copy of 'flow'. */


nit: s/dst/key/


+static inline void
+netdev_flow_key_init(struct netdev_flow_key *key,
+ const struct flow *flow)
+{
+uint64_t *dst = miniflow_values(&key->mf);
+uint32_t hash = 0;
+uint64_t value;
+
+miniflow_map_init(&key->mf, flow);
+miniflow_init(&key->mf, flow);
+
+size_t n = dst - miniflow_get_values(&key->mf);
+
+FLOW_FOR_EACH_IN_MAPS (value, flow, key->mf.map) {
+hash = hash_add64(hash, value);
+}
+
+key->hash = hash_finish(hash, n * 8);
+key->len = netdev_flow_key_size(n);
+}
+
  static inline void
  emc_change_entry(struct emc_entry *ce, struct dp_netdev_flow *flow,
   const struct netdev_flow_key *key)
@@ -4195,7 +4217,7 @@ static int
  dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
  {
  struct dp_netdev *dp = get_dp_netdev(dpif);
-struct netdev_flow_key key, mask;
+struct netdev_flow_key key;
  struct dp_netdev_pmd_thread *pmd;
  struct match match;
  ovs_u128 ufid;
@@ -4244,9 +4266,12 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct 
dpif_flow_put *put)
  
  /* Must produce a netdev_flow_key for lookup.

   * Use the same method as employed to create the key when adding
- * the flow to the dplcs to make sure they match. */
-netdev_flow_mask_init(&mask, &match);
-netdev_flow_key_init_masked(&key, &match.flow, &mask);
+ * the flow to the dplcs to make sure they match.
+ * We need to put in the unmasked key as flow_put_on_pmd() will first try
+ * to see if an entry exists doing a packet type lookup. As masked-out
+ * fields are interpreted as zeros, they could falsely match a wider IP
+ * address mask. Installation of the flow will use the match variable. */
+netdev_flow_key_init(&key, &match.flow);
  
  if (put->pmd_id == PMD_ID_NULL) {

  if (cmap_count(&dp->poll_threads) == 0) {
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 3179e1645..32054c52e 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -636,6 +636,20 @@ OVS_VSWITCHD_STOP(["/flow: in_port is not an exact match/d
  /failed to put/d"])
  AT_CLEANUP
  
+AT_SETUP([dpif-netdev - check dpctl/add-flow wider ip match])

+OVS_VSWITCHD_START(
+  [add-port br0 p1 \
+   -- set interface p1 type=dummy options:pstream=punix:$OVS_RUNDIR/p0.sock \
+   -- set bridge br0 datapath-type=dummy])
+
+AT_CHECK([ovs-appctl revalidator/pause])
+AT_CHECK([ovs-appctl dpctl/add-flow 
"in_port(1),eth_type(0x0800),ipv4(src=0.0.0.0/192.0.0.0,dst=0.0.0.0/192.0.0.0,frag=no)" 
"3"])
+AT_CHECK([ovs-appctl dpctl/add-flow 
"in_port(1),eth_type(0x0800),ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.1/0.0.0.0,frag=no)" 
"3"])
+AT_CHECK([ovs-appctl revalidator/resume])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
  # SEND_UDP_PKTS([p_name], [p_ofport])
  #
  # Sends 128 packets to port 'p_name' with different UDP destination ports.

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



--
Adrián Moreno

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


Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-11-28 Thread Eelco Chaudron


On 25 Nov 2022, at 17:05, Adrian Moreno wrote:

> On 10/18/22 18:42, Eelco Chaudron wrote:
>> The datapath supports installing wider flows, and OVS relies on
>> this behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
>> dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
>> ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed
>> to be added.
>>
>> However, if we try to add a wildcard rule, the installation fails:
>>
>> # ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
>>ipv4(src=1.1.1.1/192.0.0.0,dst=1.1.1.2/192.0.0.0,frag=no)" 2
>> # ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
>>ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.2/0.0.0.0,frag=no)" 2
>> ovs-vswitchd: updating flow table (File exists)
>>
>> The reason is that the key used to determine if the flow is already
>> present in the system uses the original key ANDed with the mask.
>> This results in the IP address not being part of the (miniflow) key,
>> i.e., being substituted with an all-zero value. When doing the actual
>> lookup, this results in the key wrongfully matching the first flow,
>> and therefore the flow does not get installed. The solution is to use
>> the unmasked key for the existence check, the same way this is handled
>> in the userspace datapath.
>>
>> Signed-off-by: Eelco Chaudron 
>> ---
>>   lib/dpif-netdev.c|   33 +
>>   tests/dpif-netdev.at |   14 ++
>>   2 files changed, 43 insertions(+), 4 deletions(-)
>>
>> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
>> index a45b46014..daa00aa2f 100644
>> --- a/lib/dpif-netdev.c
>> +++ b/lib/dpif-netdev.c
>> @@ -3321,6 +3321,28 @@ netdev_flow_key_init_masked(struct netdev_flow_key 
>> *dst,
>>   (dst_u64 - miniflow_get_values(&dst->mf)) * 8);
>>   }
>>  +/* Initializes 'dst' as a copy of 'flow'. */
>
> nit: s/dst/key/

Hi Adrian,

Thanks for the review. There was already a v2 with an updated commit message, 
however, I just sent out a v3 with this change included.

//Eelco


>> +static inline void
>> +netdev_flow_key_init(struct netdev_flow_key *key,
>> + const struct flow *flow)
>> +{
>> +uint64_t *dst = miniflow_values(&key->mf);
>> +uint32_t hash = 0;
>> +uint64_t value;
>> +
>> +miniflow_map_init(&key->mf, flow);
>> +miniflow_init(&key->mf, flow);
>> +
>> +size_t n = dst - miniflow_get_values(&key->mf);
>> +
>> +FLOW_FOR_EACH_IN_MAPS (value, flow, key->mf.map) {
>> +hash = hash_add64(hash, value);
>> +}
>> +
>> +key->hash = hash_finish(hash, n * 8);
>> +key->len = netdev_flow_key_size(n);
>> +}
>> +
>>   static inline void
>>   emc_change_entry(struct emc_entry *ce, struct dp_netdev_flow *flow,
>>const struct netdev_flow_key *key)
>> @@ -4195,7 +4217,7 @@ static int
>>   dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
>>   {
>>   struct dp_netdev *dp = get_dp_netdev(dpif);
>> -struct netdev_flow_key key, mask;
>> +struct netdev_flow_key key;
>>   struct dp_netdev_pmd_thread *pmd;
>>   struct match match;
>>   ovs_u128 ufid;
>> @@ -4244,9 +4266,12 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct 
>> dpif_flow_put *put)
>>/* Must produce a netdev_flow_key for lookup.
>>* Use the same method as employed to create the key when adding
>> - * the flow to the dplcs to make sure they match. */
>> -netdev_flow_mask_init(&mask, &match);
>> -netdev_flow_key_init_masked(&key, &match.flow, &mask);
>> + * the flow to the dplcs to make sure they match.
>> + * We need to put in the unmasked key as flow_put_on_pmd() will first 
>> try
>> + * to see if an entry exists doing a packet type lookup. As masked-out
>> + * fields are interpreted as zeros, they could falsely match a wider IP
>> + * address mask. Installation of the flow will use the match variable. 
>> */
>> +netdev_flow_key_init(&key, &match.flow);
>>if (put->pmd_id == PMD_ID_NULL) {
>>   if (cmap_count(&dp->poll_threads) == 0) {
>> diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
>> index 3179e1645..32054c52e 100644
>> --- a/tests/dpif-netdev.at
>> +++ b/tests/dpif-netdev.at
>> @@ -636,6 +636,20 @@ OVS_VSWITCHD_STOP(["/flow: in_port is not an exact 
>> match/d
>>   /failed to put/d"])
>>   AT_CLEANUP
>>  +AT_SETUP([dpif-netdev - check dpctl/add-flow wider ip match])
>> +OVS_VSWITCHD_START(
>> +  [add-port br0 p1 \
>> +   -- set interface p1 type=dummy options:pstream=punix:$OVS_RUNDIR/p0.sock 
>> \
>> +   -- set bridge br0 datapath-type=dummy])
>> +
>> +AT_CHECK([ovs-appctl revalidator/pause])
>> +AT_CHECK([ovs-appctl dpctl/add-flow 
>> "in_port(1),eth_type(0x0800),ipv4(src=0.0.0.0/192.0.0.0,dst=0.0.0.0/192.0.0.0,frag=no)"
>>  "3"])
>> +AT_CHECK([ovs-appctl dpctl/add-flow 
>> "in_port(1),eth_type(0x0800),ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.1/0.0.0.0,frag=no)"
>>  "3"])
>

Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-10-18 Thread Ilya Maximets
On 10/18/22 18:42, Eelco Chaudron wrote:
> The datapath supports installing wider flows, and OVS relies on
> this behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
> dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
> ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed
> to be added.
> 
> However, if we try to add a wildcard rule, the installation fails:
> 
> # ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
>   ipv4(src=1.1.1.1/192.0.0.0,dst=1.1.1.2/192.0.0.0,frag=no)" 2
> # ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
>   ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.2/0.0.0.0,frag=no)" 2
> ovs-vswitchd: updating flow table (File exists)
> 
> The reason is that the key used to determine if the flow is already
> present in the system uses the original key ANDed with the mask.
> This results in the IP address not being part of the (miniflow) key,
> i.e., being substituted with an all-zero value. When doing the actual
> lookup, this results in the key wrongfully matching the first flow,
> and therefore the flow does not get installed. The solution is to use
> the unmasked key for the existence check, the same way this is handled
> in the userspace datapath.
> 
> Signed-off-by: Eelco Chaudron 
> ---
>  lib/dpif-netdev.c|   33 +
>  tests/dpif-netdev.at |   14 ++
>  2 files changed, 43 insertions(+), 4 deletions(-)
> 
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index a45b46014..daa00aa2f 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -3321,6 +3321,28 @@ netdev_flow_key_init_masked(struct netdev_flow_key 
> *dst,
>  (dst_u64 - miniflow_get_values(&dst->mf)) * 8);
>  }
>  
> +/* Initializes 'dst' as a copy of 'flow'. */
> +static inline void
> +netdev_flow_key_init(struct netdev_flow_key *key,
> + const struct flow *flow)
> +{
> +uint64_t *dst = miniflow_values(&key->mf);
> +uint32_t hash = 0;
> +uint64_t value;
> +
> +miniflow_map_init(&key->mf, flow);
> +miniflow_init(&key->mf, flow);
> +
> +size_t n = dst - miniflow_get_values(&key->mf);
> +
> +FLOW_FOR_EACH_IN_MAPS (value, flow, key->mf.map) {
> +hash = hash_add64(hash, value);
> +}
> +
> +key->hash = hash_finish(hash, n * 8);
> +key->len = netdev_flow_key_size(n);
> +}
> +
>  static inline void
>  emc_change_entry(struct emc_entry *ce, struct dp_netdev_flow *flow,
>   const struct netdev_flow_key *key)
> @@ -4195,7 +4217,7 @@ static int
>  dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
>  {
>  struct dp_netdev *dp = get_dp_netdev(dpif);
> -struct netdev_flow_key key, mask;
> +struct netdev_flow_key key;
>  struct dp_netdev_pmd_thread *pmd;
>  struct match match;
>  ovs_u128 ufid;
> @@ -4244,9 +4266,12 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct 
> dpif_flow_put *put)
>  
>  /* Must produce a netdev_flow_key for lookup.
>   * Use the same method as employed to create the key when adding
> - * the flow to the dplcs to make sure they match. */
> -netdev_flow_mask_init(&mask, &match);
> -netdev_flow_key_init_masked(&key, &match.flow, &mask);
> + * the flow to the dplcs to make sure they match.
> + * We need to put in the unmasked key as flow_put_on_pmd() will first try
> + * to see if an entry exists doing a packet type lookup. As masked-out
> + * fields are interpreted as zeros, they could falsely match a wider IP
> + * address mask. Installation of the flow will use the match variable. */
> +netdev_flow_key_init(&key, &match.flow);


Hi, Eelco.  Thanks for the patch.
It is indeed an issue that we can lookup the incorrect flow
due to new mask being wider, but I'm wondering if that will
cause any other issues for L3 traffic as you seem to mainly
revert part of this previous commit:
  beb75a40fdc2 ("userspace: Switching of L3 packets in L2 pipeline")
The commit message was tlking about something not being
properly masked...

CC: Jan, maybe you remember some details about that?

Also, the "Use the same method" doesn't make a lot of sense
with the change.

Best regards, Ilya Maximets.

>  
>  if (put->pmd_id == PMD_ID_NULL) {
>  if (cmap_count(&dp->poll_threads) == 0) {
> diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
> index 3179e1645..32054c52e 100644
> --- a/tests/dpif-netdev.at
> +++ b/tests/dpif-netdev.at
> @@ -636,6 +636,20 @@ OVS_VSWITCHD_STOP(["/flow: in_port is not an exact 
> match/d
>  /failed to put/d"])
>  AT_CLEANUP
>  
> +AT_SETUP([dpif-netdev - check dpctl/add-flow wider ip match])
> +OVS_VSWITCHD_START(
> +  [add-port br0 p1 \
> +   -- set interface p1 type=dummy options:pstream=punix:$OVS_RUNDIR/p0.sock \
> +   -- set bridge br0 datapath-type=dummy])
> +
> +AT_CHECK([ovs-appctl revalidator/pause])
> +AT_CHECK([ovs-appctl dpctl/add-flow 
> "in_port(1),eth_type(0x0800),ipv

Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-10-18 Thread Jan Scheurich via dev
Hi guys,

I am afraid that commit is too long ago that would remember any details that 
caused us to change the code in beb75a40fdc2 ("userspace: Switching of L3 
packets in L2 pipeline"). What I vaguely remember was that I couldn’t 
comprehend the original code and it was not working correctly in some of the 
cases we needed/tested. But perhaps the changes we introduced also had corner 
cases we didn't consider. 

Question though: 

> > The datapath supports installing wider flows, and OVS relies on this
> > behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
> > dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
> > ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed to be
> > added.

That sounds strange to me. I always believed the datapath only supports 
non-overlapping flows, i.e. a packet can match at most one datapath flow. Only 
with that pre-requisite the dpcls classifier can work without priorities. Have 
I been wrong in this? What would be the semantics of adding a wider flow to the 
datapath? To my knowledge there is no guarantee that the dpcls subtables are 
visited in any specific order that would honor the mask width. And the first 
match will win.

Please clarify this. And in which sense OVS relies on this behavior?

BR, Jan

> -Original Message-
> From: Ilya Maximets 
> Sent: Tuesday, 18 October 2022 21:40
> To: Eelco Chaudron ; d...@openvswitch.org
> Cc: i.maxim...@ovn.org; Jan Scheurich 
> Subject: Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding
> datapath flows.
> 
> On 10/18/22 18:42, Eelco Chaudron wrote:
> > The datapath supports installing wider flows, and OVS relies on this
> > behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
> > dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
> > ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed to be
> > added.
> >
> > However, if we try to add a wildcard rule, the installation fails:
> >
> > # ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
> >   ipv4(src=1.1.1.1/192.0.0.0,dst=1.1.1.2/192.0.0.0,frag=no)" 2 #
> > ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
> >   ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.2/0.0.0.0,frag=no)" 2
> > ovs-vswitchd: updating flow table (File exists)
> >
> > The reason is that the key used to determine if the flow is already
> > present in the system uses the original key ANDed with the mask.
> > This results in the IP address not being part of the (miniflow) key,
> > i.e., being substituted with an all-zero value. When doing the actual
> > lookup, this results in the key wrongfully matching the first flow,
> > and therefore the flow does not get installed. The solution is to use
> > the unmasked key for the existence check, the same way this is handled
> > in the userspace datapath.
> >
> > Signed-off-by: Eelco Chaudron 
> > ---
> >  lib/dpif-netdev.c|   33 +
> >  tests/dpif-netdev.at |   14 ++
> >  2 files changed, 43 insertions(+), 4 deletions(-)
> >
> > diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index
> > a45b46014..daa00aa2f 100644
> > --- a/lib/dpif-netdev.c
> > +++ b/lib/dpif-netdev.c
> > @@ -3321,6 +3321,28 @@ netdev_flow_key_init_masked(struct
> netdev_flow_key *dst,
> >  (dst_u64 - miniflow_get_values(&dst->mf))
> > * 8);  }
> >
> > +/* Initializes 'dst' as a copy of 'flow'. */ static inline void
> > +netdev_flow_key_init(struct netdev_flow_key *key,
> > + const struct flow *flow) {
> > +uint64_t *dst = miniflow_values(&key->mf);
> > +uint32_t hash = 0;
> > +uint64_t value;
> > +
> > +miniflow_map_init(&key->mf, flow);
> > +miniflow_init(&key->mf, flow);
> > +
> > +size_t n = dst - miniflow_get_values(&key->mf);
> > +
> > +FLOW_FOR_EACH_IN_MAPS (value, flow, key->mf.map) {
> > +hash = hash_add64(hash, value);
> > +}
> > +
> > +key->hash = hash_finish(hash, n * 8);
> > +key->len = netdev_flow_key_size(n); }
> > +
> >  static inline void
> >  emc_change_entry(struct emc_entry *ce, struct dp_netdev_flow *flow,
> >   const struct netdev_flow_key *key) @@ -4195,7
> > +4217,7 @@ static int  dpif_netdev_flow_put(struct dpif *dpif, const
> > struct dpif_flow_put *put)  {
> >  struct dp_netdev *dp = get_dp_netdev(dpif);
> > -struct netdev_flow_key key, mask;
> > +struct netdev_flow

Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-10-18 Thread Ilya Maximets
On 10/19/22 00:07, Jan Scheurich wrote:
> Hi guys,
> 
> I am afraid that commit is too long ago that would remember any details that 
> caused us to change the code in beb75a40fdc2 ("userspace: Switching of L3 
> packets in L2 pipeline"). What I vaguely remember was that I couldn’t 
> comprehend the original code and it was not working correctly in some of the 
> cases we needed/tested. But perhaps the changes we introduced also had corner 
> cases we didn't consider. 
> 
> Question though: 
> 
>>> The datapath supports installing wider flows, and OVS relies on this
>>> behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
>>> dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
>>> ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed to be
>>> added.
> 
> That sounds strange to me. I always believed the datapath only supports 
> non-overlapping flows, i.e. a packet can match at most one datapath flow. 
> Only with that pre-requisite the dpcls classifier can work without 
> priorities. Have I been wrong in this? What would be the semantics of adding 
> a wider flow to the datapath? To my knowledge there is no guarantee that the 
> dpcls subtables are visited in any specific order that would honor the mask 
> width. And the first match will win.

Yes, you're right that classifier is always choosing the first match,
so it can not work correctly in general if there are multiple flows
that match the same packet.

What this change is trying to achieve, IIUC, is a bit of an odd case
where we have multiple flows that match on the same packet, but one
of them is a superset of another.  And these flows should have identical
actions, of course, if we want the system to still function correctly.
(Should we actually compare actions to be identical and matches to
be a superset?  If actions are not correct, the flow must be soon
revalidated, so that might not be a big concern.  If the mask is not
a superset, the flow_wildcards_has_extra() check should catch that
and revalidate one of them, so should not be a problem either, I guess.
But we need to check that flow mods are always using ufid generated
from a full key and not updating actions on an incorrect flow.  Same
for flow deletions, we need to be sure that while removing flows we're
actually finding and removing the one that we need.)

We should actually re-check the classier and the caches to make sure
that there will not be any surprises and they are capable of dealing
with possible duplicates...

> Please clarify this. And in which sense OVS relies on this behavior?

The problem is a revalidation process.  During revalidation OVS removes
too generic flows from the datapath to avoid incorrect matches, but
allows too narrow flows to stay in the datapath to avoid the dataplane
disruption and also to avoid constant flow deletions if the datapath
ignores wildcards on certain fields/bits.  See flow_wildcards_has_extra()
check in the revalidate_ukey__() function.

The problem here is that we have too narrow flow installed and now
OpenFlow rules got changed, so the actual flow should be more generic.
Revalidators will not remove the narrow flow, we will eventually get
an upcall on the packet that doesn't match the narrow flow, but we will
not be able to install a more generic flow because after masking with
the new wider mask the key matches on the narrow flow, so we get EEXIST.

This particular scenario is probably not an actual problem for the
userspace datapath, because upcalls are handled in a different code
path, so the wider flow should be installed just fine alongside with
the too narrow flow (We still need to check that all parts of the
code can handle this).  So, it is only for a "slow" path here where
upper layers may decide to add a wider flow via dpif_flow_put(), but
will fail.

It's more of an issue for a kernel datapath where we seem to have
a similar code inside of a kernel.

In any case, if the "fast" upcall path is able to add such flows
(I didn't verify that), the "slow" dpif_flow_put() case should work
the same way to avoid confusion and potential more serious issues in
the future.

Best regards, Ilya Maximets.

> 
> BR, Jan
> 
>> -Original Message-
>> From: Ilya Maximets 
>> Sent: Tuesday, 18 October 2022 21:40
>> To: Eelco Chaudron ; d...@openvswitch.org
>> Cc: i.maxim...@ovn.org; Jan Scheurich 
>> Subject: Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding
>> datapath flows.
>>
>> On 10/18/22 18:42, Eelco Chaudron wrote:
>>> The datapath supports installing wider flows, and OVS relies on this
>>> behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
>>> dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
>>> ipv4(src=192.1.1.1/128

Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-10-19 Thread Eelco Chaudron
gt; upper layers may decide to add a wider flow via dpif_flow_put(), but
> will fail.

Thats why I had the remark in the “the same way this is handled the userspace 
datapath.” in the commit message.
So the dpif_netdev_flow_put() seems to be only used in the dummy datapath used 
by all the test cases, but the actual dpif-netdev path seems to use the 
handle_packet_upcall().

Where in handle_packet_upcall() it will do a dp_netdev_pmd_lookup_flow() using 
the unmasked packet key (like my change below), and then does the 
dp_netdev_flow_add() using the masked ukey.

Jan, I’m wondering why you only changed the dpif_netdev_flow_put() and not the 
PMD path, as this is probably the path used in real life. Or is there some odd 
kernel tunnel combination that is causing this path to be utilized? If so, do 
you have a test case you can run on this change? The default dp make checks all 
pass.

> It's more of an issue for a kernel datapath where we seem to have
> a similar code inside of a kernel.

Yes, I do have a kernel patch ready also for this. But I waited because I 
wanted to get the userspace patch in/discussed first (before someone in the 
kernel does a quick ack and commit ;)

> In any case, if the "fast" upcall path is able to add such flows
> (I didn't verify that), the "slow" dpif_flow_put() case should work
> the same way to avoid confusion and potential more serious issues in
> the future.

See above, I can confirm the fast path works similar to this change, i.e. using 
the packet ukey.

> Best regards, Ilya Maximets.
>
>>
>> BR, Jan
>>
>>> -Original Message-
>>> From: Ilya Maximets 
>>> Sent: Tuesday, 18 October 2022 21:40
>>> To: Eelco Chaudron ; d...@openvswitch.org
>>> Cc: i.maxim...@ovn.org; Jan Scheurich 
>>> Subject: Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding
>>> datapath flows.
>>>
>>> On 10/18/22 18:42, Eelco Chaudron wrote:
>>>> The datapath supports installing wider flows, and OVS relies on this
>>>> behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
>>>> dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
>>>> ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed to be
>>>> added.
>>>>
>>>> However, if we try to add a wildcard rule, the installation fails:
>>>>
>>>> # ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
>>>>   ipv4(src=1.1.1.1/192.0.0.0,dst=1.1.1.2/192.0.0.0,frag=no)" 2 #
>>>> ovs-appctl dpctl/add-flow system@myDP "in_port(1),eth_type(0x0800), \
>>>>   ipv4(src=192.1.1.1/0.0.0.0,dst=49.1.1.2/0.0.0.0,frag=no)" 2
>>>> ovs-vswitchd: updating flow table (File exists)
>>>>
>>>> The reason is that the key used to determine if the flow is already
>>>> present in the system uses the original key ANDed with the mask.
>>>> This results in the IP address not being part of the (miniflow) key,
>>>> i.e., being substituted with an all-zero value. When doing the actual
>>>> lookup, this results in the key wrongfully matching the first flow,
>>>> and therefore the flow does not get installed. The solution is to use
>>>> the unmasked key for the existence check, the same way this is handled
>>>> in the userspace datapath.
>>>>
>>>> Signed-off-by: Eelco Chaudron 
>>>> ---
>>>>  lib/dpif-netdev.c|   33 +
>>>>  tests/dpif-netdev.at |   14 ++
>>>>  2 files changed, 43 insertions(+), 4 deletions(-)
>>>>
>>>> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index
>>>> a45b46014..daa00aa2f 100644
>>>> --- a/lib/dpif-netdev.c
>>>> +++ b/lib/dpif-netdev.c
>>>> @@ -3321,6 +3321,28 @@ netdev_flow_key_init_masked(struct
>>> netdev_flow_key *dst,
>>>>  (dst_u64 - miniflow_get_values(&dst->mf))
>>>> * 8);  }
>>>>
>>>> +/* Initializes 'dst' as a copy of 'flow'. */ static inline void
>>>> +netdev_flow_key_init(struct netdev_flow_key *key,
>>>> + const struct flow *flow) {
>>>> +uint64_t *dst = miniflow_values(&key->mf);
>>>> +uint32_t hash = 0;
>>>> +uint64_t value;
>>>> +
>>>> +miniflow_map_init(&key->mf, flow);
>>>> +miniflow_init(&key->mf, flow);
>>>> +
>>>> +size_t n = dst - miniflow_get_values(&key->mf);
>

Re: [ovs-dev] [PATCH] dpif-netdev: Use unmasked key when adding datapath flows.

2022-10-20 Thread Eelco Chaudron


On 19 Oct 2022, at 11:31, Eelco Chaudron wrote:

> On 19 Oct 2022, at 1:08, Ilya Maximets wrote:
>
>> On 10/19/22 00:07, Jan Scheurich wrote:
>>> Hi guys,
>>>
>>> I am afraid that commit is too long ago that would remember any details 
>>> that caused us to change the code in beb75a40fdc2 ("userspace: Switching of 
>>> L3 packets in L2 pipeline"). What I vaguely remember was that I couldn’t 
>>> comprehend the original code and it was not working correctly in some of 
>>> the cases we needed/tested. But perhaps the changes we introduced also had 
>>> corner cases we didn't consider.
>
> First of all, thanks Ilya, for following this up while I was asleep and 
> digging into who made the change :) I was trying to get the patch out quickly 
> for some feedback and forgot this step :(
>
> My change does not seem to cause any failures in the test cases you changed. 
> But it looks like you did not have any specific test cases for the problem 
> you were trying to solve.

I took another look at the original code before Jan’s patch and replaced 
netdev_flow_key_init() with the original function, netdev_flow_key_from_flow(), 
from before commit beb75a40fdc2 I see two tunnel cases fail:

794: tunnel_push_pop - actionFAILED 
(tunnel-push-pop.at:660)
803: tunnel_push_pop_ipv6 - action   FAILED 
(tunnel-push-pop-ipv6.at:528)

This is due to the odd way of transforming the key to a packet and back to a 
flow, where my implementation uses the key as supplied to create the miniflow, 
which seems the right approach to me.

>>> Question though:
>>>
> The datapath supports installing wider flows, and OVS relies on this
> behavior. For example if ipv4(src=1.1.1.1/192.0.0.0,
> dst=1.1.1.2/192.0.0.0) exists, a wider flow (smaller mask) of
> ipv4(src=192.1.1.1/128.0.0.0,dst=192.1.1.2/128.0.0.0) is allowed to be
> added.
>>>
>>> That sounds strange to me. I always believed the datapath only supports 
>>> non-overlapping flows, i.e. a packet can match at most one datapath flow. 
>>> Only with that pre-requisite the dpcls classifier can work without 
>>> priorities. Have I been wrong in this? What would be the semantics of 
>>> adding a wider flow to the datapath? To my knowledge there is no guarantee 
>>> that the dpcls subtables are visited in any specific order that would honor 
>>> the mask width. And the first match will win.
>>
>> Yes, you're right that classifier is always choosing the first match,
>> so it can not work correctly in general if there are multiple flows
>> that match the same packet.
>>
>> What this change is trying to achieve, IIUC, is a bit of an odd case
>> where we have multiple flows that match on the same packet, but one
>> of them is a superset of another.  And these flows should have identical
>> actions, of course, if we want the system to still function correctly.
>> (Should we actually compare actions to be identical and matches to
>> be a superset?  If actions are not correct, the flow must be soon
>> revalidated, so that might not be a big concern.  If the mask is not
>> a superset, the flow_wildcards_has_extra() check should catch that
>> and revalidate one of them, so should not be a problem either, I guess.
>> But we need to check that flow mods are always using ufid generated
>> from a full key and not updating actions on an incorrect flow.  Same
>> for flow deletions, we need to be sure that while removing flows we're
>> actually finding and removing the one that we need.)
>
> Let me take another look at this delete code, as the mod code uses the same 
> path as add, so that should be taken care of.

I did some experiments and reviewed the code, and we should be good with the 
delete part in both userspace and the kernel (with both the ufid and key delete 
methods).

>> We should actually re-check the classier and the caches to make sure
>> that there will not be any surprises and they are capable of dealing
>> with possible duplicates...
>
> I did take a brief look at it and could not find any red flags, but if you 
> have something specific in mind please let me know and I recheck.
>
> Also as the current PMD datapath code already uses it in this way (see 
> below), we might have caught issues earlier if the exist.
>
>>> Please clarify this. And in which sense OVS relies on this behavior?
>>
>> The problem is a revalidation process.  During revalidation OVS removes
>> too generic flows from the datapath to avoid incorrect matches, but
>> allows too narrow flows to stay in the datapath to avoid the dataplane
>> disruption and also to avoid constant flow deletions if the datapath
>> ignores wildcards on certain fields/bits.  See flow_wildcards_has_extra()
>> check in the revalidate_ukey__() function.
>>
>> The problem here is that we have too narrow flow installed and now
>> OpenFlow rules got changed, so the actual flow should be more generic.
>> Revalidators will not remove the narrow flow, we will eventually get
>> an upcall on