[ovs-dev] [PATCH ovn v3] ovn-northd: Forward ARP requests on localnet ports.

Dumitru Ceara dceara at redhat.com
Tue Mar 24 10:03:29 UTC 2020


Commit 32f5ebb06226 limited the ARP/ND broadcast domain but in scenarios
where ARP responder flows are installed only on chassis that own the
associated logical ports ARP requests should still be forwarded on
localnet ports because the router pipeline should be executed on the
chassis that owns the logical port. Only that chassis will reply to the
ARP/ND request.

Reported-by: Michael Plato <michael.plato at tu-berlin.de>
Reported-at: https://mail.openvswitch.org/pipermail/ovs-discuss/2020-March/049856.html
Fixes: 32f5ebb06226 ("ovn-northd: Limit ARP/ND broadcast domain whenever possible.")
Signed-off-by: Dumitru Ceara <dceara at redhat.com>

---
V3:
- Address Numan's comment and add unit test.
V2:
- Address Numan's comment and update ovn-northd documentation.
---
 northd/ovn-northd.8.xml |   3 +-
 northd/ovn-northd.c     |   6 +-
 tests/ovn.at            | 169 ++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 162 insertions(+), 16 deletions(-)

diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 7d03cbc..1e0993e 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1194,7 +1194,8 @@ output;
         Priority-75 flows for each IP address/VIP/NAT address owned by a
         router port connected to the switch. These flows match ARP requests
         and ND packets for the specific IP addresses.  Matched packets are
-        forwarded only to the router that owns the IP address.
+        forwarded only to the router that owns the IP address and, if
+        present, to the localnet port of the logical switch.
       </li>
 
       <li>
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index f648d2e..b76df05 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -5903,8 +5903,12 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset *ips,
     ds_put_cstr(&match, "}");
 
     /* Send a the packet only to the router pipeline and skip flooding it
-     * in the broadcast domain.
+     * in the broadcast domain (except for the localnet port).
      */
+    if (od->localnet_port) {
+        ds_put_format(&actions, "clone { outport = %s; output; }; ",
+                      od->localnet_port->json_key);
+    }
     ds_put_format(&actions, "outport = %s; output;", patch_op->json_key);
     ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
                             ds_cstr(&match), ds_cstr(&actions), stage_hint);
diff --git a/tests/ovn.at b/tests/ovn.at
index 1b6073f..4baf2e9 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -17928,13 +17928,14 @@ net_add n1
 sim_add hv1
 as hv1
 ovs-vsctl add-br br-phys
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
 ovn_attach n1 br-phys 192.168.0.1
 
-ovs-vsctl -- add-port br-int hv1-vif1 -- \
-    set interface hv1-vif1 external-ids:iface-id=sw-agg-ext \
-    options:tx_pcap=hv1/vif1-tx.pcap \
-    options:rxq_pcap=hv1/vif1-rx.pcap \
-    ofport-request=1
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ovn_attach n1 br-phys 192.168.0.2
 
 # One Aggregation Switch connected to two Logical networks (routers).
 ovn-nbctl ls-add sw-agg
@@ -17950,18 +17951,66 @@ ovn-nbctl lsp-add sw-agg sw-rtr2                   \
     -- lsp-set-addresses sw-rtr2 00:00:00:00:02:00 \
     -- lsp-set-options sw-rtr2 router-port=rtr2-sw
 
-# Configure L3 interface IPv4 & IPv6 on both routers
+# Localnet port on the Aggregation Switch.
+ovn-nbctl lsp-add sw-agg sw-agg-ln
+ovn-nbctl lsp-set-addresses sw-agg-ln unknown
+ovn-nbctl lsp-set-type sw-agg-ln localnet
+ovn-nbctl lsp-set-options sw-agg-ln network_name=phys
+
+# Configure L3 interface IPv4 & IPv6 on both routers.
 ovn-nbctl lr-add rtr1
 ovn-nbctl lrp-add rtr1 rtr1-sw 00:00:00:00:01:00 10.0.0.1/24 10::1/64
 
+ovn-nbctl lrp-add rtr1 rtr1-sw1 00:00:01:00:00:00 20.0.0.1/24 20::1/64
+
 ovn-nbctl lr-add rtr2
 ovn-nbctl lrp-add rtr2 rtr2-sw 00:00:00:00:02:00 10.0.0.2/24 10::2/64
 
+# Configure router gateway ports.
+ovn-nbctl lrp-set-gateway-chassis rtr1-sw hv1 20
+ovn-nbctl lrp-set-gateway-chassis rtr2-sw hv1 20
+
+# One private network behind rtr1 with two VMs.
+ovn-nbctl ls-add sw1
+ovn-nbctl lsp-add sw1 sw1-p1 \
+    -- lsp-set-addresses sw1-p1 00:00:00:01:00:00
+ovn-nbctl lsp-add sw1 sw1-p2 \
+    -- lsp-set-addresses sw1-p2 00:00:00:02:00:00
+ovn-nbctl lsp-add sw1 sw1-rtr1                       \
+    -- lsp-set-type sw1-rtr1 router                  \
+    -- lsp-set-addresses sw1-rtr1 00:00:01:00:00:00  \
+    -- lsp-set-options sw1-rtr1 router-port=rtr1-sw1
+
+# Bind a "VM" connected to sw-agg on hv1.
+as hv1
+ovs-vsctl -- add-port br-int hv1-vif0 -- \
+    set interface hv1-vif0 external-ids:iface-id=sw-agg-ext \
+    options:tx_pcap=hv1/vif0-tx.pcap \
+    options:rxq_pcap=hv1/vif0-rx.pcap \
+    ofport-request=1
+
+# Bind a "VM" connected to sw1 on hv1.
+as hv1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=sw1-p1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=2
+
+# Bind a "VM" connected to sw1 on hv2.
+as hv2
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=sw1-p2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=3
+
 OVN_POPULATE_ARP
 ovn-nbctl --wait=hv sync
 
 sw_dp_uuid=$(ovn-sbctl --bare --columns _uuid list datapath_binding sw-agg)
 sw_dp_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding sw-agg)
+r1_dp_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding rtr1)
 
 r1_tnl_key=$(ovn-sbctl --bare --columns tunnel_key list port_binding sw-rtr1)
 r2_tnl_key=$(ovn-sbctl --bare --columns tunnel_key list port_binding sw-rtr2)
@@ -17970,9 +18019,10 @@ mc_key=$(ovn-sbctl --bare --columns tunnel_key find multicast_group datapath=${s
 mc_key=$(printf "%04x" $mc_key)
 
 match_sw_metadata="metadata=0x${sw_dp_key}"
+match_r1_metadata="metadata=0x${r1_dp_key}"
 
 # Inject ARP request for first router owned IP address.
-send_arp_request 1 1 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 1)
+send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 1)
 
 # Verify that the ARP request is sent only to rtr1.
 match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.1,arp_op=1"
@@ -18001,7 +18051,7 @@ OVS_WAIT_UNTIL([
 # Inject ND_NS for ofirst router owned IP address.
 src_ipv6=00100000000000000000000000000254
 dst_ipv6=00100000000000000000000000000001
-send_nd_ns 1 1 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
+send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
 
 # Verify that the ND_NS is sent only to rtr1.
 match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::1"
@@ -18038,7 +18088,7 @@ ovn-nbctl lr-lb-add rtr2 lb2-v6
 ovn-nbctl --wait=hv sync
 
 # Inject ARP request for first router owned VIP address.
-send_arp_request 1 1 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 11)
+send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 11)
 
 # Verify that the ARP request is sent only to rtr1.
 match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.11,arp_op=1"
@@ -18067,7 +18117,7 @@ OVS_WAIT_UNTIL([
 # Inject ND_NS for first router owned VIP address.
 src_ipv6=00100000000000000000000000000254
 dst_ipv6=00100000000000000000000000000011
-send_nd_ns 1 1 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
+send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
 
 # Verify that the ND_NS is sent only to rtr1.
 match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::11"
@@ -18091,14 +18141,21 @@ OVS_WAIT_UNTIL([
     test "0" = "${pkts_flooded}"
 ])
 
-# Configure NAT on both routers
+# Configure NAT on both routers.
 ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.111 42.42.42.1
 ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::111 42::1
 ovn-nbctl lr-nat-add rtr2 dnat_and_snat 10.0.0.222 42.42.42.2
 ovn-nbctl lr-nat-add rtr2 dnat_and_snat 10::222 42::2
 
+# Configure FIP1 and FIP2 on rtr1 for sw1-p1 and sw1-p2.
+ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.121 20.0.0.11 sw1-p1 00:00:00:01:00:00
+ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::121 20::11 sw1-p1 00:00:00:01:00:00
+ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10.0.0.122 20.0.0.12 sw1-p2 00:00:00:02:00:00
+ovn-nbctl lr-nat-add rtr1 dnat_and_snat 10::122 20::12 sw1-p2 00:00:00:02:00:00
+ovn-nbctl --wait=hv sync
+
 # Inject ARP request for first router owned NAT address.
-send_arp_request 1 1 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 111)
+send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 111)
 
 # Verify that the ARP request is sent only to rtr1.
 match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.111,arp_op=1"
@@ -18124,10 +18181,50 @@ OVS_WAIT_UNTIL([
     test "0" = "${pkts_flooded}"
 ])
 
+# Inject ARP request for FIP1.
+send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 121)
+
+# Verify that the ARP request is replied to from hv1 and not hv2.
+match_arp_req="priority=90.*${match_r1_metadata}.*arp_tpa=10.0.0.121,arp_op=1"
+
+as hv1
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_arp_req}" | grep n_packets=1 -c)
+    test "1" = "${pkts_on_rtr1}"
+])
+
+as hv2
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_arp_req}" | grep n_packets=1 -c)
+    test "0" = "${pkts_on_rtr1}"
+])
+
+# Inject ARP request for FIP2.
+send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 122)
+
+# Verify that the ARP request is replied to from hv2 and not hv1.
+match_arp_req="priority=90.*${match_r1_metadata}.*arp_tpa=10.0.0.122,arp_op=1"
+
+as hv2
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_arp_req}" | grep n_packets=1 -c)
+    test "1" = "${pkts_on_rtr1}"
+])
+
+as hv1
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_arp_req}" | grep n_packets=1 -c)
+    test "0" = "${pkts_on_rtr1}"
+])
+
 # Inject ND_NS for first router owned IP address.
 src_ipv6=00100000000000000000000000000254
 dst_ipv6=00100000000000000000000000000111
-send_nd_ns 1 1 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
+send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
 
 # Verify that the ND_NS is sent only to rtr1.
 match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::111"
@@ -18151,7 +18248,51 @@ OVS_WAIT_UNTIL([
     test "0" = "${pkts_flooded}"
 ])
 
-OVN_CLEANUP([hv1])
+# Inject ND_NS for FIP1.
+src_ipv6=00100000000000000000000000000254
+dst_ipv6=00100000000000000000000000000121
+send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 72dd
+
+# Verify that the ND_NS is replied to from hv1 and not hv2.
+match_nd_ns="priority=90.*${match_r1_metadata}.*icmp_type=135.*nd_target=10::121"
+
+as hv1
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_nd_ns}" | grep n_packets=1 -c)
+    test "1" = "${pkts_on_rtr1}"
+])
+
+as hv2
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_nd_ns}" | grep n_packets=1 -c)
+    test "0" = "${pkts_on_rtr1}"
+])
+
+# Inject ND_NS for FIP2.
+src_ipv6=00100000000000000000000000000254
+dst_ipv6=00100000000000000000000000000122
+send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 72db
+
+# Verify that the ND_NS is replied to from hv2 and not hv1.
+match_nd_ns="priority=90.*${match_r1_metadata}.*icmp_type=135.*nd_target=10::122"
+
+as hv2
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_nd_ns}" | grep n_packets=1 -c)
+    test "1" = "${pkts_on_rtr1}"
+])
+
+as hv1
+OVS_WAIT_UNTIL([
+    pkts_on_rtr1=$(ovs-ofctl dump-flows br-int | \
+    grep -E "${match_nd_ns}" | grep n_packets=1 -c)
+    test "0" = "${pkts_on_rtr1}"
+])
+
+OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
 
 AT_SETUP([ovn -- trace when flow cookie updated])
-- 
1.8.3.1



More information about the dev mailing list