Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/arp/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def garp_enabled(rand_selected_dut, config_facts):
arp_accept_res = duthost.shell(cat_arp_accept_cmd.format(vlan))
arp_accept_vals.append(arp_accept_res['stdout'])

yield all(int(val) == 1 for val in arp_accept_vals)
yield all(int(val) == 2 for val in arp_accept_vals)

garp_disable_cmd = 'sonic-db-cli CONFIG_DB HDEL "VLAN_INTERFACE|{}" grat_arp'
for vlan in vlan_intfs:
Expand Down
200 changes: 200 additions & 0 deletions tests/arp/test_arp_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
This module tests extended ARP features including gratuitous ARP and proxy ARP
"""
import logging
import time
import ptf.testutils as testutils
import pytest

from ipaddress import ip_network
from scapy.all import Ether, IPv6, ICMPv6ND_NA, ICMPv6NDOptSrcLLAddr
from tests.arp.arp_utils import clear_dut_arp_cache
from tests.common.helpers.constants import PTF_TIMEOUT
from tests.common.utilities import increment_ipv4_addr
Expand Down Expand Up @@ -54,6 +57,203 @@ def test_arp_garp_enabled(rand_selected_dut, garp_enabled, ip_and_intf_info, int
pytest_assert(switch_arptable['arptable']['v4'][arp_request_ip]['interface'] in vlan_intfs)


def test_arp_accept_value(rand_selected_dut, garp_enabled, config_facts):
"""
Verify that arp_accept is set to 2 when grat_arp is enabled.

The garp_enabled fixture enables grat_arp in CONFIG_DB. This test verifies
that the kernel arp_accept sysctl is programmed to 2 (same-subnet only).
"""
duthost = rand_selected_dut

vlan_intfs = list(config_facts['VLAN_INTERFACE'].keys())

for vlan in vlan_intfs:
arp_accept_res = duthost.shell('cat /proc/sys/net/ipv4/conf/{}/arp_accept'.format(vlan))
pytest_assert(int(arp_accept_res['stdout']) == 2,
"Expected arp_accept=2 for {}, got {}".format(vlan, arp_accept_res['stdout']))


def test_arp_garp_out_of_subnet_not_learned(rand_selected_dut, garp_enabled, ip_and_intf_info,
intfs_for_test, config_facts, ptfadapter):
"""
Send a gratuitous ARP (GARP) packet with a source IP outside the subnet
of the receiving interface.

With arp_accept=2, the DUT should NOT learn a neighbor entry from this
out-of-subnet GARP.
"""
duthost = rand_selected_dut

# Derive an out-of-subnet IP from the VLAN's IPv4 subnet
vlan_addrs = list(list(config_facts['VLAN_INTERFACE'].items())[0][1].keys())
out_of_subnet_ip = None
for addr in vlan_addrs:
try:
net = ip_network(addr, strict=False)
if net.version == 4:
out_of_subnet_ip = str(net.broadcast_address + 10)
break
except ValueError:
continue

pytest_assert(out_of_subnet_ip is not None, "No IPv4 VLAN subnet found to derive out-of-subnet IP")

logger.info("VLAN subnet: {}, out-of-subnet IP: {}".format(net, out_of_subnet_ip))
arp_src_mac = '00:00:07:08:09:0b'
_, _, intf1_index, _, = intfs_for_test

pkt = testutils.simple_arp_packet(pktlen=60,
eth_dst='ff:ff:ff:ff:ff:ff',
eth_src=arp_src_mac,
vlan_pcp=0,
arp_op=2,
ip_snd=out_of_subnet_ip,
ip_tgt=out_of_subnet_ip,
hw_snd=arp_src_mac,
hw_tgt='ff:ff:ff:ff:ff:ff'
)

clear_dut_arp_cache(duthost)

logger.info("Sending out-of-subnet GARP for target {} from PTF interface {}".format(
out_of_subnet_ip, intf1_index))
testutils.send_packet(ptfadapter, intf1_index, pkt)

# Allow some time for the DUT to process the packet
time.sleep(2)

switch_arptable = duthost.switch_arptable()['ansible_facts']
pytest_assert(out_of_subnet_ip not in switch_arptable['arptable']['v4'],
"Out-of-subnet GARP source {} should NOT be learned with arp_accept=2".format(out_of_subnet_ip))


def test_ipv6_unsolicited_na_link_local_accepted(rand_selected_dut, garp_enabled, ip_and_intf_info,
intfs_for_test, config_facts, ptfadapter):
"""
Send an unsolicited IPv6 Neighbor Advertisement (NA) with a link-local
source address (fe80::).

Link-local neighbors should still be accepted regardless of arp_accept
settings, since link-local is always valid on any interface.
"""
duthost = rand_selected_dut
ptf_intf_ipv6_addr = ip_and_intf_info[2]
pytest_require(ptf_intf_ipv6_addr is not None, 'No IPv6 VLAN address configured on device')

link_local_ip = 'fe80::100:1'
na_src_mac = '00:00:07:08:09:0c'
_, _, intf1_index, _, = intfs_for_test

# Construct unsolicited NA with link-local source
pkt = Ether(src=na_src_mac, dst='33:33:00:00:00:01')
pkt /= IPv6(src=link_local_ip, dst='ff02::1')
pkt /= ICMPv6ND_NA(tgt=link_local_ip, R=0, S=0, O=1)
pkt /= ICMPv6NDOptSrcLLAddr(type=2, lladdr=na_src_mac)

clear_dut_arp_cache(duthost, is_ipv6=True)

logger.info("Sending link-local unsolicited NA for target {} from PTF interface {}".format(
link_local_ip, intf1_index))
testutils.send_packet(ptfadapter, intf1_index, pkt)

time.sleep(2)

switch_arptable = duthost.switch_arptable()['ansible_facts']
pytest_assert(link_local_ip in switch_arptable['arptable']['v6'],
"Link-local unsolicited NA source {} should be learned".format(link_local_ip))


def test_ipv6_unsolicited_na_in_subnet_learned(rand_selected_dut, garp_enabled, ip_and_intf_info,
intfs_for_test, config_facts, ptfadapter):
"""
Send an unsolicited IPv6 Neighbor Advertisement (NA) with a source address
inside the VLAN's IPv6 subnet.

The DUT should learn a neighbor entry from this in-subnet unsolicited NA.
"""
duthost = rand_selected_dut
ptf_intf_ipv6_addr = ip_and_intf_info[2]
pytest_require(ptf_intf_ipv6_addr is not None, 'No IPv6 VLAN address configured on device')

# Use the in-subnet IPv6 address from ip_and_intf_info (offset by 3 from network address)
in_subnet_ipv6 = str(ptf_intf_ipv6_addr)
na_src_mac = '00:00:07:08:09:0e'
_, _, intf1_index, _, = intfs_for_test

# Construct unsolicited NA with in-subnet source
pkt = Ether(src=na_src_mac, dst='33:33:00:00:00:01')
pkt /= IPv6(src=in_subnet_ipv6, dst='ff02::1')
pkt /= ICMPv6ND_NA(tgt=in_subnet_ipv6, R=0, S=0, O=1)
pkt /= ICMPv6NDOptSrcLLAddr(type=2, lladdr=na_src_mac)

clear_dut_arp_cache(duthost, is_ipv6=True)

logger.info("Sending in-subnet unsolicited NA for target {} from PTF interface {}".format(
in_subnet_ipv6, intf1_index))
testutils.send_packet(ptfadapter, intf1_index, pkt)

time.sleep(2)

vlan_intfs = list(config_facts['VLAN_INTERFACE'].keys())
switch_arptable = duthost.switch_arptable()['ansible_facts']
pytest_assert(in_subnet_ipv6 in switch_arptable['arptable']['v6'],
"In-subnet unsolicited NA source {} should be learned".format(in_subnet_ipv6))
pytest_assert(switch_arptable['arptable']['v6'][in_subnet_ipv6]['macaddress'].lower() == na_src_mac.lower())
pytest_assert(switch_arptable['arptable']['v6'][in_subnet_ipv6]['interface'] in vlan_intfs)


def test_ipv6_unsolicited_na_out_of_subnet_not_learned(rand_selected_dut, garp_enabled, ip_and_intf_info,
intfs_for_test, config_facts, ptfadapter):
"""
Send an unsolicited IPv6 Neighbor Advertisement (NA) with a source address
outside the VLAN's IPv6 subnet.

The DUT should NOT learn a neighbor entry from this out-of-subnet
unsolicited NA.
"""
duthost = rand_selected_dut
ptf_intf_ipv6_addr = ip_and_intf_info[2]
pytest_require(ptf_intf_ipv6_addr is not None, 'No IPv6 VLAN address configured on device')

# Derive an out-of-subnet IPv6 address from the VLAN's IPv6 subnet
vlan_addrs = list(list(config_facts['VLAN_INTERFACE'].items())[0][1].keys())
out_of_subnet_ipv6 = None
for addr in vlan_addrs:
try:
net = ip_network(addr, strict=False)
if net.version == 6:
# Use an address well beyond the subnet's broadcast
out_of_subnet_ipv6 = str(net.broadcast_address + 10)
break
except ValueError:
continue

pytest_assert(out_of_subnet_ipv6 is not None,
"No IPv6 VLAN subnet found to derive out-of-subnet address")

na_src_mac = '00:00:07:08:09:0d'
_, _, intf1_index, _, = intfs_for_test

# Construct unsolicited NA: sent to all-nodes multicast, override flag set
pkt = Ether(src=na_src_mac, dst='33:33:00:00:00:01')
pkt /= IPv6(src=out_of_subnet_ipv6, dst='ff02::1')
pkt /= ICMPv6ND_NA(tgt=out_of_subnet_ipv6, R=0, S=0, O=1)
pkt /= ICMPv6NDOptSrcLLAddr(type=2, lladdr=na_src_mac)

clear_dut_arp_cache(duthost, is_ipv6=True)

logger.info("Sending out-of-subnet unsolicited NA for target {} from PTF interface {}".format(
out_of_subnet_ipv6, intf1_index))
testutils.send_packet(ptfadapter, intf1_index, pkt)

time.sleep(2)

switch_arptable = duthost.switch_arptable()['ansible_facts']
pytest_assert(out_of_subnet_ipv6 not in switch_arptable['arptable']['v6'],
"Out-of-subnet unsolicited NA source {} should NOT be learned".format(out_of_subnet_ipv6))


def test_proxy_arp(rand_selected_dut, proxy_arp_enabled, ip_and_intf_info, ptfadapter, packets_for_test):
"""
Send an ARP request or neighbor solicitation (NS) to the DUT for an IP address within the subnet of the DUT's VLAN.
Expand Down
Loading