[Proxy] ARP tests 03/5503/6
authorNeale Ranns <nranns@cisco.com>
Fri, 17 Feb 2017 05:57:05 +0000 (21:57 -0800)
committerDamjan Marion <dmarion.lists@gmail.com>
Mon, 27 Feb 2017 12:10:50 +0000 (12:10 +0000)
Change-Id: I40d6d763b55a26cdee0afef85d1acdd19dd10dd6
Signed-off-by: Neale Ranns <nranns@cisco.com>
test/test_neighbor.py [new file with mode: 0644]
test/util.py
test/vpp_interface.py
test/vpp_neighbor.py [new file with mode: 0644]
test/vpp_papi_provider.py

diff --git a/test/test_neighbor.py b/test/test_neighbor.py
new file mode 100644 (file)
index 0000000..6a60809
--- /dev/null
@@ -0,0 +1,425 @@
+#!/usr/bin/env python
+
+import unittest
+from socket import AF_INET, AF_INET6, inet_pton
+
+from framework import VppTestCase, VppTestRunner
+from vpp_neighbor import VppNeighbor, find_nbr
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, ARP
+from scapy.layers.inet import IP, UDP
+
+# not exported by scapy, so redefined here
+arp_opts = {"who-has": 1, "is-at": 2}
+
+
+class ARPTestCase(VppTestCase):
+    """ ARP Test Case """
+
+    def setUp(self):
+        super(ARPTestCase, self).setUp()
+
+        # create 3 pg interfaces
+        self.create_pg_interfaces(range(4))
+
+        # pg0 configured with ip4 and 6 addresses used for input
+        # pg1 configured with ip4 and 6 addresses used for output
+        # pg2 is unnumbered to pg0
+        for i in self.pg_interfaces:
+            i.admin_up()
+
+        self.pg0.config_ip4()
+        self.pg0.config_ip6()
+        self.pg0.resolve_arp()
+
+        self.pg1.config_ip4()
+        self.pg1.config_ip6()
+
+        # pg3 in a different VRF
+        self.pg3.set_table_ip4(1)
+        self.pg3.config_ip4()
+
+    def verify_arp_req(self, rx, smac, sip, dip):
+        ether = rx[Ether]
+        self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
+        self.assertEqual(ether.src, smac)
+
+        arp = rx[ARP]
+        self.assertEqual(arp.hwtype, 1)
+        self.assertEqual(arp.ptype, 0x800)
+        self.assertEqual(arp.hwlen, 6)
+        self.assertEqual(arp.plen, 4)
+        self.assertEqual(arp.op, arp_opts["who-has"])
+        self.assertEqual(arp.hwsrc, smac)
+        self.assertEqual(arp.hwdst, "00:00:00:00:00:00")
+        self.assertEqual(arp.psrc, sip)
+        self.assertEqual(arp.pdst, dip)
+
+    def verify_arp_resp(self, rx, smac, dmac, sip, dip):
+        ether = rx[Ether]
+        self.assertEqual(ether.dst, dmac)
+        self.assertEqual(ether.src, smac)
+
+        arp = rx[ARP]
+        self.assertEqual(arp.hwtype, 1)
+        self.assertEqual(arp.ptype, 0x800)
+        self.assertEqual(arp.hwlen, 6)
+        self.assertEqual(arp.plen, 4)
+        self.assertEqual(arp.op, arp_opts["is-at"])
+        self.assertEqual(arp.hwsrc, smac)
+        self.assertEqual(arp.hwdst, dmac)
+        self.assertEqual(arp.psrc, sip)
+        self.assertEqual(arp.pdst, dip)
+
+    def verify_ip(self, rx, smac, dmac, sip, dip):
+        ether = rx[Ether]
+        self.assertEqual(ether.dst, dmac)
+        self.assertEqual(ether.src, smac)
+
+        ip = rx[IP]
+        self.assertEqual(ip.src, sip)
+        self.assertEqual(ip.dst, dip)
+
+    def send_and_assert_no_replies(self, intf, pkts, remark):
+        intf.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        for i in self.pg_interfaces:
+            i.assert_nothing_captured(remark=remark)
+
+    def test_arp(self):
+        """ ARP """
+
+        #
+        # Generate some hosts on the LAN
+        #
+        self.pg1.generate_remote_hosts(4)
+
+        #
+        # Send IP traffic to one of these unresolved hosts.
+        #  expect the generation of an ARP request
+        #
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1._remote_hosts[1].ip4) /
+             UDP(sport=1234, dport=1234) /
+             Raw())
+
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture(1)
+
+        self.verify_arp_req(rx[0],
+                            self.pg1.local_mac,
+                            self.pg1.local_ip4,
+                            self.pg1._remote_hosts[1].ip4)
+
+        #
+        # And a dynamic ARP entry for host 1
+        #
+        dyn_arp = VppNeighbor(self,
+                              self.pg1.sw_if_index,
+                              self.pg1.remote_hosts[1].mac,
+                              self.pg1.remote_hosts[1].ip4)
+        dyn_arp.add_vpp_config()
+
+        #
+        # now we expect IP traffic forwarded
+        #
+        dyn_p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+                 IP(src=self.pg0.remote_ip4,
+                    dst=self.pg1._remote_hosts[1].ip4) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw())
+
+        self.pg0.add_stream(dyn_p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture(1)
+
+        self.verify_ip(rx[0],
+                       self.pg1.local_mac,
+                       self.pg1.remote_hosts[1].mac,
+                       self.pg0.remote_ip4,
+                       self.pg1._remote_hosts[1].ip4)
+
+        #
+        # And a Static ARP entry for host 2
+        #
+        static_arp = VppNeighbor(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[2].mac,
+                                 self.pg1.remote_hosts[2].ip4,
+                                 is_static=1)
+        static_arp.add_vpp_config()
+
+        static_p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+                    IP(src=self.pg0.remote_ip4,
+                       dst=self.pg1._remote_hosts[2].ip4) /
+                    UDP(sport=1234, dport=1234) /
+                    Raw())
+
+        self.pg0.add_stream(static_p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture(1)
+
+        self.verify_ip(rx[0],
+                       self.pg1.local_mac,
+                       self.pg1.remote_hosts[2].mac,
+                       self.pg0.remote_ip4,
+                       self.pg1._remote_hosts[2].ip4)
+
+        #
+        # flap the link. dynamic ARPs get flush, statics don't
+        #
+        self.pg1.admin_down()
+        self.pg1.admin_up()
+
+        self.pg0.add_stream(static_p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        rx = self.pg1.get_capture(1)
+
+        self.verify_ip(rx[0],
+                       self.pg1.local_mac,
+                       self.pg1.remote_hosts[2].mac,
+                       self.pg0.remote_ip4,
+                       self.pg1._remote_hosts[2].ip4)
+
+        self.pg0.add_stream(dyn_p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture(1)
+        self.verify_arp_req(rx[0],
+                            self.pg1.local_mac,
+                            self.pg1.local_ip4,
+                            self.pg1._remote_hosts[1].ip4)
+
+        #
+        # Send an ARP request from one of the so-far unlearned remote hosts
+        #
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                   src=self.pg1._remote_hosts[3].mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg1._remote_hosts[3].mac,
+                 pdst=self.pg1.local_ip4,
+                 psrc=self.pg1._remote_hosts[3].ip4))
+
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture(1)
+        self.verify_arp_resp(rx[0],
+                             self.pg1.local_mac,
+                             self.pg1._remote_hosts[3].mac,
+                             self.pg1.local_ip4,
+                             self.pg1._remote_hosts[3].ip4)
+
+        #
+        # VPP should have learned the mapping for the remote host
+        #
+        self.assertTrue(find_nbr(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1._remote_hosts[3].ip4))
+
+        #
+        # ERROR Cases
+        #  1 - don't respond to ARP request for address not within the
+        #      interface's sub-net
+        #
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg0.remote_mac,
+                 pdst="10.10.10.3",
+                 psrc=self.pg0.remote_ip4))
+        self.send_and_assert_no_replies(self.pg0, p,
+                                        "ARP req for non-local destination")
+
+        #
+        #  2 - don't respond to ARP request from an address not within the
+        #      interface's sub-net
+        #
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg0.remote_mac,
+                 psrc="10.10.10.3",
+                 pdst=self.pg0.local_ip4))
+        self.send_and_assert_no_replies(self.pg0, p,
+                                        "ARP req for non-local source")
+
+        #
+        #  3 - don't respond to ARP request from an address that belongs to
+        #      the router
+        #
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg0.remote_mac,
+                 psrc=self.pg0.local_ip4,
+                 pdst=self.pg0.local_ip4))
+        self.send_and_assert_no_replies(self.pg0, p,
+                                        "ARP req for non-local source")
+
+        #
+        #  4 - don't respond to ARP requests that has mac source different
+        #      from ARP request HW source
+        #      the router
+        #
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc="00:00:00:DE:AD:BE",
+                 psrc=self.pg0.remote_ip4,
+                 pdst=self.pg0.local_ip4))
+        self.send_and_assert_no_replies(self.pg0, p,
+                                        "ARP req for non-local source")
+
+        #
+        # cleanup
+        #
+        dyn_arp.remove_vpp_config()
+        static_arp.remove_vpp_config()
+
+    def test_proxy_arp(self):
+        """ Proxy ARP """
+
+        #
+        # Proxy ARP rewquest packets for each interface
+        #
+        arp_req_pg2 = (Ether(src=self.pg2.remote_mac,
+                             dst="ff:ff:ff:ff:ff:ff") /
+                       ARP(op="who-has",
+                           hwsrc=self.pg2.remote_mac,
+                           pdst="10.10.10.3",
+                           psrc=self.pg1.remote_ip4))
+        arp_req_pg0 = (Ether(src=self.pg0.remote_mac,
+                             dst="ff:ff:ff:ff:ff:ff") /
+                       ARP(op="who-has",
+                           hwsrc=self.pg0.remote_mac,
+                           pdst="10.10.10.3",
+                           psrc=self.pg0.remote_ip4))
+        arp_req_pg1 = (Ether(src=self.pg1.remote_mac,
+                             dst="ff:ff:ff:ff:ff:ff") /
+                       ARP(op="who-has",
+                           hwsrc=self.pg1.remote_mac,
+                           pdst="10.10.10.3",
+                           psrc=self.pg1.remote_ip4))
+        arp_req_pg3 = (Ether(src=self.pg3.remote_mac,
+                             dst="ff:ff:ff:ff:ff:ff") /
+                       ARP(op="who-has",
+                           hwsrc=self.pg3.remote_mac,
+                           pdst="10.10.10.3",
+                           psrc=self.pg3.remote_ip4))
+
+        #
+        # Configure Proxy ARP for 10.10.10.0 -> 10.10.10.124
+        #
+        self.vapi.proxy_arp_add_del(inet_pton(AF_INET, "10.10.10.2"),
+                                    inet_pton(AF_INET, "10.10.10.124"))
+
+        #
+        # No responses are sent when the interfaces are not enabled for proxy
+        # ARP
+        #
+        self.send_and_assert_no_replies(self.pg0, arp_req_pg0,
+                                        "ARP req from unconfigured interface")
+        self.send_and_assert_no_replies(self.pg2, arp_req_pg2,
+                                        "ARP req from unconfigured interface")
+
+        #
+        # Make pg2 un-numbered to pg1
+        #  still won't reply.
+        #
+        self.pg2.set_unnumbered(self.pg1.sw_if_index)
+
+        self.send_and_assert_no_replies(self.pg2, arp_req_pg2,
+                                        "ARP req from unnumbered interface")
+
+        #
+        # Enable each interface to reply to proxy ARPs
+        #
+        for i in self.pg_interfaces:
+            i.set_proxy_arp()
+
+        #
+        # Now each of the interfaces should reply to a request to a proxied
+        # address
+        #
+        self.pg0.add_stream(arp_req_pg0)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg0.get_capture(1)
+        self.verify_arp_resp(rx[0],
+                             self.pg0.local_mac,
+                             self.pg0.remote_mac,
+                             "10.10.10.3",
+                             self.pg0.remote_ip4)
+
+        self.pg1.add_stream(arp_req_pg1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg1.get_capture(1)
+        self.verify_arp_resp(rx[0],
+                             self.pg1.local_mac,
+                             self.pg1.remote_mac,
+                             "10.10.10.3",
+                             self.pg1.remote_ip4)
+
+        self.pg2.add_stream(arp_req_pg2)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg2.get_capture(1)
+        self.verify_arp_resp(rx[0],
+                             self.pg2.local_mac,
+                             self.pg2.remote_mac,
+                             "10.10.10.3",
+                             self.pg1.remote_ip4)
+
+        #
+        # A request for an address out of the configured range
+        #
+        arp_req_pg1_hi = (Ether(src=self.pg1.remote_mac,
+                                dst="ff:ff:ff:ff:ff:ff") /
+                          ARP(op="who-has",
+                              hwsrc=self.pg1.remote_mac,
+                              pdst="10.10.10.125",
+                              psrc=self.pg1.remote_ip4))
+        self.send_and_assert_no_replies(self.pg1, arp_req_pg1_hi,
+                                        "ARP req out of range HI")
+        arp_req_pg1_low = (Ether(src=self.pg1.remote_mac,
+                                 dst="ff:ff:ff:ff:ff:ff") /
+                           ARP(op="who-has",
+                               hwsrc=self.pg1.remote_mac,
+                               pdst="10.10.10.1",
+                               psrc=self.pg1.remote_ip4))
+        self.send_and_assert_no_replies(self.pg1, arp_req_pg1_low,
+                                        "ARP req out of range Low")
+
+        #
+        # Request for an address in the proxy range but from an interface
+        # in a different VRF
+        #
+        self.send_and_assert_no_replies(self.pg3, arp_req_pg3,
+                                        "ARP req from different VRF")
+
+        #
+        # Disable Each interface for proxy ARP
+        #  - expect none to respond
+        #
+        for i in self.pg_interfaces:
+            i.set_proxy_arp(0)
+
+        self.send_and_assert_no_replies(self.pg0, arp_req_pg0,
+                                        "ARP req from disable")
+        self.send_and_assert_no_replies(self.pg1, arp_req_pg1,
+                                        "ARP req from disable")
+        self.send_and_assert_no_replies(self.pg2, arp_req_pg2,
+                                        "ARP req from disable")
index 3a8bd83..d6b77f9 100644 (file)
@@ -47,6 +47,11 @@ def ip4n_range(ip4n, s, e):
             for ip in ip4_range(ip4, s, e))
 
 
+def mactobinary(mac):
+    """ Convert the : separated format into binary packet data for the API """
+    return mac.replace(':', '').decode('hex')
+
+
 class NumericConstant(object):
     __metaclass__ = ABCMeta
 
index 9c904aa..125d8f0 100644 (file)
@@ -2,6 +2,7 @@ from abc import abstractmethod, ABCMeta
 import socket
 
 from util import Host
+from vpp_neighbor import VppNeighbor
 
 
 class VppInterface(object):
@@ -316,3 +317,15 @@ class VppInterface(object):
                i.table_id == self.ip4_table_id:
                 return True
         return False
+
+    def set_unnumbered(self, ip_sw_if_index):
+        """ Set the interface to unnumbered via ip_sw_if_index """
+        self.test.vapi.sw_interface_set_unnumbered(
+            self.sw_if_index,
+            ip_sw_if_index)
+
+    def set_proxy_arp(self, enable=1):
+        """ Set the interface to enable/disable Proxy ARP """
+        self.test.vapi.proxy_arp_intfc_enable_disable(
+            self.sw_if_index,
+            enable)
diff --git a/test/vpp_neighbor.py b/test/vpp_neighbor.py
new file mode 100644 (file)
index 0000000..fbd41eb
--- /dev/null
@@ -0,0 +1,77 @@
+"""
+  Neighbour Entries
+
+  object abstractions for ARP and ND
+"""
+
+from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
+from vpp_object import *
+from util import mactobinary
+
+
+def find_nbr(test, sw_if_index, ip_addr, is_static=0, inet=AF_INET):
+    nbrs = test.vapi.ip_neighbor_dump(sw_if_index,
+                                      is_ipv6=1 if AF_INET6 == inet else 0)
+    if inet == AF_INET:
+        s = 4
+    else:
+        s = 16
+    nbr_addr = inet_pton(inet, ip_addr)
+
+    for n in nbrs:
+        if nbr_addr == n.ip_address[:s] \
+           and is_static == n.is_static:
+            return True
+    return False
+
+
+class VppNeighbor(VppObject):
+    """
+    ARP Entry
+    """
+
+    def __init__(self, test, sw_if_index, mac_addr, nbr_addr,
+                 af=AF_INET, is_static=0):
+        self._test = test
+        self.sw_if_index = sw_if_index
+        self.mac_addr = mactobinary(mac_addr)
+        self.af = af
+        self.is_static = is_static
+        self.nbr_addr = inet_pton(af, nbr_addr)
+
+    def add_vpp_config(self):
+        self._test.vapi.ip_neighbor_add_del(
+            self.sw_if_index,
+            self.mac_addr,
+            self.nbr_addr,
+            is_add=1,
+            is_ipv6=1 if AF_INET6 == self.af else 0,
+            is_static=self.is_static)
+        self._test.registry.register(self, self._test.logger)
+
+    def remove_vpp_config(self):
+        self._test.vapi.ip_neighbor_add_del(
+            self.sw_if_index,
+            self.mac_addr,
+            self.nbr_addr,
+            is_ipv6=1 if AF_INET6 == self.af else 0,
+            is_add=0,
+            is_static=self.is_static)
+
+    def query_vpp_config(self):
+        dump = self._test.vapi.ip_neighbor_dump(
+            self.sw_if_index,
+            is_ipv6=1 if AF_INET6 == self.af else 0)
+        for n in dump:
+            if self.nbr_addr == n.ip_address \
+               and self.is_static == n.is_static:
+                return True
+        return False
+
+    def __str__(self):
+        return self.object_id()
+
+    def object_id(self):
+        return ("%d:%s"
+                % (self.sw_if_index,
+                   inet_ntop(self.af, self.nbr_addr)))
index dd9baff..67b3e14 100644 (file)
@@ -240,6 +240,20 @@ class VppPapiProvider(object):
                          'address_length': addr_len,
                          'address': addr})
 
+    def sw_interface_set_unnumbered(self, sw_if_index, ip_sw_if_index,
+                                    is_add=1):
+        """ Set the Interface to be unnumbered
+
+        :param is_add:  (Default value = 1)
+        :param sw_if_index - interface That will be unnumbered
+        :param ip_sw_if_index - interface with an IP addres
+
+        """
+        return self.api(self.papi.sw_interface_set_unnumbered,
+                        {'sw_if_index': ip_sw_if_index,
+                         'unnumbered_sw_if_index': sw_if_index,
+                         'is_add': is_add})
+
     def sw_interface_enable_disable_mpls(self, sw_if_index,
                                          is_enable=1):
         """
@@ -638,6 +652,59 @@ class VppPapiProvider(object):
              }
         )
 
+    def ip_neighbor_dump(self,
+                         sw_if_index,
+                         is_ipv6=0):
+        """ Return IP neighbor dump.
+
+        :param sw_if_index:
+        :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0)
+        """
+
+        return self.api(
+            self.papi.ip_neighbor_dump,
+            {'is_ipv6': is_ipv6,
+             'sw_if_index': sw_if_index
+             }
+        )
+
+    def proxy_arp_add_del(self,
+                          low_address,
+                          hi_address,
+                          vrf_id=0,
+                          is_add=1):
+        """ Config Proxy Arp Range.
+
+        :param low_address: Start address in the rnage to Proxy for
+        :param hi_address: End address in the rnage to Proxy for
+        :param vrf_id: The VRF/table in which to proxy
+        """
+
+        return self.api(
+            self.papi.proxy_arp_add_del,
+            {'vrf_id': vrf_id,
+             'is_add': is_add,
+             'low_address': low_address,
+             'hi_address': hi_address,
+             }
+        )
+
+    def proxy_arp_intfc_enable_disable(self,
+                                       sw_if_index,
+                                       is_enable=1):
+        """ Enable/Disable an interface for proxy ARP requests
+
+        :param sw_if_index: Interface
+        :param enable_disable: Enable/Disable
+        """
+
+        return self.api(
+            self.papi.proxy_arp_intfc_enable_disable,
+            {'sw_if_index': sw_if_index,
+             'enable_disable': is_enable
+             }
+        )
+
     def reset_vrf(self,
                   vrf_id,
                   is_ipv6=0,