Skip to content

Commit d162828

Browse files
igsilyahzhou8
authored andcommitted
logical-fields: Fix IPv6 dp flow explosion caused by ip6.mcast_rsvd.
OVN routers are configured to drop any traffic with a destination being one of the Reserved Multicast Addresses (RFC 4291). This is done by matching on all the bits of ipv6.dst, except for bits 112-116 that cover all the addresses. Once installed into OVS, this turns into a following match: ipv6_dst=ff00::/fff0:ffff:ffff:ffff:ffff:ffff:ffff:ffff We fixed a large chunk of IPv6 datapath flow explosion issues by turning on prefix tacking in the flow classifier in OVS in commit 89e43f7 ("controller: Fix IPv6 dp flow explosion by setting flow table prefixes."). However, prefix tracking doesn't work for masks that are not contiguous. That means that if a packet reaches a classifier subtable with non-contiguous mask, all the bits of that mask will be un-wildcarded. It's not a huge problem in a general case, because most non-contiguous masks would typically match on just a few bits. But ip6.mcast_rsvd is matching on 124 bits, un-wildcarding them for most of the IPv6 traffic traversing a router and causing creation of a separate exact-match datapath flow per destination IP. For setups that handle large amount of traffic from many different external addresses this issue makes IPv6 handling significantly harder than IPv4, causing much higher load on the datapath with potential overflow of datapath flow tables and a subsequent upcall storm. Even without the overflow, OVS spends a lot of time revalidating all these datapath flows burning CPU cycles. In general, since the number of external IP addresses is virtually unlimited, there should be no configuration where OVN exact-matches them, otherwise it will be a significant datapath scaling issue. Fix that by replacing a non-contiguous bit-match with a match on an address set where all the reserved multicast addresses are just listed directly. There are only 16 of them, so this should not be a huge problem to have extra 15 OpenFlow rules per router, but it will allow OVS to use prefix tracking for these flows and avoid creating separate datapath flow per destination IP. Also adding a simple lsp-to-external routing test case to make sure we don't have exact matches in this simple common use case. The OVS classifier can likely be improved to handle non-contiguous masks better, but it's not how the prefix tracking is designed, so it's not a simple task. Fixes: 677a3ba ("ovn: Add MLD support.") Reported-at: https://issues.redhat.com/browse/FDP-1557 Signed-off-by: Ilya Maximets <i.maximets@ovn.org> Signed-off-by: Han Zhou <hzhou@ovn.org> (cherry picked from commit ab19375)
1 parent eece712 commit d162828

2 files changed

Lines changed: 157 additions & 2 deletions

File tree

lib/logical-fields.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,12 @@ ovn_init_symtab(struct shash *symtab)
257257

258258
/* Predefined IPv6 multicast groups (RFC 4291, 2.7.1). */
259259
expr_symtab_add_predicate(symtab, "ip6.mcast_rsvd",
260-
"ip6.dst[116..127] == 0xff0 && "
261-
"ip6.dst[0..111] == 0x0");
260+
"ip6.dst == { "
261+
"ff00::0, ff01::0, ff02::0, ff03::0, "
262+
"ff04::0, ff05::0, ff06::0, ff07::0, "
263+
"ff08::0, ff09::0, ff0a::0, ff0b::0, "
264+
"ff0c::0, ff0d::0, ff0e::0, ff0f::0 "
265+
"}");
262266
expr_symtab_add_predicate(symtab, "ip6.mcast_all_nodes",
263267
"ip6.dst == ff01::1 || ip6.dst == ff02::1");
264268
expr_symtab_add_predicate(symtab, "ip6.mcast_all_rtrs",

tests/ovn.at

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39151,6 +39151,157 @@ OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
3915139151
AT_CLEANUP
3915239152
])
3915339153

39154+
dnl This test checks that the megaflows translated by ovs-vswitchd don't
39155+
dnl have extensive matches on external IP addresses for simple routing.
39156+
OVN_FOR_EACH_NORTHD([
39157+
AT_SETUP([IPv4/v6 routing to external - megaflow check for src/dst matches])
39158+
AT_SKIP_IF([test $HAVE_SCAPY = no])
39159+
ovn_start
39160+
39161+
check ovn-nbctl ls-add sw0
39162+
39163+
check ovn-nbctl lsp-add sw0 vm0
39164+
check ovn-nbctl lsp-set-addresses vm0 "f0:00:0f:01:02:03 10.0.0.3 1000::3"
39165+
39166+
check ovn-nbctl ls-add sw1
39167+
39168+
check ovn-nbctl lsp-add sw1 ext
39169+
check ovn-nbctl lsp-set-addresses ext unknown
39170+
check ovn-nbctl lsp-set-type ext localnet
39171+
check ovn-nbctl lsp-set-options ext network_name=phys
39172+
39173+
check ovn-nbctl lr-add lr0
39174+
39175+
check ovn-nbctl lrp-add lr0 lr0-sw0 fa:16:3e:00:00:01 10.0.0.250/24 1000::f0/64
39176+
check ovn-nbctl lsp-add sw0 sw0-lr0
39177+
check ovn-nbctl lsp-set-type sw0-lr0 router
39178+
check ovn-nbctl lsp-set-addresses sw0-lr0 router
39179+
check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
39180+
39181+
check ovn-nbctl lrp-add lr0 lr0-sw1 fa:16:3e:00:00:02 20.0.0.250/24 2000::f0/64
39182+
check ovn-nbctl lsp-add sw1 sw1-lr0
39183+
check ovn-nbctl lsp-set-type sw1-lr0 router
39184+
check ovn-nbctl lsp-set-addresses sw1-lr0 router
39185+
check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
39186+
39187+
dnl Add default routes for the external gateway.
39188+
check ovn-nbctl lr-route-add lr0 "0.0.0.0/0" 20.0.0.254 lr0-sw1
39189+
check ovn-nbctl lr-route-add lr0 "::/0" 2000::fe lr0-sw1
39190+
39191+
net_add n1
39192+
sim_add hv
39193+
as hv
39194+
check ovs-vsctl add-br br-phys
39195+
ovn_attach n1 br-phys 192.168.0.1
39196+
check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
39197+
check ovs-vsctl add-port br-int vif1 -- \
39198+
set Interface vif1 external-ids:iface-id=vm0 \
39199+
options:tx_pcap=hv/vif1-tx.pcap \
39200+
options:rxq_pcap=hv/vif1-rx.pcap \
39201+
ofport-request=1
39202+
39203+
check ovn-nbctl --wait=sb sync
39204+
wait_for_ports_up
39205+
39206+
dnl Create MAC binding entries for the external gateway, so OVN doesn't need
39207+
dnl to ARP/ND for it.
39208+
lr0_dp=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
39209+
check_uuid ovn-sbctl create mac_binding datapath=$lr0_dp logical_port=lr0-sw1 \
39210+
ip=\"2000::fe\" mac=\"f0:00:0f:01:02:fe\"
39211+
check_uuid ovn-sbctl create mac_binding datapath=$lr0_dp logical_port=lr0-sw1 \
39212+
ip=\"20.0.0.254\" mac=\"f0:00:0f:01:02:fe\"
39213+
check ovn-nbctl --wait=hv sync
39214+
39215+
AS_BOX([IPv6 - from external to vm0])
39216+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:02', src='f0:00:0f:01:02:fe')/ \
39217+
IPv6(dst='1000::3', src='3000::4', hlim=64)/ \
39218+
UDP(sport=53, dport=4369)")
39219+
as hv
39220+
ovs-appctl ofproto/trace br-phys in_port=br-phys_n1 $packet --names > ext_ip6_ofproto_trace.txt
39221+
check ovs-appctl netdev-dummy/receive br-phys_n1 $packet
39222+
39223+
AT_CAPTURE_FILE([ext_ip6_ofproto_trace.txt])
39224+
39225+
dnl Make sure the datapath flow doesn't match on a full external address.
39226+
AT_CHECK([grep Megaflow ext_ip6_ofproto_trace.txt], [0], [stdout])
39227+
AT_CHECK([grep Megaflow ext_ip6_ofproto_trace.txt | grep -q '3000::4'], [1])
39228+
39229+
dnl Make sure that the packet was received by vm0. The L2 addresses and the
39230+
dnl hop limit will be different since the packet was routed.
39231+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='fa:16:3e:00:00:01')/ \
39232+
IPv6(dst='1000::3', src='3000::4', hlim=63)/ \
39233+
UDP(sport=53, dport=4369)")
39234+
echo $packet >> expected-vif1
39235+
OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
39236+
39237+
AS_BOX([IPv6 - from vm0 to external])
39238+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:01', src='f0:00:0f:01:02:03')/ \
39239+
IPv6(dst='3000::4', src='1000::3', hlim=64)/ \
39240+
UDP(sport=53, dport=4369)")
39241+
as hv
39242+
ovs-appctl ofproto/trace br-int in_port=vif1 $packet --names > vm0_ip6_ofproto_trace.txt
39243+
check ovs-appctl netdev-dummy/receive vif1 $packet
39244+
39245+
AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])
39246+
39247+
dnl Make sure the datapath flow doesn't match on a full external address.
39248+
AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt], [0], [stdout])
39249+
AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -q '3000::4'], [1])
39250+
39251+
dnl Make sure that the packet was received externally. The L2 addresses and
39252+
dnl the hop limit will be different since the packet was routed.
39253+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:fe', src='fa:16:3e:00:00:02')/ \
39254+
IPv6(dst='3000::4', src='1000::3', hlim=63)/ \
39255+
UDP(sport=53, dport=4369)")
39256+
echo $packet >> expected-ext
39257+
OVN_CHECK_PACKETS([hv/br-phys_n1-tx.pcap], [expected-ext])
39258+
39259+
AS_BOX([IPv4 - from external to vm0])
39260+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:02', src='f0:00:0f:01:02:fe')/ \
39261+
IP(dst='10.0.0.3', src='30.0.0.4', ttl=64)/ \
39262+
UDP(sport=53, dport=4369)")
39263+
as hv
39264+
ovs-appctl ofproto/trace br-phys in_port=br-phys_n1 $packet --names > ext_ip4_ofproto_trace.txt
39265+
check ovs-appctl netdev-dummy/receive br-phys_n1 $packet
39266+
39267+
AT_CAPTURE_FILE([ext_ip4_ofproto_trace.txt])
39268+
39269+
dnl Make sure the datapath flow doesn't match on a full external address.
39270+
AT_CHECK([grep Megaflow ext_ip4_ofproto_trace.txt], [0], [stdout])
39271+
AT_CHECK([grep Megaflow ext_ip4_ofproto_trace.txt | grep -q '30.0.0.4'], [1])
39272+
39273+
dnl Make sure that the packet was received by vm0. The L2 addresses and the
39274+
dnl hop limit will be different since the packet was routed.
39275+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='fa:16:3e:00:00:01')/ \
39276+
IP(dst='10.0.0.3', src='30.0.0.4', ttl=63)/ \
39277+
UDP(sport=53, dport=4369)")
39278+
echo $packet >> expected-vif1
39279+
OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
39280+
39281+
AS_BOX([IPv4 - from vm0 to external])
39282+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:01', src='f0:00:0f:01:02:03')/ \
39283+
IP(dst='30.0.0.4', src='10.0.0.3', ttl=64)/ \
39284+
UDP(sport=53, dport=4369)")
39285+
as hv
39286+
ovs-appctl ofproto/trace br-int in_port=vif1 $packet --names > vm0_ip4_ofproto_trace.txt
39287+
check ovs-appctl netdev-dummy/receive vif1 $packet
39288+
39289+
AT_CAPTURE_FILE([vm0_ip4_ofproto_trace.txt])
39290+
39291+
dnl Make sure the datapath flow doesn't match on a full external address.
39292+
AT_CHECK([grep Megaflow vm0_ip4_ofproto_trace.txt], [0], [stdout])
39293+
AT_CHECK([grep Megaflow vm0_ip4_ofproto_trace.txt | grep -q '30.0.0.4'], [1])
39294+
39295+
dnl Make sure that the packet was received externally. The L2 addresses and
39296+
dnl the hop limit will be different since the packet was routed.
39297+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:fe', src='fa:16:3e:00:00:02')/ \
39298+
IP(dst='30.0.0.4', src='10.0.0.3', ttl=63)/ \
39299+
UDP(sport=53, dport=4369)")
39300+
echo $packet >> expected-ext
39301+
OVN_CHECK_PACKETS([hv/br-phys_n1-tx.pcap], [expected-ext])
39302+
39303+
AT_CLEANUP
39304+
])
3915439305

3915539306
OVN_FOR_EACH_NORTHD([
3915639307
AT_SETUP([Multichassis port I-P processing])

0 commit comments

Comments
 (0)