From 71f8181fc2904bb731bc3968dd3340dacac51b47 Mon Sep 17 00:00:00 2001 From: Xichen96 Date: Sun, 19 Jul 2026 16:55:48 +0000 Subject: [PATCH] [dhcp_relay]: cover chained requests without Option 82 Add native relay coverage for a nonlocal chained request without Option 82 and require forwarding without adding relay information. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2b881aa9-3a3a-4aaf-b2ca-b941705b2438 Signed-off-by: Xichen96 --- .../files/ptftests/py3/dhcp_relay_test.py | 53 ++++++++++++---- tests/dhcp_relay/test_dhcpv4_relay.py | 62 +++++++++++++++++++ 2 files changed, 104 insertions(+), 11 deletions(-) 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..d5425f4e7d2 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,9 @@ 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.include_client_option82 = self.test_params.get('include_client_option82', True) + 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": @@ -300,11 +303,12 @@ def create_dhcp_discover_packet(self, dst_mac=BROADCAST_MAC, src_port=DHCP_CLIEN 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.DHCP].options.insert( - discover_packet[scapy.DHCP].options.index("end"), - (82, relay_option82) - ) + discover_packet[scapy.BOOTP].giaddr = self.client_giaddr + if self.include_client_option82: + discover_packet[scapy.DHCP].options.insert( + discover_packet[scapy.DHCP].options.index("end"), + (82, relay_option82) + ) return discover_packet @@ -337,8 +341,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 and self.client_giaddr: + 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 @@ -349,7 +355,10 @@ def create_dhcp_discover_relayed_packet(self): dport=self.DHCP_SERVER_PORT, len=308) # Relay-side behavior based on agent_mode - if self.agent_relay_mode == "discard": + if not self.include_client_option82: + dhcp_options = [('message-type', 'discover'), ('end')] + + elif self.agent_relay_mode == "discard": dhcp_options = [('message-type', 'discover'), (82, self.option82), ('end')] elif self.agent_relay_mode == "replace": @@ -1203,11 +1212,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..b69a669c8c8 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__) @@ -439,6 +440,67 @@ def test_dhcp_relay_agent_mode( sonic_dhcp_relay_config(duthost, dut_dhcp_relay_data) +def test_dhcp_relay_chained_request_without_option82( + 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 forwards a chained request without adding Option 82.""" + 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 discard {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": "discard", + "client_giaddr": DOWNSTREAM_RELAY_IP, + "include_client_option82": False, + "expected_forward": True, + "downlink_vlan_iface_name": str(dhcp_relay['downlink_vlan_iface']['name']), + }, + log_file="/tmp/test_dhcp_relay_chained_no_option82.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,