git: bd87fc2a1c1a - main - tests/dhclient: import scapy inside vnet

From: Siva Mahadevan <siva_at_FreeBSD.org>
Date: Wed, 28 Oct 2026 19:19:29 UTC
The branch main has been updated by siva:

URL: https://cgit.FreeBSD.org/src/commit/?id=bd87fc2a1c1a20a080c38b12a55b377967a7c4a7

commit bd87fc2a1c1a20a080c38b12a55b377967a7c4a7
Author:     Siva Mahadevan <siva@FreeBSD.org>
AuthorDate: 2026-10-27 17:19:27 +0000
Commit:     Siva Mahadevan <siva@FreeBSD.org>
CommitDate: 2026-10-28 19:15:44 +0000

    tests/dhclient: import scapy inside vnet
    
    This fixes a [CI failure][0].
    
    [0] https://ci.freebsd.org/job/FreeBSD-main-amd64-test/29813/testReport/sbin.dhclient.functional/py/__test_cases_list__/
    
    PR:             299641
    Reviewed by:    markj, ngie
    MFC after:      3 days
    Sponsored by:   The FreeBSD Foundation
    Pull Request:   https://ron-dev.freebsd.org/FreeBSD/src/pulls/83
---
 sbin/dhclient/tests/functional.py | 97 ++++++++++++++++++++-------------------
 1 file changed, 50 insertions(+), 47 deletions(-)

diff --git a/sbin/dhclient/tests/functional.py b/sbin/dhclient/tests/functional.py
index 3a984307c691..205bd11a76ef 100644
--- a/sbin/dhclient/tests/functional.py
+++ b/sbin/dhclient/tests/functional.py
@@ -10,7 +10,6 @@
 import subprocess
 
 import pytest
-import scapy.all as sp
 from atf_python.sys.net.tools import ToolsHelper
 from atf_python.sys.net.vnet import VnetTestTemplate
 
@@ -21,44 +20,6 @@ def mac_to_chaddr(mac_str):
     return mac_bytes + b"\x00" * (16 - len(mac_bytes))
 
 
-def get_dhcp_type(pkt):
-    """Extract the DHCP message type from a packet."""
-    if not pkt.haslayer(sp.DHCP):
-        return None
-    for opt in pkt[sp.DHCP].options:
-        if isinstance(opt, tuple) and opt[0] == "message-type":
-            return sp.DHCPTypes[opt[1]]
-    return None
-
-
-def build_reply(iface, server_ip, client_ip, client_mac, xid, msg_type,
-                extra_opts=None, sname=b"", file=b""):
-    """Build a DHCP OFFER or ACK packet."""
-    options = [
-        ("message-type", msg_type),
-        ("server_id", server_ip),
-        ("lease_time", 3600),
-        ("subnet_mask", "255.255.255.0"),
-        ("router", server_ip),
-    ]
-    if extra_opts:
-        options.extend(extra_opts)
-    options.append("end")
-
-    return (
-        sp.Ether(dst=client_mac, src=sp.get_if_hwaddr(iface))
-        / sp.IP(src=server_ip, dst="255.255.255.255")
-        / sp.UDP(sport=67, dport=68)
-        / sp.BOOTP(op=2,
-            xid=xid,
-            yiaddr=client_ip,
-            siaddr=server_ip,
-            chaddr=mac_to_chaddr(client_mac),
-            sname=sname,
-            file=file,
-        )
-        / sp.DHCP(options=options)
-    )
 
 
 class TestDhclient(VnetTestTemplate):
@@ -106,6 +67,9 @@ class TestDhclient(VnetTestTemplate):
         ToolsHelper.print_output(
             "/sbin/ifconfig {} inet {}/24 up".format(ifname, self._SERVER_IP)
         )
+        # Import in the correct vnet, so as to not confuse Scapy
+        import scapy.all as sp
+        self.sp = sp
         return ifname
 
     def _do_dora(self, ifname, first_extra_opts=None, extra_opts=None,
@@ -123,33 +87,33 @@ class TestDhclient(VnetTestTemplate):
         def handle_dhcp(pkt):
             nonlocal discover_count, done
 
-            msg_type = get_dhcp_type(pkt)
+            msg_type = self.get_dhcp_type(pkt)
             if msg_type is None:
                 return
 
-            client_mac = pkt[sp.Ether].src
-            xid = pkt[sp.BOOTP].xid
+            client_mac = pkt[self.sp.Ether].src
+            xid = pkt[self.sp.BOOTP].xid
 
             if msg_type == "discover":
                 discover_count += 1
                 opts = extra_opts
                 if discover_count == 1 and first_extra_opts is not None:
                     opts = first_extra_opts
-                reply = build_reply(
+                reply = self.build_reply(
                     ifname, self._SERVER_IP, self._CLIENT_IP, client_mac,
                     xid, "offer", extra_opts=opts, sname=sname, file=file,
                 )
-                sp.sendp(reply, iface=ifname, verbose=0)
+                self.sp.sendp(reply, iface=ifname, verbose=0)
 
             elif msg_type == "request":
-                reply = build_reply(
+                reply = self.build_reply(
                     ifname, self._SERVER_IP, self._CLIENT_IP, client_mac,
                     xid, "ack", sname=sname, file=file,
                 )
-                sp.sendp(reply, iface=ifname, verbose=0)
+                self.sp.sendp(reply, iface=ifname, verbose=0)
                 done = True
 
-        sniffer = sp.AsyncSniffer(
+        sniffer = self.sp.AsyncSniffer(
             iface=ifname,
             filter="udp and dst port 67",
             prn=handle_dhcp,
@@ -165,6 +129,45 @@ class TestDhclient(VnetTestTemplate):
         result = self.wait_object(self.vnet_map["vnet1"].pipe, timeout=15)
         return discover_count, result
 
+
+    def get_dhcp_type(self, pkt):
+        """Extract the DHCP message type from a packet."""
+        if not pkt.haslayer(self.sp.DHCP):
+            return None
+        for opt in pkt[self.sp.DHCP].options:
+            if isinstance(opt, tuple) and opt[0] == "message-type":
+                return self.sp.DHCPTypes[opt[1]]
+        return None
+
+    def build_reply(self, iface, server_ip, client_ip, client_mac, xid, msg_type,
+                    extra_opts=None, sname=b"", file=b""):
+        """Build a DHCP OFFER or ACK packet."""
+        options = [
+            ("message-type", msg_type),
+            ("server_id", server_ip),
+            ("lease_time", 3600),
+            ("subnet_mask", "255.255.255.0"),
+            ("router", server_ip),
+        ]
+        if extra_opts:
+            options.extend(extra_opts)
+        options.append("end")
+
+        return (
+            self.sp.Ether(dst=client_mac, src=self.sp.get_if_hwaddr(iface))
+            / self.sp.IP(src=server_ip, dst="255.255.255.255")
+            / self.sp.UDP(sport=67, dport=68)
+            / self.sp.BOOTP(op=2,
+                xid=xid,
+                yiaddr=client_ip,
+                siaddr=server_ip,
+                chaddr=mac_to_chaddr(client_mac),
+                sname=sname,
+                file=file,
+            )
+            / self.sp.DHCP(options=options)
+        )
+
     @pytest.mark.require_user("root")
     def test_classless_routes_truncated(self):
         """