diff mbox series

[ovs-dev,v2] expr: crush the result of a sorted OR expression.

Message ID 20210506204922.2287892-1-mmichels@redhat.com
State Accepted
Headers show
Series [ovs-dev,v2] expr: crush the result of a sorted OR expression. | expand

Commit Message

Mark Michelson May 6, 2021, 8:49 p.m. UTC
ACLs and logical router policies leave the user open to crafting
creative matches, such as this one:

(ip4.dst == 172.27.0.65/32) && ip4.src == $as && ip4.dst != 10.128.0.0/11

Ideally, that final part of the match would just be omitted, but in some
CMSes this kind of thing gets generated. If we zero in on the ip4.dst
part of the match, then initially, this gets parsed into the expression:

ip4.dst == 0xac1b0041 && ip4.dst[21..31] != 0x54

After annotation, simplification, and evaluating conditions, this
becomes:

ip4.dst == 0xac1b0041 && (ip4.dst[21] || ip4.dst[22] || !ip4.dst[23] ||
ip4.dst[24] || !ip4.dst[25] || ip4.dst[26] || !ip4.dst[27] ||
ip4.dst[28] || ip4.dst[29] || ip4.dst[30] || ip4.dst[31])

At this point, we call expr_normalize(), which then calls expr_sort().
expr_sort() attempts to turn expressions of the type

a && (b || c || d)

into

ab && ac && ad

In this particular case, it turns the expression into

(ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041
|| ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041)

In other words, the same expression repeated 5 times.

Because the address set in the original match expands to multiple
addresses, and it is ANDed with the above disjunction, a conjunctive
match is created. This results in the following OF flow being created:

nw_dst=172.27.0.65,action=conjunction(2,1/2),conjunction(2,1/2),
                          conjunction(2,1/2),conjunction(2,1/2),
			  conjunction(2,1/2)

If multiple ACLs or logical router policies perform similar matches,
this can cause the number of conjunction actions on the flow to balloon,
possibly even reaching the point where the flow size is larger than 64K.

This patch seeks to fix the issue by crushing the resulting OR that is
created from expr_sort(). In the example match, this changes the ip4.dst
match to just:

ip4.dst == 0xac1b0041

Because it is now a single comparison, there's no conjunctive match
needed, and the generated OF is as expected.

Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1955161
Signed-off-by: Mark Michelson <mmichels@redhat.com>
---
 lib/expr.c   | 2 +-
 tests/ovn.at | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

Comments

Dumitru Ceara May 7, 2021, 12:03 p.m. UTC | #1
On 5/6/21 10:49 PM, Mark Michelson wrote:
> ACLs and logical router policies leave the user open to crafting
> creative matches, such as this one:
> 
> (ip4.dst == 172.27.0.65/32) && ip4.src == $as && ip4.dst != 10.128.0.0/11
> 
> Ideally, that final part of the match would just be omitted, but in some
> CMSes this kind of thing gets generated. If we zero in on the ip4.dst
> part of the match, then initially, this gets parsed into the expression:
> 
> ip4.dst == 0xac1b0041 && ip4.dst[21..31] != 0x54
> 
> After annotation, simplification, and evaluating conditions, this
> becomes:
> 
> ip4.dst == 0xac1b0041 && (ip4.dst[21] || ip4.dst[22] || !ip4.dst[23] ||
> ip4.dst[24] || !ip4.dst[25] || ip4.dst[26] || !ip4.dst[27] ||
> ip4.dst[28] || ip4.dst[29] || ip4.dst[30] || ip4.dst[31])
> 
> At this point, we call expr_normalize(), which then calls expr_sort().
> expr_sort() attempts to turn expressions of the type
> 
> a && (b || c || d)
> 
> into
> 
> ab && ac && ad

Hmm, I guess you meant "ab || ac || ad" here, right?

> 
> In this particular case, it turns the expression into
> 
> (ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041
> || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041)
> 
> In other words, the same expression repeated 5 times.
> 
> Because the address set in the original match expands to multiple
> addresses, and it is ANDed with the above disjunction, a conjunctive
> match is created. This results in the following OF flow being created:
> 
> nw_dst=172.27.0.65,action=conjunction(2,1/2),conjunction(2,1/2),
>                           conjunction(2,1/2),conjunction(2,1/2),
> 			  conjunction(2,1/2)
> 
> If multiple ACLs or logical router policies perform similar matches,
> this can cause the number of conjunction actions on the flow to balloon,
> possibly even reaching the point where the flow size is larger than 64K.
> 
> This patch seeks to fix the issue by crushing the resulting OR that is
> created from expr_sort(). In the example match, this changes the ip4.dst
> match to just:
> 
> ip4.dst == 0xac1b0041
> 
> Because it is now a single comparison, there's no conjunctive match
> needed, and the generated OF is as expected.

Thanks for the very detailed commit log, it really makes the problem
you're trying to solve very clear!

> 
> Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1955161
> Signed-off-by: Mark Michelson <mmichels@redhat.com>
> ---

I think the change is OK, but it might be good for other people to
review this too before pushing this patch.  With that in mind:

Acked-by: Dumitru Ceara <dceara@redhat.com>

Regards,
Dumitru
Mark Michelson May 7, 2021, 1:02 p.m. UTC | #2
On 5/7/21 8:03 AM, Dumitru Ceara wrote:
> On 5/6/21 10:49 PM, Mark Michelson wrote:
>> ACLs and logical router policies leave the user open to crafting
>> creative matches, such as this one:
>>
>> (ip4.dst == 172.27.0.65/32) && ip4.src == $as && ip4.dst != 10.128.0.0/11
>>
>> Ideally, that final part of the match would just be omitted, but in some
>> CMSes this kind of thing gets generated. If we zero in on the ip4.dst
>> part of the match, then initially, this gets parsed into the expression:
>>
>> ip4.dst == 0xac1b0041 && ip4.dst[21..31] != 0x54
>>
>> After annotation, simplification, and evaluating conditions, this
>> becomes:
>>
>> ip4.dst == 0xac1b0041 && (ip4.dst[21] || ip4.dst[22] || !ip4.dst[23] ||
>> ip4.dst[24] || !ip4.dst[25] || ip4.dst[26] || !ip4.dst[27] ||
>> ip4.dst[28] || ip4.dst[29] || ip4.dst[30] || ip4.dst[31])
>>
>> At this point, we call expr_normalize(), which then calls expr_sort().
>> expr_sort() attempts to turn expressions of the type
>>
>> a && (b || c || d)
>>
>> into
>>
>> ab && ac && ad
> 
> Hmm, I guess you meant "ab || ac || ad" here, right?

Whoops, yes that's correct. I'll amend the commit log when I merge if 
this gets approved. If a v3 is required for some other reason, then I'll 
amend the commit log in that version.

> 
>>
>> In this particular case, it turns the expression into
>>
>> (ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041
>> || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041)
>>
>> In other words, the same expression repeated 5 times.
>>
>> Because the address set in the original match expands to multiple
>> addresses, and it is ANDed with the above disjunction, a conjunctive
>> match is created. This results in the following OF flow being created:
>>
>> nw_dst=172.27.0.65,action=conjunction(2,1/2),conjunction(2,1/2),
>>                            conjunction(2,1/2),conjunction(2,1/2),
>> 			  conjunction(2,1/2)
>>
>> If multiple ACLs or logical router policies perform similar matches,
>> this can cause the number of conjunction actions on the flow to balloon,
>> possibly even reaching the point where the flow size is larger than 64K.
>>
>> This patch seeks to fix the issue by crushing the resulting OR that is
>> created from expr_sort(). In the example match, this changes the ip4.dst
>> match to just:
>>
>> ip4.dst == 0xac1b0041
>>
>> Because it is now a single comparison, there's no conjunctive match
>> needed, and the generated OF is as expected.
> 
> Thanks for the very detailed commit log, it really makes the problem
> you're trying to solve very clear!
> 
>>
>> Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1955161
>> Signed-off-by: Mark Michelson <mmichels@redhat.com>
>> ---
> 
> I think the change is OK, but it might be good for other people to
> review this too before pushing this patch.  With that in mind:
> 
> Acked-by: Dumitru Ceara <dceara@redhat.com>
> 
> Regards,
> Dumitru
>
Numan Siddique May 14, 2021, 7:19 p.m. UTC | #3
On Fri, May 7, 2021 at 9:03 AM Mark Michelson <mmichels@redhat.com> wrote:
>
> On 5/7/21 8:03 AM, Dumitru Ceara wrote:
> > On 5/6/21 10:49 PM, Mark Michelson wrote:
> >> ACLs and logical router policies leave the user open to crafting
> >> creative matches, such as this one:
> >>
> >> (ip4.dst == 172.27.0.65/32) && ip4.src == $as && ip4.dst != 10.128.0.0/11
> >>
> >> Ideally, that final part of the match would just be omitted, but in some
> >> CMSes this kind of thing gets generated. If we zero in on the ip4.dst
> >> part of the match, then initially, this gets parsed into the expression:
> >>
> >> ip4.dst == 0xac1b0041 && ip4.dst[21..31] != 0x54
> >>
> >> After annotation, simplification, and evaluating conditions, this
> >> becomes:
> >>
> >> ip4.dst == 0xac1b0041 && (ip4.dst[21] || ip4.dst[22] || !ip4.dst[23] ||
> >> ip4.dst[24] || !ip4.dst[25] || ip4.dst[26] || !ip4.dst[27] ||
> >> ip4.dst[28] || ip4.dst[29] || ip4.dst[30] || ip4.dst[31])
> >>
> >> At this point, we call expr_normalize(), which then calls expr_sort().
> >> expr_sort() attempts to turn expressions of the type
> >>
> >> a && (b || c || d)
> >>
> >> into
> >>
> >> ab && ac && ad
> >
> > Hmm, I guess you meant "ab || ac || ad" here, right?
>
> Whoops, yes that's correct. I'll amend the commit log when I merge if
> this gets approved. If a v3 is required for some other reason, then I'll
> amend the commit log in that version.
>
> >
> >>
> >> In this particular case, it turns the expression into
> >>
> >> (ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041
> >> || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041)
> >>
> >> In other words, the same expression repeated 5 times.
> >>
> >> Because the address set in the original match expands to multiple
> >> addresses, and it is ANDed with the above disjunction, a conjunctive
> >> match is created. This results in the following OF flow being created:
> >>
> >> nw_dst=172.27.0.65,action=conjunction(2,1/2),conjunction(2,1/2),
> >>                            conjunction(2,1/2),conjunction(2,1/2),
> >>                        conjunction(2,1/2)
> >>
> >> If multiple ACLs or logical router policies perform similar matches,
> >> this can cause the number of conjunction actions on the flow to balloon,
> >> possibly even reaching the point where the flow size is larger than 64K.
> >>
> >> This patch seeks to fix the issue by crushing the resulting OR that is
> >> created from expr_sort(). In the example match, this changes the ip4.dst
> >> match to just:
> >>
> >> ip4.dst == 0xac1b0041
> >>
> >> Because it is now a single comparison, there's no conjunctive match
> >> needed, and the generated OF is as expected.
> >
> > Thanks for the very detailed commit log, it really makes the problem
> > you're trying to solve very clear!
> >
> >>
> >> Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1955161
> >> Signed-off-by: Mark Michelson <mmichels@redhat.com>
> >> ---
> >
> > I think the change is OK, but it might be good for other people to
> > review this too before pushing this patch.  With that in mind:
> >
> > Acked-by: Dumitru Ceara <dceara@redhat.com>

Can you please add a test case for this ?

Either you can enhance this test case here -
https://github.com/ovn-org/ovn/blob/master/tests/ovn.at#L716
or add a new one.

With a test case added:
Acked-by: Numan Siddique <numans@ovn.org>

Thanks
Numan

> >
> > Regards,
> > Dumitru
> >
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Numan Siddique May 14, 2021, 7:22 p.m. UTC | #4
On Fri, May 14, 2021 at 3:19 PM Numan Siddique <numans@ovn.org> wrote:
>
> On Fri, May 7, 2021 at 9:03 AM Mark Michelson <mmichels@redhat.com> wrote:
> >
> > On 5/7/21 8:03 AM, Dumitru Ceara wrote:
> > > On 5/6/21 10:49 PM, Mark Michelson wrote:
> > >> ACLs and logical router policies leave the user open to crafting
> > >> creative matches, such as this one:
> > >>
> > >> (ip4.dst == 172.27.0.65/32) && ip4.src == $as && ip4.dst != 10.128.0.0/11
> > >>
> > >> Ideally, that final part of the match would just be omitted, but in some
> > >> CMSes this kind of thing gets generated. If we zero in on the ip4.dst
> > >> part of the match, then initially, this gets parsed into the expression:
> > >>
> > >> ip4.dst == 0xac1b0041 && ip4.dst[21..31] != 0x54
> > >>
> > >> After annotation, simplification, and evaluating conditions, this
> > >> becomes:
> > >>
> > >> ip4.dst == 0xac1b0041 && (ip4.dst[21] || ip4.dst[22] || !ip4.dst[23] ||
> > >> ip4.dst[24] || !ip4.dst[25] || ip4.dst[26] || !ip4.dst[27] ||
> > >> ip4.dst[28] || ip4.dst[29] || ip4.dst[30] || ip4.dst[31])
> > >>
> > >> At this point, we call expr_normalize(), which then calls expr_sort().
> > >> expr_sort() attempts to turn expressions of the type
> > >>
> > >> a && (b || c || d)
> > >>
> > >> into
> > >>
> > >> ab && ac && ad
> > >
> > > Hmm, I guess you meant "ab || ac || ad" here, right?
> >
> > Whoops, yes that's correct. I'll amend the commit log when I merge if
> > this gets approved. If a v3 is required for some other reason, then I'll
> > amend the commit log in that version.
> >
> > >
> > >>
> > >> In this particular case, it turns the expression into
> > >>
> > >> (ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041
> > >> || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041)
> > >>
> > >> In other words, the same expression repeated 5 times.
> > >>
> > >> Because the address set in the original match expands to multiple
> > >> addresses, and it is ANDed with the above disjunction, a conjunctive
> > >> match is created. This results in the following OF flow being created:
> > >>
> > >> nw_dst=172.27.0.65,action=conjunction(2,1/2),conjunction(2,1/2),
> > >>                            conjunction(2,1/2),conjunction(2,1/2),
> > >>                        conjunction(2,1/2)
> > >>
> > >> If multiple ACLs or logical router policies perform similar matches,
> > >> this can cause the number of conjunction actions on the flow to balloon,
> > >> possibly even reaching the point where the flow size is larger than 64K.
> > >>
> > >> This patch seeks to fix the issue by crushing the resulting OR that is
> > >> created from expr_sort(). In the example match, this changes the ip4.dst
> > >> match to just:
> > >>
> > >> ip4.dst == 0xac1b0041
> > >>
> > >> Because it is now a single comparison, there's no conjunctive match
> > >> needed, and the generated OF is as expected.
> > >
> > > Thanks for the very detailed commit log, it really makes the problem
> > > you're trying to solve very clear!
> > >
> > >>
> > >> Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1955161
> > >> Signed-off-by: Mark Michelson <mmichels@redhat.com>
> > >> ---
> > >
> > > I think the change is OK, but it might be good for other people to
> > > review this too before pushing this patch.  With that in mind:
> > >
> > > Acked-by: Dumitru Ceara <dceara@redhat.com>
>
> Can you please add a test case for this ?
>
> Either you can enhance this test case here -
> https://github.com/ovn-org/ovn/blob/master/tests/ovn.at#L716
> or add a new one.
>
> With a test case added:
> Acked-by: Numan Siddique <numans@ovn.org>

Oops.  My bad. There's already a test case.  Please ignore my comment.

Thanks
Numan

>
> Thanks
> Numan
>
> > >
> > > Regards,
> > > Dumitru
> > >
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >
Mark Michelson May 17, 2021, 7:52 p.m. UTC | #5
On 5/14/21 3:22 PM, Numan Siddique wrote:
> On Fri, May 14, 2021 at 3:19 PM Numan Siddique <numans@ovn.org> wrote:
>>
>> On Fri, May 7, 2021 at 9:03 AM Mark Michelson <mmichels@redhat.com> wrote:
>>>
>>> On 5/7/21 8:03 AM, Dumitru Ceara wrote:
>>>> On 5/6/21 10:49 PM, Mark Michelson wrote:
>>>>> ACLs and logical router policies leave the user open to crafting
>>>>> creative matches, such as this one:
>>>>>
>>>>> (ip4.dst == 172.27.0.65/32) && ip4.src == $as && ip4.dst != 10.128.0.0/11
>>>>>
>>>>> Ideally, that final part of the match would just be omitted, but in some
>>>>> CMSes this kind of thing gets generated. If we zero in on the ip4.dst
>>>>> part of the match, then initially, this gets parsed into the expression:
>>>>>
>>>>> ip4.dst == 0xac1b0041 && ip4.dst[21..31] != 0x54
>>>>>
>>>>> After annotation, simplification, and evaluating conditions, this
>>>>> becomes:
>>>>>
>>>>> ip4.dst == 0xac1b0041 && (ip4.dst[21] || ip4.dst[22] || !ip4.dst[23] ||
>>>>> ip4.dst[24] || !ip4.dst[25] || ip4.dst[26] || !ip4.dst[27] ||
>>>>> ip4.dst[28] || ip4.dst[29] || ip4.dst[30] || ip4.dst[31])
>>>>>
>>>>> At this point, we call expr_normalize(), which then calls expr_sort().
>>>>> expr_sort() attempts to turn expressions of the type
>>>>>
>>>>> a && (b || c || d)
>>>>>
>>>>> into
>>>>>
>>>>> ab && ac && ad
>>>>
>>>> Hmm, I guess you meant "ab || ac || ad" here, right?
>>>
>>> Whoops, yes that's correct. I'll amend the commit log when I merge if
>>> this gets approved. If a v3 is required for some other reason, then I'll
>>> amend the commit log in that version.
>>>
>>>>
>>>>>
>>>>> In this particular case, it turns the expression into
>>>>>
>>>>> (ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041
>>>>> || ip4.dst == 0xac1b0041 || ip4.dst == 0xac1b0041)
>>>>>
>>>>> In other words, the same expression repeated 5 times.
>>>>>
>>>>> Because the address set in the original match expands to multiple
>>>>> addresses, and it is ANDed with the above disjunction, a conjunctive
>>>>> match is created. This results in the following OF flow being created:
>>>>>
>>>>> nw_dst=172.27.0.65,action=conjunction(2,1/2),conjunction(2,1/2),
>>>>>                             conjunction(2,1/2),conjunction(2,1/2),
>>>>>                         conjunction(2,1/2)
>>>>>
>>>>> If multiple ACLs or logical router policies perform similar matches,
>>>>> this can cause the number of conjunction actions on the flow to balloon,
>>>>> possibly even reaching the point where the flow size is larger than 64K.
>>>>>
>>>>> This patch seeks to fix the issue by crushing the resulting OR that is
>>>>> created from expr_sort(). In the example match, this changes the ip4.dst
>>>>> match to just:
>>>>>
>>>>> ip4.dst == 0xac1b0041
>>>>>
>>>>> Because it is now a single comparison, there's no conjunctive match
>>>>> needed, and the generated OF is as expected.
>>>>
>>>> Thanks for the very detailed commit log, it really makes the problem
>>>> you're trying to solve very clear!
>>>>
>>>>>
>>>>> Reported at: https://bugzilla.redhat.com/show_bug.cgi?id=1955161
>>>>> Signed-off-by: Mark Michelson <mmichels@redhat.com>
>>>>> ---
>>>>
>>>> I think the change is OK, but it might be good for other people to
>>>> review this too before pushing this patch.  With that in mind:
>>>>
>>>> Acked-by: Dumitru Ceara <dceara@redhat.com>
>>
>> Can you please add a test case for this ?
>>
>> Either you can enhance this test case here -
>> https://github.com/ovn-org/ovn/blob/master/tests/ovn.at#L716
>> or add a new one.
>>
>> With a test case added:
>> Acked-by: Numan Siddique <numans@ovn.org>
> 
> Oops.  My bad. There's already a test case.  Please ignore my comment.
> 
> Thanks
> Numan

Thanks Numan and Dumitru. I made the correction to the commit message 
and pushed to master, branch-21.03, and branch-20.12.

> 
>>
>> Thanks
>> Numan
>>
>>>>
>>>> Regards,
>>>> Dumitru
>>>>
>>>
>>> _______________________________________________
>>> dev mailing list
>>> dev@openvswitch.org
>>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>>
>
diff mbox series

Patch

diff --git a/lib/expr.c b/lib/expr.c
index cfc1082e1..244ff3139 100644
--- a/lib/expr.c
+++ b/lib/expr.c
@@ -2468,7 +2468,7 @@  crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
             free(or);
             return cmp;
         } else {
-            return or;
+            return crush_cmps(or, symbol);
         }
     } else {
         /* Transform "x && (a0 || a1) && (b0 || b1) && ..." into
diff --git a/tests/ovn.at b/tests/ovn.at
index fe6a7c85b..0c1df24a4 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -693,6 +693,11 @@  ip,nw_src=4.0.0.0/4.0.0.0
 ip,nw_src=64.0.0.0/64.0.0.0
 ip,nw_src=8.0.0.0/8.0.0.0
 ])
+AT_CHECK([expr_to_flow 'ip4.dst == 172.27.0.65 && ip4.src == $set1 && ip4.dst != 10.128.0.0/14'], [0], [dnl
+ip,nw_src=10.0.0.1,nw_dst=172.27.0.65
+ip,nw_src=10.0.0.2,nw_dst=172.27.0.65
+ip,nw_src=10.0.0.3,nw_dst=172.27.0.65
+])
 AT_CLEANUP
 
 AT_SETUP([ovn -- converting expressions to flows -- port groups])