aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeale Ranns <nranns@cisco.com>2017-02-16 21:57:05 -0800
committerDamjan Marion <dmarion.lists@gmail.com>2017-02-27 12:10:50 +0000
commit39f9d8bd226ab5aa366f181a5cbf7c873f599e06 (patch)
tree313fc63450ddca953a6c7ee9c1ee9260169f4003
parent3e7b569361f97368b0cad3468fac76ef2a398bfa (diff)
[Proxy] ARP tests
Change-Id: I40d6d763b55a26cdee0afef85d1acdd19dd10dd6 Signed-off-by: Neale Ranns <nranns@cisco.com>
-rw-r--r--test/test_neighbor.py425
-rw-r--r--test/util.py5
-rw-r--r--test/vpp_interface.py13
-rw-r--r--test/vpp_neighbor.py77
-rw-r--r--test/vpp_papi_provider.py67
5 files changed, 587 insertions, 0 deletions
diff --git a/test/test_neighbor.py b/test/test_neighbor.py
new file mode 100644
index 00000000000..6a60809160b
--- /dev/null
+++ b/test/test_neighbor.py
@@ -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")
diff --git a/test/util.py b/test/util.py
index 3a8bd838b00..d6b77f9d3c3 100644
--- a/test/util.py
+++ b/test/util.py
@@ -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
diff --git a/test/vpp_interface.py b/test/vpp_interface.py
index 9c904aac8fd..125d8f044b4 100644
--- a/test/vpp_interface.py
+++ b/test/vpp_interface.py
@@ -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
index 00000000000..fbd41eb5352
--- /dev/null
+++ b/test/vpp_neighbor.py
@@ -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)))
diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py
index dd9baff1479..67b3e141447 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -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,