diff --git a/ansible/roles/test/files/ptftests/dhcp_relay_test.py b/ansible/roles/test/files/ptftests/dhcp_relay_test.py index 1d1ca62066c..1db53a4af9e 100644 --- a/ansible/roles/test/files/ptftests/dhcp_relay_test.py +++ b/ansible/roles/test/files/ptftests/dhcp_relay_test.py @@ -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: @@ -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'] @@ -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), @@ -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, @@ -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), @@ -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), @@ -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, @@ -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), diff --git a/tests/dhcp_relay/test_dhcp_relay.py b/tests/dhcp_relay/test_dhcp_relay.py index f5537b2ae56..8d167df818e 100644 --- a/tests/dhcp_relay/test_dhcp_relay.py +++ b/tests/dhcp_relay/test_dhcp_relay.py @@ -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): @@ -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(): @@ -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 @@ -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, @@ -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']: @@ -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']: @@ -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, @@ -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 @@ -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")