Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 24 additions & 8 deletions ansible/roles/test/files/ptftests/dhcp_relay_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ def setUp(self):
self.client_port_index = int(self.test_params['client_port_index'])
self.client_mac = self.dataplane.get_mac(0, self.client_port_index)

self.switch_loopback_ip = self.test_params['switch_loopback_ip']

# 'dual' for dual tor testing
# 'single' for regular single tor testing
self.dual_tor = (self.test_params['testing_mode'] == 'dual')

# option82 is a byte string created by the relay agent. It contains the circuit_id and remote_id fields.
# circuit_id is stored as suboption 1 of option 82.
# It consists of the following:
Expand All @@ -139,6 +145,16 @@ def setUp(self):
self.option82 += struct.pack('BB', 2, len(remote_id_string))
self.option82 += remote_id_string

# In 'dual' testing mode, vlan ip is stored as suboption 5 of option 82.
# It consists of the following:
# Byte 0: Suboption number, always set to 5
# Byte 1: Length of suboption data in bytes, always set to 4 (ipv4 addr has 4 bytes)
# Bytes 2+: vlan ip addr
if self.dual_tor:
link_selection = ''.join([chr(int(byte)) for byte in self.relay_iface_ip.split('.')])
self.option82 += struct.pack('BB', 5, 4)
self.option82 += link_selection

# We'll assign our client the IP address 1 greater than our relay interface (i.e., gateway) IP
self.client_ip = incrementIpAddress(self.relay_iface_ip, 1)
self.client_subnet = self.test_params['relay_iface_netmask']
Expand Down Expand Up @@ -195,7 +211,7 @@ def create_dhcp_discover_relayed_packet(self):
ciaddr=self.DEFAULT_ROUTE_IP,
yiaddr=self.DEFAULT_ROUTE_IP,
siaddr=self.DEFAULT_ROUTE_IP,
giaddr=self.relay_iface_ip,
giaddr=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
chaddr=my_chaddr)
bootp /= scapy.DHCP(options=[('message-type', 'discover'),
('relay_agent_Information', self.option82),
Expand All @@ -214,10 +230,10 @@ def create_dhcp_offer_packet(self):
eth_dst=self.relay_iface_mac,
eth_client=self.client_mac,
ip_server=self.server_ip,
ip_dst=self.relay_iface_ip,
ip_dst=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
ip_offered=self.client_ip,
port_dst=self.DHCP_SERVER_PORT,
ip_gateway=self.relay_iface_ip,
ip_gateway=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
netmask_client=self.client_subnet,
dhcp_lease=self.LEASE_TIME,
padding_bytes=0,
Expand Down Expand Up @@ -246,7 +262,7 @@ def create_dhcp_offer_relayed_packet(self):
ciaddr=self.DEFAULT_ROUTE_IP,
yiaddr=self.client_ip,
siaddr=self.server_ip,
giaddr=self.relay_iface_ip,
giaddr=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
chaddr=my_chaddr)
bootp /= scapy.DHCP(options=[('message-type', 'offer'),
('server_id', self.server_ip),
Expand Down Expand Up @@ -300,7 +316,7 @@ def create_dhcp_request_relayed_packet(self):
ciaddr=self.DEFAULT_ROUTE_IP,
yiaddr=self.DEFAULT_ROUTE_IP,
siaddr=self.DEFAULT_ROUTE_IP,
giaddr=self.relay_iface_ip,
giaddr=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
chaddr=my_chaddr)
bootp /= scapy.DHCP(options=[('message-type', 'request'),
('requested_addr', self.client_ip),
Expand All @@ -321,10 +337,10 @@ def create_dhcp_ack_packet(self):
eth_dst=self.relay_iface_mac,
eth_client=self.client_mac,
ip_server=self.server_ip,
ip_dst=self.relay_iface_ip,
ip_dst=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
ip_offered=self.client_ip,
port_dst=self.DHCP_SERVER_PORT,
ip_gateway=self.relay_iface_ip,
ip_gateway=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
netmask_client=self.client_subnet,
dhcp_lease=self.LEASE_TIME,
padding_bytes=0,
Expand Down Expand Up @@ -353,7 +369,7 @@ def create_dhcp_ack_relayed_packet(self):
ciaddr=self.DEFAULT_ROUTE_IP,
yiaddr=self.client_ip,
siaddr=self.server_ip,
giaddr=self.relay_iface_ip,
giaddr=self.relay_iface_ip if not self.dual_tor else self.switch_loopback_ip,
chaddr=my_chaddr)
bootp /= scapy.DHCP(options=[('message-type', 'ack'),
('server_id', self.server_ip),
Expand Down
101 changes: 86 additions & 15 deletions tests/dhcp_relay/test_dhcp_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

BROADCAST_MAC = 'ff:ff:ff:ff:ff:ff'
DEFAULT_DHCP_CLIENT_PORT = 68
SINGLE_TOR_MODE = 'single'
DUAL_TOR_MODE = 'dual'


@pytest.fixture(autouse=True)
def ignore_expected_loganalyzer_exceptions(rand_one_dut_hostname, loganalyzer):
Expand Down Expand Up @@ -42,6 +45,8 @@ def dut_dhcp_relay_data(duthosts, rand_one_dut_hostname, ptfhost, tbinfo):
mg_facts = duthost.get_extended_minigraph_facts(tbinfo)
host_facts = duthost.setup()['ansible_facts']

switch_loopback_ip = mg_facts['minigraph_lo_interfaces'][0]['addr']

# SONiC spawns one DHCP relay agent per VLAN interface configured on the DUT
vlan_dict = mg_facts['minigraph_vlans']
for vlan_iface_name, vlan_info_dict in vlan_dict.items():
Expand Down Expand Up @@ -94,10 +99,13 @@ def dut_dhcp_relay_data(duthosts, rand_one_dut_hostname, ptfhost, tbinfo):
dhcp_relay_data['client_iface'] = client_iface
dhcp_relay_data['uplink_interfaces'] = uplink_interfaces
dhcp_relay_data['uplink_port_indices'] = uplink_port_indices
dhcp_relay_data['switch_loopback_ip'] = str(switch_loopback_ip)

dhcp_relay_data_list.append(dhcp_relay_data)

return dhcp_relay_data_list


@pytest.fixture(scope="module")
def validate_dut_routes_exist(duthosts, rand_one_dut_hostname, dut_dhcp_relay_data):
"""Fixture to valid a route to each DHCP server exist
Expand All @@ -112,12 +120,61 @@ def validate_dut_routes_exist(duthosts, rand_one_dut_hostname, dut_dhcp_relay_da
assert len(rtInfo["nexthops"]) > 0, "Failed to find route to DHCP server '{0}'".format(dhcp_server)


def test_dhcp_relay_default(duthosts, rand_one_dut_hostname, ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist):
def restart_dhcp_service(duthost):
duthost.shell('systemctl reset-failed dhcp_relay')
duthost.shell('systemctl restart dhcp_relay')
duthost.shell('systemctl reset-failed dhcp_relay')

for retry in range(5):
time.sleep(30)
dhcp_status = duthost.shell('docker container top dhcp_relay | grep dhcrelay | cat')["stdout"]
if dhcp_status != "":
break
else:
assert False, "Failed to restart dhcp docker"

time.sleep(30)


def get_subtype_from_configdb(duthost):
# HEXISTS returns 1 if the key exists, otherwise 0
subtype_exist = int(duthost.shell('redis-cli -n 4 HEXISTS "DEVICE_METADATA|localhost" "subtype"')["stdout"])
subtype_value = ""
if subtype_exist:
subtype_value = duthost.shell('redis-cli -n 4 HGET "DEVICE_METADATA|localhost" "subtype"')["stdout"]
return subtype_exist, subtype_value


@pytest.fixture(scope="module", params=[SINGLE_TOR_MODE, DUAL_TOR_MODE])
def testing_config(request, duthosts, rand_one_dut_hostname):
testing_mode = request.param
duthost = duthosts[rand_one_dut_hostname]
subtype_exist, subtype_value = get_subtype_from_configdb(duthost)

if testing_mode == SINGLE_TOR_MODE:
if subtype_exist:
duthost.shell('redis-cli -n 4 HDEL "DEVICE_METADATA|localhost" "subtype"')
restart_dhcp_service(duthost)

if testing_mode == DUAL_TOR_MODE:
if not subtype_exist or subtype_value != 'DualToR':
duthost.shell('redis-cli -n 4 HSET "DEVICE_METADATA|localhost" "subtype" "DualToR"')
restart_dhcp_service(duthost)

yield testing_mode, duthost

if testing_mode == DUAL_TOR_MODE:
duthost.shell('redis-cli -n 4 HDEL "DEVICE_METADATA|localhost" "subtype"')
restart_dhcp_service(duthost)


def test_dhcp_relay_default(ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist, testing_config):
"""Test DHCP relay functionality on T0 topology.

For each DHCP relay agent running on the DuT, verify DHCP packets are relayed properly
"""
duthost = duthosts[rand_one_dut_hostname]
testing_mode, duthost = testing_config

for dhcp_relay in dut_dhcp_relay_data:
# Run the DHCP relay test on the PTF host
ptf_runner(ptfhost,
Expand All @@ -134,17 +191,20 @@ def test_dhcp_relay_default(duthosts, rand_one_dut_hostname, ptfhost, dut_dhcp_r
"relay_iface_mac": str(dhcp_relay['downlink_vlan_iface']['mac']),
"relay_iface_netmask": str(dhcp_relay['downlink_vlan_iface']['mask']),
"dest_mac_address": BROADCAST_MAC,
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT},
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT,
"switch_loopback_ip": dhcp_relay['switch_loopback_ip'],
"testing_mode": testing_mode},
log_file="/tmp/dhcp_relay_test.DHCPTest.log")


def test_dhcp_relay_after_link_flap(duthosts, rand_one_dut_hostname, ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist):
def test_dhcp_relay_after_link_flap(ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist, testing_config):
"""Test DHCP relay functionality on T0 topology after uplinks flap

For each DHCP relay agent running on the DuT, with relay agent running, flap the uplinks,
then test whether the DHCP relay agent relays packets properly.
"""
duthost = duthosts[rand_one_dut_hostname]
testing_mode, duthost = testing_config

for dhcp_relay in dut_dhcp_relay_data:
# Bring all uplink interfaces down
for iface in dhcp_relay['uplink_interfaces']:
Expand Down Expand Up @@ -175,18 +235,21 @@ def test_dhcp_relay_after_link_flap(duthosts, rand_one_dut_hostname, ptfhost, du
"relay_iface_mac": str(dhcp_relay['downlink_vlan_iface']['mac']),
"relay_iface_netmask": str(dhcp_relay['downlink_vlan_iface']['mask']),
"dest_mac_address": BROADCAST_MAC,
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT},
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT,
"switch_loopback_ip": dhcp_relay['switch_loopback_ip'],
"testing_mode": testing_mode},
log_file="/tmp/dhcp_relay_test.DHCPTest.log")


def test_dhcp_relay_start_with_uplinks_down(duthosts, rand_one_dut_hostname, ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist):
def test_dhcp_relay_start_with_uplinks_down(ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist, testing_config):
"""Test DHCP relay functionality on T0 topology when relay agent starts with uplinks down

For each DHCP relay agent running on the DuT, bring the uplinks down, then restart the
relay agent while the uplinks are still down. Then test whether the DHCP relay agent
relays packets properly.
"""
duthost = duthosts[rand_one_dut_hostname]
testing_mode, duthost = testing_config

for dhcp_relay in dut_dhcp_relay_data:
# Bring all uplink interfaces down
for iface in dhcp_relay['uplink_interfaces']:
Expand Down Expand Up @@ -224,16 +287,19 @@ def test_dhcp_relay_start_with_uplinks_down(duthosts, rand_one_dut_hostname, ptf
"relay_iface_mac": str(dhcp_relay['downlink_vlan_iface']['mac']),
"relay_iface_netmask": str(dhcp_relay['downlink_vlan_iface']['mask']),
"dest_mac_address": BROADCAST_MAC,
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT},
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT,
"switch_loopback_ip": dhcp_relay['switch_loopback_ip'],
"testing_mode": testing_mode},
log_file="/tmp/dhcp_relay_test.DHCPTest.log")


def test_dhcp_relay_unicast_mac(duthosts, rand_one_dut_hostname, ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist):
def test_dhcp_relay_unicast_mac(ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist, testing_config):
"""Test DHCP relay functionality on T0 topology with unicast mac

Instead of using broadcast MAC, use unicast MAC of DUT and verify that DHCP relay functionality is entact.
"""
duthost = duthosts[rand_one_dut_hostname]
testing_mode, duthost = testing_config

for dhcp_relay in dut_dhcp_relay_data:
# Run the DHCP relay test on the PTF host
ptf_runner(ptfhost,
Expand All @@ -250,17 +316,20 @@ def test_dhcp_relay_unicast_mac(duthosts, rand_one_dut_hostname, ptfhost, dut_dh
"relay_iface_mac": str(dhcp_relay['downlink_vlan_iface']['mac']),
"relay_iface_netmask": str(dhcp_relay['downlink_vlan_iface']['mask']),
"dest_mac_address": duthost.facts["router_mac"],
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT},
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT,
"switch_loopback_ip": dhcp_relay['switch_loopback_ip'],
"testing_mode": testing_mode},
log_file="/tmp/dhcp_relay_test.DHCPTest.log")


def test_dhcp_relay_random_sport(duthosts, rand_one_dut_hostname, ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist):
def test_dhcp_relay_random_sport(ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist, testing_config):
"""Test DHCP relay functionality on T0 topology with random source port (sport)

If the client is SNAT'd, the source port could be changed to a non-standard port (i.e., not 68).
Verify that DHCP relay works with random high sport.
"""
duthost = duthosts[rand_one_dut_hostname]
testing_mode, duthost = testing_config

RANDOM_CLIENT_PORT = random.choice(range(1000, 65535))
for dhcp_relay in dut_dhcp_relay_data:
# Run the DHCP relay test on the PTF host
Expand All @@ -278,5 +347,7 @@ def test_dhcp_relay_random_sport(duthosts, rand_one_dut_hostname, ptfhost, dut_d
"relay_iface_mac": str(dhcp_relay['downlink_vlan_iface']['mac']),
"relay_iface_netmask": str(dhcp_relay['downlink_vlan_iface']['mask']),
"dest_mac_address": BROADCAST_MAC,
"client_udp_src_port": RANDOM_CLIENT_PORT},
"client_udp_src_port": RANDOM_CLIENT_PORT,
"switch_loopback_ip": dhcp_relay['switch_loopback_ip'],
"testing_mode": testing_mode},
log_file="/tmp/dhcp_relay_test.DHCPTest.log")