From 39f9d8bd226ab5aa366f181a5cbf7c873f599e06 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Thu, 16 Feb 2017 21:57:05 -0800 Subject: [PATCH] [Proxy] ARP tests Change-Id: I40d6d763b55a26cdee0afef85d1acdd19dd10dd6 Signed-off-by: Neale Ranns --- test/test_neighbor.py | 425 ++++++++++++++++++++++++++++++++++++++++++++++ test/util.py | 5 + test/vpp_interface.py | 13 ++ test/vpp_neighbor.py | 77 +++++++++ test/vpp_papi_provider.py | 67 ++++++++ 5 files changed, 587 insertions(+) create mode 100644 test/test_neighbor.py create mode 100644 test/vpp_neighbor.py 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, -- 2.16.6