Skip to content

Commit e13c40c

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 21e2a8b commit e13c40c

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

248248
/* Predefined IPv6 multicast groups (RFC 4291, 2.7.1). */
249249
expr_symtab_add_predicate(symtab, "ip6.mcast_rsvd",
250-
"ip6.dst[116..127] == 0xff0 && "
251-
"ip6.dst[0..111] == 0x0");
250+
"ip6.dst == { "
251+
"ff00::0, ff01::0, ff02::0, ff03::0, "
252+
"ff04::0, ff05::0, ff06::0, ff07::0, "
253+
"ff08::0, ff09::0, ff0a::0, ff0b::0, "
254+
"ff0c::0, ff0d::0, ff0e::0, ff0f::0 "
255+
"}");
252256
expr_symtab_add_predicate(symtab, "ip6.mcast_all_nodes",
253257
"ip6.dst == ff01::1 || ip6.dst == ff02::1");
254258
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
@@ -39954,6 +39954,157 @@ OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
3995439954
AT_CLEANUP
3995539955
])
3995639956

39957+
dnl This test checks that the megaflows translated by ovs-vswitchd don't
39958+
dnl have extensive matches on external IP addresses for simple routing.
39959+
OVN_FOR_EACH_NORTHD([
39960+
AT_SETUP([IPv4/v6 routing to external - megaflow check for src/dst matches])
39961+
AT_SKIP_IF([test $HAVE_SCAPY = no])
39962+
ovn_start
39963+
39964+
check ovn-nbctl ls-add sw0
39965+
39966+
check ovn-nbctl lsp-add sw0 vm0
39967+
check ovn-nbctl lsp-set-addresses vm0 "f0:00:0f:01:02:03 10.0.0.3 1000::3"
39968+
39969+
check ovn-nbctl ls-add sw1
39970+
39971+
check ovn-nbctl lsp-add sw1 ext
39972+
check ovn-nbctl lsp-set-addresses ext unknown
39973+
check ovn-nbctl lsp-set-type ext localnet
39974+
check ovn-nbctl lsp-set-options ext network_name=phys
39975+
39976+
check ovn-nbctl lr-add lr0
39977+
39978+
check ovn-nbctl lrp-add lr0 lr0-sw0 fa:16:3e:00:00:01 10.0.0.250/24 1000::f0/64
39979+
check ovn-nbctl lsp-add sw0 sw0-lr0
39980+
check ovn-nbctl lsp-set-type sw0-lr0 router
39981+
check ovn-nbctl lsp-set-addresses sw0-lr0 router
39982+
check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
39983+
39984+
check ovn-nbctl lrp-add lr0 lr0-sw1 fa:16:3e:00:00:02 20.0.0.250/24 2000::f0/64
39985+
check ovn-nbctl lsp-add sw1 sw1-lr0
39986+
check ovn-nbctl lsp-set-type sw1-lr0 router
39987+
check ovn-nbctl lsp-set-addresses sw1-lr0 router
39988+
check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
39989+
39990+
dnl Add default routes for the external gateway.
39991+
check ovn-nbctl lr-route-add lr0 "0.0.0.0/0" 20.0.0.254 lr0-sw1
39992+
check ovn-nbctl lr-route-add lr0 "::/0" 2000::fe lr0-sw1
39993+
39994+
net_add n1
39995+
sim_add hv
39996+
as hv
39997+
check ovs-vsctl add-br br-phys
39998+
ovn_attach n1 br-phys 192.168.0.1
39999+
check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
40000+
check ovs-vsctl add-port br-int vif1 -- \
40001+
set Interface vif1 external-ids:iface-id=vm0 \
40002+
options:tx_pcap=hv/vif1-tx.pcap \
40003+
options:rxq_pcap=hv/vif1-rx.pcap \
40004+
ofport-request=1
40005+
40006+
check ovn-nbctl --wait=sb sync
40007+
wait_for_ports_up
40008+
40009+
dnl Create MAC binding entries for the external gateway, so OVN doesn't need
40010+
dnl to ARP/ND for it.
40011+
lr0_dp=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
40012+
check_uuid ovn-sbctl create mac_binding datapath=$lr0_dp logical_port=lr0-sw1 \
40013+
ip=\"2000::fe\" mac=\"f0:00:0f:01:02:fe\"
40014+
check_uuid ovn-sbctl create mac_binding datapath=$lr0_dp logical_port=lr0-sw1 \
40015+
ip=\"20.0.0.254\" mac=\"f0:00:0f:01:02:fe\"
40016+
check ovn-nbctl --wait=hv sync
40017+
40018+
AS_BOX([IPv6 - from external to vm0])
40019+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:02', src='f0:00:0f:01:02:fe')/ \
40020+
IPv6(dst='1000::3', src='3000::4', hlim=64)/ \
40021+
UDP(sport=53, dport=4369)")
40022+
as hv
40023+
ovs-appctl ofproto/trace br-phys in_port=br-phys_n1 $packet --names > ext_ip6_ofproto_trace.txt
40024+
check ovs-appctl netdev-dummy/receive br-phys_n1 $packet
40025+
40026+
AT_CAPTURE_FILE([ext_ip6_ofproto_trace.txt])
40027+
40028+
dnl Make sure the datapath flow doesn't match on a full external address.
40029+
AT_CHECK([grep Megaflow ext_ip6_ofproto_trace.txt], [0], [stdout])
40030+
AT_CHECK([grep Megaflow ext_ip6_ofproto_trace.txt | grep -q '3000::4'], [1])
40031+
40032+
dnl Make sure that the packet was received by vm0. The L2 addresses and the
40033+
dnl hop limit will be different since the packet was routed.
40034+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='fa:16:3e:00:00:01')/ \
40035+
IPv6(dst='1000::3', src='3000::4', hlim=63)/ \
40036+
UDP(sport=53, dport=4369)")
40037+
echo $packet >> expected-vif1
40038+
OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
40039+
40040+
AS_BOX([IPv6 - from vm0 to external])
40041+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:01', src='f0:00:0f:01:02:03')/ \
40042+
IPv6(dst='3000::4', src='1000::3', hlim=64)/ \
40043+
UDP(sport=53, dport=4369)")
40044+
as hv
40045+
ovs-appctl ofproto/trace br-int in_port=vif1 $packet --names > vm0_ip6_ofproto_trace.txt
40046+
check ovs-appctl netdev-dummy/receive vif1 $packet
40047+
40048+
AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])
40049+
40050+
dnl Make sure the datapath flow doesn't match on a full external address.
40051+
AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt], [0], [stdout])
40052+
AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -q '3000::4'], [1])
40053+
40054+
dnl Make sure that the packet was received externally. The L2 addresses and
40055+
dnl the hop limit will be different since the packet was routed.
40056+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:fe', src='fa:16:3e:00:00:02')/ \
40057+
IPv6(dst='3000::4', src='1000::3', hlim=63)/ \
40058+
UDP(sport=53, dport=4369)")
40059+
echo $packet >> expected-ext
40060+
OVN_CHECK_PACKETS([hv/br-phys_n1-tx.pcap], [expected-ext])
40061+
40062+
AS_BOX([IPv4 - from external to vm0])
40063+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:02', src='f0:00:0f:01:02:fe')/ \
40064+
IP(dst='10.0.0.3', src='30.0.0.4', ttl=64)/ \
40065+
UDP(sport=53, dport=4369)")
40066+
as hv
40067+
ovs-appctl ofproto/trace br-phys in_port=br-phys_n1 $packet --names > ext_ip4_ofproto_trace.txt
40068+
check ovs-appctl netdev-dummy/receive br-phys_n1 $packet
40069+
40070+
AT_CAPTURE_FILE([ext_ip4_ofproto_trace.txt])
40071+
40072+
dnl Make sure the datapath flow doesn't match on a full external address.
40073+
AT_CHECK([grep Megaflow ext_ip4_ofproto_trace.txt], [0], [stdout])
40074+
AT_CHECK([grep Megaflow ext_ip4_ofproto_trace.txt | grep -q '30.0.0.4'], [1])
40075+
40076+
dnl Make sure that the packet was received by vm0. The L2 addresses and the
40077+
dnl hop limit will be different since the packet was routed.
40078+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='fa:16:3e:00:00:01')/ \
40079+
IP(dst='10.0.0.3', src='30.0.0.4', ttl=63)/ \
40080+
UDP(sport=53, dport=4369)")
40081+
echo $packet >> expected-vif1
40082+
OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
40083+
40084+
AS_BOX([IPv4 - from vm0 to external])
40085+
packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:01', src='f0:00:0f:01:02:03')/ \
40086+
IP(dst='30.0.0.4', src='10.0.0.3', ttl=64)/ \
40087+
UDP(sport=53, dport=4369)")
40088+
as hv
40089+
ovs-appctl ofproto/trace br-int in_port=vif1 $packet --names > vm0_ip4_ofproto_trace.txt
40090+
check ovs-appctl netdev-dummy/receive vif1 $packet
40091+
40092+
AT_CAPTURE_FILE([vm0_ip4_ofproto_trace.txt])
40093+
40094+
dnl Make sure the datapath flow doesn't match on a full external address.
40095+
AT_CHECK([grep Megaflow vm0_ip4_ofproto_trace.txt], [0], [stdout])
40096+
AT_CHECK([grep Megaflow vm0_ip4_ofproto_trace.txt | grep -q '30.0.0.4'], [1])
40097+
40098+
dnl Make sure that the packet was received externally. The L2 addresses and
40099+
dnl the hop limit will be different since the packet was routed.
40100+
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:fe', src='fa:16:3e:00:00:02')/ \
40101+
IP(dst='30.0.0.4', src='10.0.0.3', ttl=63)/ \
40102+
UDP(sport=53, dport=4369)")
40103+
echo $packet >> expected-ext
40104+
OVN_CHECK_PACKETS([hv/br-phys_n1-tx.pcap], [expected-ext])
40105+
40106+
AT_CLEANUP
40107+
])
3995740108

3995840109
OVN_FOR_EACH_NORTHD([
3995940110
AT_SETUP([Multichassis port I-P processing])

0 commit comments

Comments
 (0)