diff --git a/ansible/roles/test/files/ptftests/py3/dhcp_relay_test.py b/ansible/roles/test/files/ptftests/py3/dhcp_relay_test.py index 7b911e9f5e8..088a23b346c 100644 --- a/ansible/roles/test/files/ptftests/py3/dhcp_relay_test.py +++ b/ansible/roles/test/files/ptftests/py3/dhcp_relay_test.py @@ -174,6 +174,8 @@ def setUp(self): self.portchannels_ip_list = self.test_params.get('portchannels_ip_list', None) self.agent_relay_mode = self.test_params.get('agent_relay_mode', None) self.max_hop_count = self.test_params.get('max_hop_count', None) + self.client_giaddr = self.test_params.get('client_giaddr', self.switch_loopback_ip) + self.expected_forward = self.test_params.get('expected_forward', None) self.client_vrf = self.test_params.get('client_vrf', None) self.dhcpv4_disable_flag = self.test_params.get('dhcpv4_disable_flag', None) if self.relay_agent == "sonic-relay-agent": @@ -211,6 +213,8 @@ def setUp(self): self.option82 += struct.pack('BB', self.REMOTE_ID_SUBOPTION, len(remote_id_string)) self.option82 += remote_id_string.encode('utf-8') + self.client_option82 = b'\x01\x07Vlan100\x02\x06\x11\x22\x33\x44\x55\x66' + link_selection_added = False # set below; dual-tor block skips SubOption 5 if set if self.relay_agent == "sonic-relay-agent": @@ -286,24 +290,14 @@ def create_dhcp_discover_packet(self, dst_mac=BROADCAST_MAC, src_port=DHCP_CLIEN discover_packet[scapy.IP].dst = self.switch_loopback_ip discover_packet[scapy.IP].src = self.client_ip else: - # Sub-option 1: Circuit ID (VLAN 100) - # Circuit ID sub-option type 1, length 7, data 'Vlan100' - circuit_id = b'\x01' + bytes([7]) + b'Vlan100' - - # Sub-option 2: Remote ID (MAC address) - # Remote ID sub-option type 2, length 6, MAC address - remote_id = b'\x02' + bytes([6]) + bytes.fromhex("112233445566") - # Combine the new sub-options for relay - relay_option82 = circuit_id + remote_id - discover_packet[scapy.Ether].dst = self.uplink_mac discover_packet[scapy.IP].src = self.client_ip discover_packet[scapy.IP].dst = self.switch_loopback_ip discover_packet[scapy.BOOTP].hops = self.max_hop_count if self.max_hop_count == self.MAX_HOP_COUNT else 1 - discover_packet[scapy.BOOTP].giaddr = self.switch_loopback_ip + discover_packet[scapy.BOOTP].giaddr = self.client_giaddr discover_packet[scapy.DHCP].options.insert( discover_packet[scapy.DHCP].options.index("end"), - (82, relay_option82) + (82, self.client_option82) ) return discover_packet @@ -337,8 +331,10 @@ def create_dhcp_discover_relayed_packet(self): else: source_ip = self.portchannels_ip_list[0] - if ((self.link_selection and self.source_interface) or - self.server_vrf or self.dual_tor or self.agent_relay_mode): + if self.agent_relay_mode: + giaddr = self.client_giaddr + elif ((self.link_selection and self.source_interface) or + self.server_vrf or self.dual_tor): giaddr = self.switch_loopback_ip elif self.server_id_override or not self.dual_tor: giaddr = self.relay_iface_ip @@ -355,6 +351,9 @@ def create_dhcp_discover_relayed_packet(self): elif self.agent_relay_mode == "replace": dhcp_options = [('message-type', 'discover'), (82, self.option82), ('end')] + elif self.agent_relay_mode == "forward": + dhcp_options = [('message-type', 'discover'), (82, self.client_option82), ('end')] + elif self.agent_relay_mode == "append": # Sub-option 1: Circuit ID (VLAN 100) # Circuit ID sub-option type 1, length 7, data 'Vlan100' @@ -1203,11 +1202,33 @@ def check_relayed_pkts_on_server_side(self, mask, pkt, packet_type): logger.info("Expect receiving {} packets from port [{}]".format(packet_type, self.server_port_indices)) log_dhcp_packet_info(pkt) num_expected_packets = self.num_dhcp_servers - if self.agent_relay_mode == "discard" or self.dhcpv4_disable_flag or self.max_hop_count == self.MAX_HOP_COUNT: - # Expected result: No packet sent + if self.expected_forward is None: + expected_forward = not ( + self.agent_relay_mode == "discard" + or self.dhcpv4_disable_flag + or self.max_hop_count == self.MAX_HOP_COUNT + ) + else: + expected_forward = self.expected_forward + if expected_forward: + packet_mask = mask + else: num_expected_packets = 0 + unexpected_packet = ( + scapy.Ether(src=self.uplink_mac) + / scapy.IP() + / scapy.UDP(sport=self.DHCP_SERVER_PORT, dport=self.DHCP_SERVER_PORT) + ) + packet_mask = Mask(unexpected_packet) + packet_mask.set_do_not_care_scapy(scapy.Ether, "dst") + for field in ("version", "ihl", "tos", "len", "id", "flags", + "frag", "ttl", "chksum", "src", "dst", "options"): + packet_mask.set_do_not_care_scapy(scapy.IP, field) + packet_mask.set_do_not_care_scapy(scapy.UDP, "chksum") + packet_mask.set_do_not_care_scapy(scapy.UDP, "len") + packet_mask.set_ignore_extra_bytes() captured_count = testutils.count_matched_packets_all_ports( - self, mask, self.server_port_indices) + self, packet_mask, self.server_port_indices) self.assertTrue(captured_count == num_expected_packets, "Failed: %s packet counts are not equal %d != %d" % (packet_type, captured_count, num_expected_packets)) diff --git a/tests/dhcp_relay/test_dhcpv4_relay.py b/tests/dhcp_relay/test_dhcpv4_relay.py index bd4f2cede48..7ca8fff2335 100644 --- a/tests/dhcp_relay/test_dhcpv4_relay.py +++ b/tests/dhcp_relay/test_dhcpv4_relay.py @@ -79,6 +79,7 @@ def _restore_interface_ip_entries(duthost, saved_entries): CLIENT_VRF_NAME = "Vrf01" # Global macro for Client VRF MAX_HOP_COUNT = 16 CONFIG_HOP_COUNT = 2 +DOWNSTREAM_RELAY_IP = '192.0.2.1' # TEST-NET-1; synthetic downstream relay logger = logging.getLogger(__name__) @@ -422,6 +423,7 @@ def test_dhcp_relay_agent_mode( "kvm_support": True, "relay_agent": relay_agent, "agent_relay_mode": test_mode, + "client_giaddr": DOWNSTREAM_RELAY_IP, "downlink_vlan_iface_name": str(dhcp_relay['downlink_vlan_iface']['name']), }, log_file="/tmp/test_dhcp_relay_agent_mode.log", @@ -439,6 +441,66 @@ def test_dhcp_relay_agent_mode( sonic_dhcp_relay_config(duthost, dut_dhcp_relay_data) +def test_dhcp_relay_local_giaddr_dropped( + ptfhost, + dut_dhcp_relay_data, + validate_dut_routes_exist, + testing_config, + setup_standby_ports_on_rand_unselected_tor, + rand_unselected_dut, + toggle_all_simulator_ports_to_rand_selected_tor_m, # noqa: F811 + relay_agent # noqa: F811 +): + """Verify native relay rejects a chained request spoofing its local giaddr.""" + testing_mode, duthost = testing_config + + try: + for dhcp_relay in dut_dhcp_relay_data: + vlan = str(dhcp_relay['downlink_vlan_iface']['name']) + dhcp_servers = ",".join(dhcp_relay['downlink_vlan_iface']['dhcp_server_addrs']) + duthost.shell(f'config dhcpv4_relay del {vlan}') + duthost.shell(f'config dhcpv4_relay add --dhcpv4-servers {dhcp_servers}' + f' --agent-relay-mode forward {vlan}') + + ptf_runner( + ptfhost, + "ptftests", + "dhcp_relay_test.DHCPTest", + platform_dir="ptftests", + params={ + "hostname": duthost.hostname, + "client_port_index": dhcp_relay['client_iface']['port_idx'], + "other_client_port": repr(dhcp_relay['other_client_ports']), + "client_iface_alias": str(dhcp_relay['client_iface']['alias']), + "leaf_port_indices": repr(dhcp_relay['uplink_port_indices']), + "num_dhcp_servers": len(dhcp_relay['downlink_vlan_iface']['dhcp_server_addrs']), + "server_ip": dhcp_relay['downlink_vlan_iface']['dhcp_server_addrs'], + "relay_iface_ip": str(dhcp_relay['downlink_vlan_iface']['addr']), + "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, + "switch_loopback_ip": dhcp_relay['switch_loopback_ip'], + "uplink_mac": str(dhcp_relay['uplink_mac']), + "testing_mode": testing_mode, + "kvm_support": True, + "relay_agent": relay_agent, + "agent_relay_mode": "forward", + "client_giaddr": dhcp_relay['switch_loopback_ip'], + "expected_forward": False, + "downlink_vlan_iface_name": str(dhcp_relay['downlink_vlan_iface']['name']), + }, + log_file="/tmp/test_dhcp_relay_local_giaddr.log", + is_python3=True + ) + except LogAnalyzerError as err: + logger.error("Unable to find expected log in syslog") + raise err + finally: + sonic_dhcp_relay_unconfig(duthost, dut_dhcp_relay_data) + sonic_dhcp_relay_config(duthost, dut_dhcp_relay_data) + + @pytest.mark.parametrize("testcase", ["vrf_selection", "source_intf", "server_id_override"]) def test_dhcp_relay_with_non_default_vrf( ptfhost,