Skip to content

Commit

Permalink
Reply only for the multicast ND solicitations.
Browse files Browse the repository at this point in the history
IPv6 ND Solicitation (NS) responder logical flows match on ip6.dst
field.  These flows when translated to datapath flows also match on
ip6.dst, which means a separate datapath flow per destination IP
address.  This may cause significant performance issues in some
setups (particularly ovs-dpdk telco deployments).

This patch addresses this issue by matching on eth.mcast6 so that
datapath flows for normal IPv6 traffic doesn't have to match on
ip6.dst.  IPv6 NS packets are generally multicast.  A new logical
match "nd_ns_mcast" is added for this purpose.

After this patch, We no longer respond to IPv6 NS unicast packets.
Let the target reply to it, so that the sender has the ability to
monitor the targe liveness via the unicast ND solicitations.
This behavior now matches the IPv4 ARP responder flows.  Note that
after the commit [1] which was recently added we now only respond
to IPv4 ARP broadcast packets.

A recent patch [2] from Ilya partially addressed the same datapath
flow explosion issue by matching on eth.mcast6 for MLD packets.
With this patch, we now address the datapath flow explosion issue
for IPv6 traffic provided the below condition is met:
  a. All the logical ports of a logical switch are not configured
     with port security.

[1] - c48ed17 ("Do not reply on unicast arps for IPv4 targets.")
[2] - 43c34f2 ("logical-fields: Add missing multicast matches for MLD and IGMP.")

Note: Documentation for 'eth.mcastv6' and 'ip6.mcast' predicates were
missing from ovn-sb.xml and this patch adds it.

Reported-at: https://issues.redhat.com/browse/FDP-728
Reported-by: Mike Pattrick <mkp@redhat.com>
Acked-by: Dumitru Ceara <dceara@redhat.com>
Acked-by: Ilya Maximets <i.maximets@ovn.org>
Signed-off-by: Numan Siddique <numans@ovn.org>
(cherry picked from commit 2e7f318)
  • Loading branch information
numansiddique committed Aug 16, 2024
1 parent 116e660 commit f7c41b4
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 9 deletions.
3 changes: 3 additions & 0 deletions lib/logical-fields.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ ovn_init_symtab(struct shash *symtab)
"icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(symtab, "nd_ns",
"icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(symtab, "nd_ns_mcast",
"ip6.mcast && icmp6.type == 135 && icmp6.code == 0 && "
"ip.ttl == 255");
expr_symtab_add_predicate(symtab, "nd_na",
"icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(symtab, "nd_rs",
Expand Down
18 changes: 11 additions & 7 deletions northd/northd.c
Original file line number Diff line number Diff line change
Expand Up @@ -8693,16 +8693,20 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
&op->nbsp->header_);
}

/* For ND solicitations, we need to listen for both the
* unicast IPv6 address and its all-nodes multicast address,
* but always respond with the unicast IPv6 address. */
/* For ND solicitations:
* - Reply only for the all-nodes multicast address(es) of the
* logical port IPv6 address(es).
*
* - Do not reply for unicast ND solicitations. Let the target
* reply to it, so that the sender has the ability to monitor
* the target liveness via the unicast ND solicitations.
*/
for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
ds_clear(match);
ds_put_format(match,
"nd_ns && ip6.dst == {%s, %s} && nd.target == %s",
op->lsp_addrs[i].ipv6_addrs[j].addr_s,
op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s,
op->lsp_addrs[i].ipv6_addrs[j].addr_s);
"nd_ns_mcast && ip6.dst == %s && nd.target == %s",
op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s,
op->lsp_addrs[i].ipv6_addrs[j].addr_s);

ds_clear(actions);
ds_put_format(actions,
Expand Down
3 changes: 3 additions & 0 deletions ovn-sb.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,7 @@
<ul>
<li><code>eth.bcast</code> expands to <code>eth.dst == ff:ff:ff:ff:ff:ff</code></li>
<li><code>eth.mcast</code> expands to <code>eth.dst[40]</code></li>
<li><code>eth.mcastv6</code> expands to <code>eth.dst[32..47] == 0x3333</code></li>
<li><code>vlan.present</code> expands to <code>vlan.tci[12]</code></li>
<li><code>ip4</code> expands to <code>eth.type == 0x800</code></li>
<li><code>ip4.src_mcast</code> expands to
Expand All @@ -1145,8 +1146,10 @@
<li><code>ip.first_frag</code> expands to <code>ip.is_frag &amp;&amp; !ip.later_frag</code></li>
<li><code>arp</code> expands to <code>eth.type == 0x806</code></li>
<li><code>rarp</code> expands to <code>eth.type == 0x8035</code></li>
<li><code>ip6.mcast</code> expands to <code>eth.mcastv6 &amp;&amp; ip6.dst[120..127] == 0xff</code></li>
<li><code>nd</code> expands to <code>icmp6.type == {135, 136} &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
<li><code>nd_ns</code> expands to <code>icmp6.type == 135 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
<li><code>nd_ns_mcast</code> expands to <code>ip6.mcast &amp;&amp; icmp6.type == 135 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
<li><code>nd_na</code> expands to <code>icmp6.type == 136 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
<li><code>nd_rs</code> expands to <code>icmp6.type == 133 &amp;&amp;
icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
Expand Down
133 changes: 131 additions & 2 deletions tests/ovn.at
Original file line number Diff line number Diff line change
Expand Up @@ -8215,7 +8215,7 @@ done
# Complete Neighbor Solicitation packet and Neighbor Advertisement packet
# vif1 -> NS -> vif2. vif1 <- NA <- ovn-controller.
# vif2 will not receive NS packet, since ovn-controller will reply for it.
ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598fd81ce49a9480000f8163efffea1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598
ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598ff0200000000000000000001ffa1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598
na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffea1f9aefd81ce49a9480000f8163efffe9405988800e9ed60000000fd81ce49a9480000f8163efffea1f9ae0201fa163ea1f9ae

as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet
Expand Down Expand Up @@ -13915,7 +13915,7 @@ test_ns_na() {
local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5

packet=$(fmt_pkt "
Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}') /
Ether(dst='33:33:ff:ff:ff:ff', src='${src_mac}') /
IPv6(src='${src_ip}', dst='ff02::1:ff00:2') /
ICMPv6ND_NS(tgt='${dst_ip}')
")
Expand Down Expand Up @@ -36584,3 +36584,132 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=0 |grep priority=1
OVN_CLEANUP([hv1])
AT_CLEANUP
])

dnl This test checks that the megaflows translated by ovs-vswitchd
dnl don't match on IPv6 source and destination addresses for
dnl simple switching.
OVN_FOR_EACH_NORTHD([
AT_SETUP([IPv6 switching - megaflow check for IPv6 src/dst matches])
ovn_start

check ovn-nbctl ls-add sw0

check ovn-nbctl lsp-add sw0 vm0
check ovn-nbctl lsp-set-addresses vm0 "f0:00:0f:01:02:03 10.0.0.3 1000::3"

check ovn-nbctl lsp-add sw0 vm1
check ovn-nbctl lsp-set-addresses vm1 "f0:00:0f:01:02:04 10.0.0.4 1000::4"

check ovn-nbctl lr-add lr0
check ovn-nbctl lrp-add lr0 lr0-sw0 fa:16:3e:00:00:01 10.0.0.1 1000::1/64
check ovn-nbctl lsp-add sw0 sw0-lr0
check ovn-nbctl lsp-set-type sw0-lr0 router
check ovn-nbctl lsp-set-addresses sw0-lr0 router
check ovn-nbctl --wait=hv lsp-set-options sw0-lr0 router-port=lr0-sw0

net_add n1
sim_add hv
as hv
check ovs-vsctl add-br br-phys
ovn_attach n1 br-phys 192.168.0.1
check ovs-vsctl add-port br-int vif1 -- \
set Interface vif1 external-ids:iface-id=vm0 \
options:tx_pcap=hv/vif1-tx.pcap \
options:rxq_pcap=hv/vif1-rx.pcap \
ofport-request=1
check ovs-vsctl add-port br-int vif2 -- \
set Interface vif2 external-ids:iface-id=vm1 \
options:tx_pcap=hv/vif2-tx.pcap \
options:rxq_pcap=hv/vif2-rx.pcap \
ofport-request=2

check ovn-nbctl --wait=sb sync
wait_for_ports_up

AS_BOX([No port security, to vm1])
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:04', src='f0:00:0f:01:02:03')/ \
IPv6(dst='1000::4', src='1000::3')/ \
UDP(sport=53, dport=4369)")

as hv
ovs-appctl ofproto/trace br-int in_port=1 $packet > vm0_ip6_ofproto_trace.txt
ovs-appctl netdev-dummy/receive vif1 $packet

AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])

AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [1], [dnl
0
])

dnl Make sure that the packet was received by vm1. This ensures that the
dnl packet was delivered as expected and the megaflow didn't have any matches
dnl on IPv6 src or dst.

echo $packet >> expected-vif2
OVN_CHECK_PACKETS([hv/vif2-tx.pcap], [expected-vif2])

AS_BOX([No port security, to vm0])
packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='f0:00:0f:01:02:04')/ \
IPv6(dst='1000::3', src='1000::4')/ \
UDP(sport=53, dport=4369)")

as hv
ovs-appctl ofproto/trace br-int in_port=2 $packet > vm1_ip6_ofproto_trace.txt
ovs-appctl netdev-dummy/receive vif2 $packet

AT_CAPTURE_FILE([vm1_ip6_ofproto_trace.txt])

AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [1], [dnl
0
])

dnl Make sure that the packet was received by vm0. This ensures that the
dnl packet was delivered as expected and the megaflow didn't have any matches
dnl on IPv6 src or dst.
echo $packet >> expected-vif1
OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])

AS_BOX([With port security, to vm1])
dnl Add port security to vm0. The megaflow should now match on ipv6 src/dst.
check ovn-nbctl lsp-set-port-security vm0 "f0:00:0f:01:02:03 10.0.0.3 1000::3"
check ovn-nbctl --wait=hv sync

packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:04', src='f0:00:0f:01:02:03')/ \
IPv6(dst='1000::4', src='1000::3')/ \
UDP(sport=53, dport=4369)")

as hv
ovs-appctl ofproto/trace br-int in_port=1 $packet > vm0_ip6_ofproto_trace.txt
ovs-appctl netdev-dummy/receive vif1 $packet

AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])

AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [0], [dnl
1
])

dnl Make sure that the packet was received by vm1.
echo $packet >> expected-vif2
OVN_CHECK_PACKETS([hv/vif2-tx.pcap], [expected-vif2])

AS_BOX([Clear port security, to vm1])
dnl Clear port security.
check ovn-nbctl lsp-set-port-security vm0 ""
check ovn-nbctl --wait=hv sync

as hv
ovs-appctl ofproto/trace br-int in_port=1 $packet > vm0_ip6_ofproto_trace.txt
ovs-appctl netdev-dummy/receive vif1 $packet

AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])

AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [1], [dnl
0
])

dnl Make sure that the packet was received by vm1.
echo $packet >> expected-vif2
OVN_CHECK_PACKETS([hv/vif2-tx.pcap], [expected-vif2])

AT_CLEANUP
])

0 comments on commit f7c41b4

Please sign in to comment.