24b170aac5
When VPP has an interface whose address is also applied to a TAP interface on the host, then VPP's TAP interface will be unnumbered to the 'real' interface and do proxy ARP from the host. the curious aspect of this setup is that ARP requests from the host will come from the VPP's own address. Change-Id: Ia238790e1034ba3cd3facdab29387b65a31525f2 Signed-off-by: Neale Ranns <nranns@cisco.com>
1086 lines
38 KiB
Python
1086 lines
38 KiB
Python
#!/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 vpp_ip_route import VppIpRoute, VppRoutePath, find_route
|
|
|
|
from scapy.packet import Raw
|
|
from scapy.layers.l2 import Ether, ARP, Dot1Q
|
|
from scapy.layers.inet import IP, UDP
|
|
from scapy.contrib.mpls import MPLS
|
|
|
|
# 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 tearDown(self):
|
|
super(ARPTestCase, self).tearDown()
|
|
self.pg0.unconfig_ip4()
|
|
self.pg0.unconfig_ip6()
|
|
|
|
self.pg1.unconfig_ip4()
|
|
self.pg1.unconfig_ip6()
|
|
|
|
self.pg3.unconfig_ip4()
|
|
|
|
for i in self.pg_interfaces:
|
|
i.admin_down()
|
|
|
|
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_arp_vrrp_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.assertNotEqual(arp.hwsrc, smac)
|
|
self.assertTrue("00:00:5e:00:01" in arp.hwsrc or
|
|
"00:00:5E:00:01" in arp.hwsrc)
|
|
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 verify_ip_o_mpls(self, rx, smac, dmac, label, sip, dip):
|
|
ether = rx[Ether]
|
|
self.assertEqual(ether.dst, dmac)
|
|
self.assertEqual(ether.src, smac)
|
|
|
|
mpls = rx[MPLS]
|
|
self.assertTrue(mpls.label, label)
|
|
|
|
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()
|
|
timeout = 1
|
|
for i in self.pg_interfaces:
|
|
i.get_capture(0, timeout=timeout)
|
|
i.assert_nothing_captured(remark=remark)
|
|
timeout = 0.1
|
|
|
|
def test_arp(self):
|
|
""" ARP """
|
|
|
|
#
|
|
# Generate some hosts on the LAN
|
|
#
|
|
self.pg1.generate_remote_hosts(11)
|
|
|
|
#
|
|
# 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))
|
|
#
|
|
# Fire in an ARP request before the interface becomes IP enabled
|
|
#
|
|
self.pg2.generate_remote_hosts(4)
|
|
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.remote_mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg2.remote_hosts[3].ip4))
|
|
pt = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
|
|
Dot1Q(vlan=0) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.remote_mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg2.remote_hosts[3].ip4))
|
|
self.send_and_assert_no_replies(self.pg2, p,
|
|
"interface not IP enabled")
|
|
|
|
#
|
|
# Make pg2 un-numbered to pg1
|
|
#
|
|
self.pg2.set_unnumbered(self.pg1.sw_if_index)
|
|
|
|
#
|
|
# We should respond to ARP requests for the unnumbered to address
|
|
# once an attached route to the source is known
|
|
#
|
|
self.send_and_assert_no_replies(
|
|
self.pg2, p,
|
|
"ARP req for unnumbered address - no source")
|
|
|
|
attached_host = VppIpRoute(self, self.pg2.remote_hosts[3].ip4, 32,
|
|
[VppRoutePath("0.0.0.0",
|
|
self.pg2.sw_if_index)])
|
|
attached_host.add_vpp_config()
|
|
|
|
self.pg2.add_stream(p)
|
|
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,
|
|
self.pg1.local_ip4,
|
|
self.pg2.remote_hosts[3].ip4)
|
|
|
|
self.pg2.add_stream(pt)
|
|
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,
|
|
self.pg1.local_ip4,
|
|
self.pg2.remote_hosts[3].ip4)
|
|
|
|
#
|
|
# A neighbor entry that has no associated FIB-entry
|
|
#
|
|
arp_no_fib = VppNeighbor(self,
|
|
self.pg1.sw_if_index,
|
|
self.pg1.remote_hosts[4].mac,
|
|
self.pg1.remote_hosts[4].ip4,
|
|
is_no_fib_entry=1)
|
|
arp_no_fib.add_vpp_config()
|
|
|
|
#
|
|
# check we have the neighbor, but no route
|
|
#
|
|
self.assertTrue(find_nbr(self,
|
|
self.pg1.sw_if_index,
|
|
self.pg1._remote_hosts[4].ip4))
|
|
self.assertFalse(find_route(self,
|
|
self.pg1._remote_hosts[4].ip4,
|
|
32))
|
|
#
|
|
# pg2 is unnumbered to pg1, so we can form adjacencies out of pg2
|
|
# from within pg1's subnet
|
|
#
|
|
arp_unnum = VppNeighbor(self,
|
|
self.pg2.sw_if_index,
|
|
self.pg1.remote_hosts[5].mac,
|
|
self.pg1.remote_hosts[5].ip4)
|
|
arp_unnum.add_vpp_config()
|
|
|
|
p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
|
|
IP(src=self.pg0.remote_ip4,
|
|
dst=self.pg1._remote_hosts[5].ip4) /
|
|
UDP(sport=1234, dport=1234) /
|
|
Raw())
|
|
|
|
self.pg0.add_stream(p)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx = self.pg2.get_capture(1)
|
|
|
|
self.verify_ip(rx[0],
|
|
self.pg2.local_mac,
|
|
self.pg1.remote_hosts[5].mac,
|
|
self.pg0.remote_ip4,
|
|
self.pg1._remote_hosts[5].ip4)
|
|
|
|
#
|
|
# ARP requests from hosts in pg1's subnet sent on pg2 are replied to
|
|
# with the unnumbered interface's address as the source
|
|
#
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.remote_mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg1.remote_hosts[6].ip4))
|
|
|
|
self.pg2.add_stream(p)
|
|
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,
|
|
self.pg1.local_ip4,
|
|
self.pg1.remote_hosts[6].ip4)
|
|
|
|
#
|
|
# An attached host route out of pg2 for an undiscovered hosts generates
|
|
# an ARP request with the unnumbered address as the source
|
|
#
|
|
att_unnum = VppIpRoute(self, self.pg1.remote_hosts[7].ip4, 32,
|
|
[VppRoutePath("0.0.0.0",
|
|
self.pg2.sw_if_index)])
|
|
att_unnum.add_vpp_config()
|
|
|
|
p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
|
|
IP(src=self.pg0.remote_ip4,
|
|
dst=self.pg1._remote_hosts[7].ip4) /
|
|
UDP(sport=1234, dport=1234) /
|
|
Raw())
|
|
|
|
self.pg0.add_stream(p)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx = self.pg2.get_capture(1)
|
|
|
|
self.verify_arp_req(rx[0],
|
|
self.pg2.local_mac,
|
|
self.pg1.local_ip4,
|
|
self.pg1._remote_hosts[7].ip4)
|
|
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.remote_mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg1.remote_hosts[7].ip4))
|
|
|
|
self.pg2.add_stream(p)
|
|
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,
|
|
self.pg1.local_ip4,
|
|
self.pg1.remote_hosts[7].ip4)
|
|
|
|
#
|
|
# An attached host route as yet unresolved out of pg2 for an
|
|
# undiscovered host, an ARP requests begets a response.
|
|
#
|
|
att_unnum1 = VppIpRoute(self, self.pg1.remote_hosts[8].ip4, 32,
|
|
[VppRoutePath("0.0.0.0",
|
|
self.pg2.sw_if_index)])
|
|
att_unnum1.add_vpp_config()
|
|
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.remote_mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg1.remote_hosts[8].ip4))
|
|
|
|
self.pg2.add_stream(p)
|
|
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,
|
|
self.pg1.local_ip4,
|
|
self.pg1.remote_hosts[8].ip4)
|
|
|
|
#
|
|
# Send an ARP request from one of the so-far unlearned remote hosts
|
|
# with a VLAN0 tag
|
|
#
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff",
|
|
src=self.pg1._remote_hosts[9].mac) /
|
|
Dot1Q(vlan=0) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg1._remote_hosts[9].mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg1._remote_hosts[9].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[9].mac,
|
|
self.pg1.local_ip4,
|
|
self.pg1._remote_hosts[9].ip4)
|
|
|
|
#
|
|
# Add a hierachy of routes for a host in the sub-net.
|
|
# Should still get an ARP resp since the cover is attached
|
|
#
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg1.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg1.remote_mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg1.remote_hosts[10].ip4))
|
|
|
|
r1 = VppIpRoute(self, self.pg1.remote_hosts[10].ip4, 30,
|
|
[VppRoutePath(self.pg1.remote_hosts[10].ip4,
|
|
self.pg1.sw_if_index)])
|
|
r1.add_vpp_config()
|
|
|
|
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_mac,
|
|
self.pg1.local_ip4,
|
|
self.pg1.remote_hosts[10].ip4)
|
|
|
|
r2 = VppIpRoute(self, self.pg1.remote_hosts[10].ip4, 32,
|
|
[VppRoutePath(self.pg1.remote_hosts[10].ip4,
|
|
self.pg1.sw_if_index)])
|
|
r2.add_vpp_config()
|
|
|
|
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_mac,
|
|
self.pg1.local_ip4,
|
|
self.pg1.remote_hosts[10].ip4)
|
|
|
|
#
|
|
# add an ARP entry that's not on the sub-net and so whose
|
|
# adj-fib fails the refinement check. then send an ARP request
|
|
# from that source
|
|
#
|
|
a1 = VppNeighbor(self,
|
|
self.pg0.sw_if_index,
|
|
self.pg0.remote_mac,
|
|
"100.100.100.50")
|
|
a1.add_vpp_config()
|
|
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg0.remote_mac,
|
|
psrc="100.100.100.50",
|
|
pdst=self.pg0.remote_ip4))
|
|
self.send_and_assert_no_replies(self.pg0, p,
|
|
"ARP req for from failed adj-fib")
|
|
|
|
#
|
|
# ERROR Cases
|
|
# 1 - don't respond to ARP request for address not within the
|
|
# interface's sub-net
|
|
# 1b - nor within the unnumbered subnet
|
|
# 1c - nor within the subnet of a different interface
|
|
#
|
|
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")
|
|
self.assertFalse(find_nbr(self,
|
|
self.pg0.sw_if_index,
|
|
"10.10.10.3"))
|
|
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.remote_mac,
|
|
pdst="10.10.10.3",
|
|
psrc=self.pg1.remote_hosts[7].ip4))
|
|
self.send_and_assert_no_replies(
|
|
self.pg0, p,
|
|
"ARP req for non-local destination - unnum")
|
|
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg0.remote_mac,
|
|
pdst=self.pg1.local_ip4,
|
|
psrc=self.pg1.remote_ip4))
|
|
self.send_and_assert_no_replies(self.pg0, p,
|
|
"ARP req diff sub-net")
|
|
self.assertFalse(find_nbr(self,
|
|
self.pg0.sw_if_index,
|
|
self.pg1.remote_ip4))
|
|
|
|
#
|
|
# 2 - don't respond to ARP request from an address not within the
|
|
# interface's sub-net
|
|
# 2b - to a prxied address
|
|
# 2c - not within a differents 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")
|
|
p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.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 - unnum")
|
|
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.pg1.remote_ip4,
|
|
pdst=self.pg0.local_ip4))
|
|
self.send_and_assert_no_replies(self.pg0, p,
|
|
"ARP req for non-local source 2c")
|
|
|
|
#
|
|
# 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()
|
|
self.pg2.unset_unnumbered(self.pg1.sw_if_index)
|
|
|
|
# need this to flush the adj-fibs
|
|
self.pg2.unset_unnumbered(self.pg1.sw_if_index)
|
|
self.pg2.admin_down()
|
|
self.pg1.admin_down()
|
|
|
|
def test_proxy_mirror_arp(self):
|
|
""" Interface Mirror Proxy ARP """
|
|
|
|
#
|
|
# When VPP has an interface whose address is also applied to a TAP
|
|
# interface on the host, then VPP's TAP interface will be unnumbered
|
|
# to the 'real' interface and do proxy ARP from the host.
|
|
# the curious aspect of this setup is that ARP requests from the host
|
|
# will come from the VPP's own address.
|
|
#
|
|
self.pg0.generate_remote_hosts(2)
|
|
|
|
arp_req_from_me = (Ether(src=self.pg2.remote_mac,
|
|
dst="ff:ff:ff:ff:ff:ff") /
|
|
ARP(op="who-has",
|
|
hwsrc=self.pg2.remote_mac,
|
|
pdst=self.pg0.remote_hosts[1].ip4,
|
|
psrc=self.pg0.local_ip4))
|
|
|
|
#
|
|
# Configure Proxy ARP for the subnet on PG0addresses on pg0
|
|
#
|
|
self.vapi.proxy_arp_add_del(self.pg0._local_ip4n_subnet,
|
|
self.pg0._local_ip4n_bcast)
|
|
|
|
# Make pg2 un-numbered to pg0
|
|
#
|
|
self.pg2.set_unnumbered(self.pg0.sw_if_index)
|
|
|
|
#
|
|
# Enable pg2 for proxy ARP
|
|
#
|
|
self.pg2.set_proxy_arp()
|
|
|
|
#
|
|
# Send the ARP request with an originating address that
|
|
# is VPP's own address
|
|
#
|
|
self.pg2.add_stream(arp_req_from_me)
|
|
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,
|
|
self.pg0.remote_hosts[1].ip4,
|
|
self.pg0.local_ip4)
|
|
|
|
#
|
|
# validate we have not learned an ARP entry as a result of this
|
|
#
|
|
self.assertFalse(find_nbr(self,
|
|
self.pg2.sw_if_index,
|
|
self.pg0.local_ip4))
|
|
|
|
#
|
|
# cleanup
|
|
#
|
|
self.pg2.set_proxy_arp(0)
|
|
self.vapi.proxy_arp_add_del(self.pg0._local_ip4n_subnet,
|
|
self.pg0._local_ip4n_bcast,
|
|
is_add=0)
|
|
|
|
def test_proxy_arp(self):
|
|
""" Proxy ARP """
|
|
|
|
self.pg1.generate_remote_hosts(2)
|
|
|
|
#
|
|
# Proxy ARP rewquest packets for each interface
|
|
#
|
|
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_pg0_tagged = (Ether(src=self.pg0.remote_mac,
|
|
dst="ff:ff:ff:ff:ff:ff") /
|
|
Dot1Q(vlan=0) /
|
|
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_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_hosts[1].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.pg0.add_stream(arp_req_pg0_tagged)
|
|
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_hosts[1].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")
|
|
|
|
#
|
|
# clean up on interface 2
|
|
#
|
|
self.pg2.unset_unnumbered(self.pg1.sw_if_index)
|
|
|
|
def test_mpls(self):
|
|
""" MPLS """
|
|
|
|
#
|
|
# Interface 2 does not yet have ip4 config
|
|
#
|
|
self.pg2.config_ip4()
|
|
self.pg2.generate_remote_hosts(2)
|
|
|
|
#
|
|
# Add a reoute with out going label via an ARP unresolved next-hop
|
|
#
|
|
ip_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
|
|
[VppRoutePath(self.pg2.remote_hosts[1].ip4,
|
|
self.pg2.sw_if_index,
|
|
labels=[55])])
|
|
ip_10_0_0_1.add_vpp_config()
|
|
|
|
#
|
|
# packets should generate an ARP request
|
|
#
|
|
p = (Ether(src=self.pg0.remote_mac,
|
|
dst=self.pg0.local_mac) /
|
|
IP(src=self.pg0.remote_ip4, dst="10.0.0.1") /
|
|
UDP(sport=1234, dport=1234) /
|
|
Raw('\xa5' * 100))
|
|
|
|
self.pg0.add_stream(p)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx = self.pg2.get_capture(1)
|
|
self.verify_arp_req(rx[0],
|
|
self.pg2.local_mac,
|
|
self.pg2.local_ip4,
|
|
self.pg2._remote_hosts[1].ip4)
|
|
|
|
#
|
|
# now resolve the neighbours
|
|
#
|
|
self.pg2.configure_ipv4_neighbors()
|
|
|
|
#
|
|
# Now packet should be properly MPLS encapped.
|
|
# This verifies that MPLS link-type adjacencies are completed
|
|
# when the ARP entry resolves
|
|
#
|
|
self.pg0.add_stream(p)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx = self.pg2.get_capture(1)
|
|
self.verify_ip_o_mpls(rx[0],
|
|
self.pg2.local_mac,
|
|
self.pg2.remote_hosts[1].mac,
|
|
55,
|
|
self.pg0.remote_ip4,
|
|
"10.0.0.1")
|
|
self.pg2.unconfig_ip4()
|
|
|
|
def test_arp_vrrp(self):
|
|
""" ARP reply with VRRP virtual src hw addr """
|
|
|
|
#
|
|
# IP packet destined for pg1 remote host arrives on pg0 resulting
|
|
# in an ARP request for the address of the remote host on pg1
|
|
#
|
|
p0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
|
|
IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
|
|
UDP(sport=1234, dport=1234) /
|
|
Raw())
|
|
|
|
self.pg0.add_stream(p0)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx1 = self.pg1.get_capture(1)
|
|
|
|
self.verify_arp_req(rx1[0],
|
|
self.pg1.local_mac,
|
|
self.pg1.local_ip4,
|
|
self.pg1.remote_ip4)
|
|
|
|
#
|
|
# ARP reply for address of pg1 remote host arrives on pg1 with
|
|
# the hw src addr set to a value in the VRRP IPv4 range of
|
|
# MAC addresses
|
|
#
|
|
p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
|
|
ARP(op="is-at", hwdst=self.pg1.local_mac,
|
|
hwsrc="00:00:5e:00:01:09", pdst=self.pg1.local_ip4,
|
|
psrc=self.pg1.remote_ip4))
|
|
|
|
self.pg1.add_stream(p1)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
#
|
|
# IP packet destined for pg1 remote host arrives on pg0 again.
|
|
# VPP should have an ARP entry for that address now and the packet
|
|
# should be sent out pg1.
|
|
#
|
|
self.pg0.add_stream(p0)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx1 = self.pg1.get_capture(1)
|
|
|
|
self.verify_ip(rx1[0],
|
|
self.pg1.local_mac,
|
|
"00:00:5e:00:01:09",
|
|
self.pg0.remote_ip4,
|
|
self.pg1.remote_ip4)
|
|
|
|
self.pg1.admin_down()
|
|
self.pg1.admin_up()
|
|
|
|
def test_arp_duplicates(self):
|
|
""" ARP Duplicates"""
|
|
|
|
#
|
|
# Generate some hosts on the LAN
|
|
#
|
|
self.pg1.generate_remote_hosts(3)
|
|
|
|
#
|
|
# Add host 1 on pg1 and pg2
|
|
#
|
|
arp_pg1 = VppNeighbor(self,
|
|
self.pg1.sw_if_index,
|
|
self.pg1.remote_hosts[1].mac,
|
|
self.pg1.remote_hosts[1].ip4)
|
|
arp_pg1.add_vpp_config()
|
|
arp_pg2 = VppNeighbor(self,
|
|
self.pg2.sw_if_index,
|
|
self.pg2.remote_mac,
|
|
self.pg1.remote_hosts[1].ip4)
|
|
arp_pg2.add_vpp_config()
|
|
|
|
#
|
|
# IP packet destined for pg1 remote host arrives on pg1 again.
|
|
#
|
|
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()
|
|
|
|
rx1 = self.pg1.get_capture(1)
|
|
|
|
self.verify_ip(rx1[0],
|
|
self.pg1.local_mac,
|
|
self.pg1.remote_hosts[1].mac,
|
|
self.pg0.remote_ip4,
|
|
self.pg1.remote_hosts[1].ip4)
|
|
|
|
#
|
|
# remove the duplicate on pg1
|
|
# packet stream shoud generate ARPs out of pg1
|
|
#
|
|
arp_pg1.remove_vpp_config()
|
|
|
|
self.pg0.add_stream(p)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx1 = self.pg1.get_capture(1)
|
|
|
|
self.verify_arp_req(rx1[0],
|
|
self.pg1.local_mac,
|
|
self.pg1.local_ip4,
|
|
self.pg1.remote_hosts[1].ip4)
|
|
|
|
#
|
|
# Add it back
|
|
#
|
|
arp_pg1.add_vpp_config()
|
|
|
|
self.pg0.add_stream(p)
|
|
self.pg_enable_capture(self.pg_interfaces)
|
|
self.pg_start()
|
|
|
|
rx1 = self.pg1.get_capture(1)
|
|
|
|
self.verify_ip(rx1[0],
|
|
self.pg1.local_mac,
|
|
self.pg1.remote_hosts[1].mac,
|
|
self.pg0.remote_ip4,
|
|
self.pg1.remote_hosts[1].ip4)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main(testRunner=VppTestRunner)
|