Skip to content

Commit 054552f

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 4e12cc2 commit 054552f

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
@@ -262,8 +262,12 @@ ovn_init_symtab(struct shash *symtab)
262262

263263
/* Predefined IPv6 multicast groups (RFC 4291, 2.7.1). */
264264
expr_symtab_add_predicate(symtab, "ip6.mcast_rsvd",
265-
"ip6.dst[116..127] == 0xff0 && "
266-
"ip6.dst[0..111] == 0x0");
265+
"ip6.dst == { "
266+
"ff00::0, ff01::0, ff02::0, ff03::0, "
267+
"ff04::0, ff05::0, ff06::0, ff07::0, "
268+
"ff08::0, ff09::0, ff0a::0, ff0b::0, "
269+
"ff0c::0, ff0d::0, ff0e::0, ff0f::0 "
270+
"}");
267271
expr_symtab_add_predicate(symtab, "ip6.mcast_all_nodes",
268272
"ip6.dst == ff01::1 || ip6.dst == ff02::1");
269273
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
@@ -40565,6 +40565,157 @@ OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
4056540565
AT_CLEANUP
4056640566
])
4056740567

40568+
dnl This test checks that the megaflows translated by ovs-vswitchd don't
40569+
dnl have extensive matches on external IP addresses for simple routing.
40570+
OVN_FOR_EACH_NORTHD([
40571+
AT_SETUP([IPv4/v6 routing to external - megaflow check for src/dst matches])
40572+
AT_SKIP_IF([test $HAVE_SCAPY = no])
40573+
ovn_start
40574+
40575+
check ovn-nbctl ls-add sw0
40576+
40577+
check ovn-nbctl lsp-add sw0 vm0
40578+
check ovn-nbctl lsp-set-addresses vm0 "f0:00:0f:01:02:03 10.0.0.3 1000::3"
40579+
40580+
check ovn-nbctl ls-add sw1
40581+
40582+
check ovn-nbctl lsp-add sw1 ext
40583+
check ovn-nbctl lsp-set-addresses ext unknown
40584+
check ovn-nbctl lsp-set-type ext localnet
40585+
check ovn-nbctl lsp-set-options ext network_name=phys
40586+
40587+
check ovn-nbctl lr-add lr0
40588+
40589+
check ovn-nbctl lrp-add lr0 lr0-sw0 fa:16:3e:00:00:01 10.0.0.250/24 1000::f0/64
40590+
check ovn-nbctl lsp-add sw0 sw0-lr0
40591+
check ovn-nbctl lsp-set-type sw0-lr0 router
40592+
check ovn-nbctl lsp-set-addresses sw0-lr0 router
40593+
check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
40594+
40595+
check ovn-nbctl lrp-add lr0 lr0-sw1 fa:16:3e:00:00:02 20.0.0.250/24 2000::f0/64
40596+
check ovn-nbctl lsp-add sw1 sw1-lr0
40597+
check ovn-nbctl lsp-set-type sw1-lr0 router
40598+
check ovn-nbctl lsp-set-addresses sw1-lr0 router
40599+
check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
40600+
40601+
dnl Add default routes for the external gateway.
40602+
check ovn-nbctl lr-route-add lr0 "0.0.0.0/0" 20.0.0.254 lr0-sw1
40603+
check ovn-nbctl lr-route-add lr0 "::/0" 2000::fe lr0-sw1
40604+
40605+
net_add n1
40606+
sim_add hv
40607+
as hv
40608+
check ovs-vsctl add-br br-phys
40609+
ovn_attach n1 br-phys 192.168.0.1
40610+
check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
40611+
check ovs-vsctl add-port br-int vif1 -- \
40612+
set Interface vif1 external-ids:iface-id=vm0 \
40613+
options:tx_pcap=hv/vif1-tx.pcap \
40614+
options:rxq_pcap=hv/vif1-rx.pcap \
40615+
ofport-request=1
40616+
40617+
check ovn-nbctl --wait=sb sync
40618+
wait_for_ports_up
40619+
40620+
dnl Create MAC binding entries for the external gateway, so OVN doesn't need
40621+
dnl to ARP/ND for it.
40622+
lr0_dp=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
40623+
check_uuid ovn-sbctl create mac_binding datapath=$lr0_dp logical_port=lr0-sw1 \
40624+
ip=\"2000::fe\" mac=\"f0:00:0f:01:02:fe\"
40625+
check_uuid ovn-sbctl create mac_binding datapath=$lr0_dp logical_port=lr0-sw1 \
40626+
ip=\"20.0.0.254\" mac=\"f0:00:0f:01:02:fe\"
40627+
check ovn-nbctl --wait=hv sync
40628+
40629+
AS_BOX([IPv6 - from external to vm0])
40630+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:02', src='f0:00:0f:01:02:fe')/ \
40631+
IPv6(dst='1000::3', src='3000::4', hlim=64)/ \
40632+
UDP(sport=53, dport=4369)")
40633+
as hv
40634+
ovs-appctl ofproto/trace br-phys in_port=br-phys_n1 $packet --names > ext_ip6_ofproto_trace.txt
40635+
check ovs-appctl netdev-dummy/receive br-phys_n1 $packet
40636+
40637+
AT_CAPTURE_FILE([ext_ip6_ofproto_trace.txt])
40638+
40639+
dnl Make sure the datapath flow doesn't match on a full external address.
40640+
AT_CHECK([grep Megaflow ext_ip6_ofproto_trace.txt], [0], [stdout])
40641+
AT_CHECK([grep Megaflow ext_ip6_ofproto_trace.txt | grep -q '3000::4'], [1])
40642+
40643+
dnl Make sure that the packet was received by vm0. The L2 addresses and the
40644+
dnl hop limit will be different since the packet was routed.
40645+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='fa:16:3e:00:00:01')/ \
40646+
IPv6(dst='1000::3', src='3000::4', hlim=63)/ \
40647+
UDP(sport=53, dport=4369)")
40648+
echo $packet >> expected-vif1
40649+
OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
40650+
40651+
AS_BOX([IPv6 - from vm0 to external])
40652+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:01', src='f0:00:0f:01:02:03')/ \
40653+
IPv6(dst='3000::4', src='1000::3', hlim=64)/ \
40654+
UDP(sport=53, dport=4369)")
40655+
as hv
40656+
ovs-appctl ofproto/trace br-int in_port=vif1 $packet --names > vm0_ip6_ofproto_trace.txt
40657+
check ovs-appctl netdev-dummy/receive vif1 $packet
40658+
40659+
AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])
40660+
40661+
dnl Make sure the datapath flow doesn't match on a full external address.
40662+
AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt], [0], [stdout])
40663+
AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -q '3000::4'], [1])
40664+
40665+
dnl Make sure that the packet was received externally. The L2 addresses and
40666+
dnl the hop limit will be different since the packet was routed.
40667+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:fe', src='fa:16:3e:00:00:02')/ \
40668+
IPv6(dst='3000::4', src='1000::3', hlim=63)/ \
40669+
UDP(sport=53, dport=4369)")
40670+
echo $packet >> expected-ext
40671+
OVN_CHECK_PACKETS([hv/br-phys_n1-tx.pcap], [expected-ext])
40672+
40673+
AS_BOX([IPv4 - from external to vm0])
40674+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:02', src='f0:00:0f:01:02:fe')/ \
40675+
IP(dst='10.0.0.3', src='30.0.0.4', ttl=64)/ \
40676+
UDP(sport=53, dport=4369)")
40677+
as hv
40678+
ovs-appctl ofproto/trace br-phys in_port=br-phys_n1 $packet --names > ext_ip4_ofproto_trace.txt
40679+
check ovs-appctl netdev-dummy/receive br-phys_n1 $packet
40680+
40681+
AT_CAPTURE_FILE([ext_ip4_ofproto_trace.txt])
40682+
40683+
dnl Make sure the datapath flow doesn't match on a full external address.
40684+
AT_CHECK([grep Megaflow ext_ip4_ofproto_trace.txt], [0], [stdout])
40685+
AT_CHECK([grep Megaflow ext_ip4_ofproto_trace.txt | grep -q '30.0.0.4'], [1])
40686+
40687+
dnl Make sure that the packet was received by vm0. The L2 addresses and the
40688+
dnl hop limit will be different since the packet was routed.
40689+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='fa:16:3e:00:00:01')/ \
40690+
IP(dst='10.0.0.3', src='30.0.0.4', ttl=63)/ \
40691+
UDP(sport=53, dport=4369)")
40692+
echo $packet >> expected-vif1
40693+
OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
40694+
40695+
AS_BOX([IPv4 - from vm0 to external])
40696+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:01', src='f0:00:0f:01:02:03')/ \
40697+
IP(dst='30.0.0.4', src='10.0.0.3', ttl=64)/ \
40698+
UDP(sport=53, dport=4369)")
40699+
as hv
40700+
ovs-appctl ofproto/trace br-int in_port=vif1 $packet --names > vm0_ip4_ofproto_trace.txt
40701+
check ovs-appctl netdev-dummy/receive vif1 $packet
40702+
40703+
AT_CAPTURE_FILE([vm0_ip4_ofproto_trace.txt])
40704+
40705+
dnl Make sure the datapath flow doesn't match on a full external address.
40706+
AT_CHECK([grep Megaflow vm0_ip4_ofproto_trace.txt], [0], [stdout])
40707+
AT_CHECK([grep Megaflow vm0_ip4_ofproto_trace.txt | grep -q '30.0.0.4'], [1])
40708+
40709+
dnl Make sure that the packet was received externally. The L2 addresses and
40710+
dnl the hop limit will be different since the packet was routed.
40711+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:fe', src='fa:16:3e:00:00:02')/ \
40712+
IP(dst='30.0.0.4', src='10.0.0.3', ttl=63)/ \
40713+
UDP(sport=53, dport=4369)")
40714+
echo $packet >> expected-ext
40715+
OVN_CHECK_PACKETS([hv/br-phys_n1-tx.pcap], [expected-ext])
40716+
40717+
AT_CLEANUP
40718+
])
4056840719

4056940720
OVN_FOR_EACH_NORTHD([
4057040721
AT_SETUP([Multichassis port I-P processing])

0 commit comments

Comments
 (0)