Commit 720669b775 for frr

commit 720669b7757adf752b5f852f474793ecc7f92a02
Author: Donald Sharp <sharpd@nvidia.com>
Date:   Thu Oct 1 15:05:05 2026 -0400

    tests: Create a freebsd version of create_interface_in_kernel

    Create a bsd version of this function that creates a loopback
    device and adds the address to it.

    Signed-off-by: Donald Sharp <sharpd@nvidia.com>

diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py
index 611539d9da..387da60acf 100644
--- a/tests/topotests/lib/common_config.py
+++ b/tests/topotests/lib/common_config.py
@@ -1548,39 +1548,70 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False):


 def create_interface_in_kernel(
-    tgen, dut, name, ip_addr, vrf=None, netmask=None, create=True
+    tgen, dut, name, ip_addr=None, vrf=None, netmask=None, create=True
 ):
     """
-    Cretae interfaces in kernel for ipv4/ipv6
-    Config is done in Linux Kernel:
+    Create an interface in the kernel and optionally assign an address.
+
+    Linux creates a dummy device. FreeBSD creates a lo(4) clone and renames
+    it to ``name``. The interface is brought up. Omit ``ip_addr`` to leave
+    the interface with no address.

     Parameters
     ----------
     * `tgen` : Topogen object
     * `dut` : Device for which interfaces to be added
     * `name` : interface name
-    * `ip_addr` : ip address for interface
-    * `vrf` : VRF name, to which interface will be associated
+    * `ip_addr` : ip address for interface, or None
+    * `vrf` : VRF name, to which interface will be associated.
+              Applied on Linux only.
     * `netmask` : netmask value, default is None
     * `create`: Create interface in kernel, if created then no need
                 to create
     """

+    if sys.platform.startswith("freebsd"):
+        create_interface_in_kernel_freebsd(
+            tgen, dut, name, ip_addr, vrf, netmask, create
+        )
+    else:
+        create_interface_in_kernel_linux(tgen, dut, name, ip_addr, vrf, netmask, create)
+
+
+def _kernel_interface_address(ip_addr, netmask):
+    """Return an ipaddress interface for ip_addr and an optional netmask."""
+    if not netmask:
+        return ipaddress.ip_interface(frr_unicode(ip_addr))
+    return ipaddress.ip_interface(
+        "{}/{}".format(frr_unicode(ip_addr), frr_unicode(netmask))
+    )
+
+
+def create_interface_in_kernel_linux(
+    tgen, dut, name, ip_addr=None, vrf=None, netmask=None, create=True
+):
+    """Create a Linux dummy interface and optionally assign an address."""
+
     rnode = tgen.gears[dut]

     if create:
         cmd = "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name)
         rnode.run(cmd)

-    if not netmask:
-        ifaddr = ipaddress.ip_interface(frr_unicode(ip_addr))
-    else:
-        ifaddr = ipaddress.ip_interface(
-            "{}/{}".format(frr_unicode(ip_addr), frr_unicode(netmask))
-        )
-    cmd = "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
-        ifaddr.version, name, ifaddr
-    )
+    if not ip_addr:
+        if vrf:
+            cmd = "ip link set {} master {}".format(name, vrf)
+            logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
+            rnode.run(cmd)
+        cmd = "ip link set {} up".format(name)
+        logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
+        rnode.run(cmd)
+        return
+
+    ifaddr = _kernel_interface_address(ip_addr, netmask)
+    cmd = (
+        "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up"
+    ).format(ifaddr.version, name, ifaddr)
     logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
     rnode.run(cmd)

@@ -1589,6 +1620,84 @@ def create_interface_in_kernel(
         rnode.run(cmd)


+def _freebsd_run(rnode, dut, args):
+    """Run an ifconfig argv inside the router and log it."""
+    logger.debug("[DUT: %s]: Running command: %s", dut, " ".join(args))
+    rnode.cmd_raises(args)
+
+
+def _freebsd_clone_loopback(rnode):
+    """Create a lo(4) clone and return the name ifconfig printed.
+
+    ``ifconfig lo create`` asks for the next unit. Once an interface is
+    named ``lo``, the kernel rejects that with EEXIST, so try an explicit
+    unit.
+    """
+
+    names = ["lo"] + ["lo{}".format(unit) for unit in range(1, 128)]
+    last = ""
+    for ifname in names:
+        rc, out, err = rnode.net.cmd_status(
+            ["/sbin/ifconfig", ifname, "create"], warn=False
+        )
+        if rc:
+            last = "{}{}".format(out or "", err or "").strip()
+            continue
+        text = (out or "").strip() or (err or "").strip()
+        created = text.split()[-1] if text else ""
+        if not created:
+            raise RuntimeError("ifconfig {} create returned no name".format(ifname))
+        return created
+    raise RuntimeError("could not clone a loopback: {}".format(last))
+
+
+def _freebsd_flush_global(rnode, dut, name, version):
+    """Delete global addresses of one family. Leave link-local and localhost."""
+
+    rc, out, _ = rnode.net.cmd_status(["/sbin/ifconfig", name], warn=False)
+    if rc or not out:
+        return
+    for line in out.splitlines():
+        parts = line.split()
+        if len(parts) < 2:
+            continue
+        kind, addr = parts[0], parts[1]
+        if version == 4 and kind == "inet" and not addr.startswith("127."):
+            _freebsd_run(rnode, dut, ["/sbin/ifconfig", name, "inet", addr, "delete"])
+        elif version == 6 and kind == "inet6":
+            bare = addr.split("%", 1)[0]
+            if bare == "::1" or bare.lower().startswith("fe80:"):
+                continue
+            _freebsd_run(rnode, dut, ["/sbin/ifconfig", name, "inet6", bare, "delete"])
+
+
+def create_interface_in_kernel_freebsd(
+    tgen, dut, name, ip_addr=None, vrf=None, netmask=None, create=True
+):
+    """Create a lo(4) clone, name it ``name``, and optionally assign an address."""
+
+    rnode = tgen.gears[dut]
+
+    if create:
+        rc, _, _ = rnode.net.cmd_status(["/sbin/ifconfig", name], warn=False)
+        if rc:
+            created = _freebsd_clone_loopback(rnode)
+            if created != name:
+                _freebsd_run(rnode, dut, ["/sbin/ifconfig", created, "name", name])
+
+    if ip_addr:
+        ifaddr = _kernel_interface_address(ip_addr, netmask)
+        _freebsd_flush_global(rnode, dut, name, ifaddr.version)
+        family = "inet6" if ifaddr.version == 6 else "inet"
+        if ifaddr.version == 6:
+            _freebsd_run(rnode, dut, ["/sbin/ifconfig", name, "inet6", "-ifdisabled"])
+        _freebsd_run(rnode, dut, ["/sbin/ifconfig", name, family, str(ifaddr)])
+    _freebsd_run(rnode, dut, ["/sbin/ifconfig", name, "up"])
+
+    if vrf:
+        logger.debug("[DUT: %s]: %s stays in the base FIB (vrf %s)", dut, name, vrf)
+
+
 def shutdown_bringup_interface_in_kernel(tgen, dut, intf_name, ifaceaction=False):
     """
     Cretae interfaces in kernel for ipv4/ipv6