diff mbox series

[net-next,6/7] selftests: drivers: add scaffolding for Netlink tests in Python

Message ID 20240402010520.1209517-7-kuba@kernel.org (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series selftests: net: groundwork for YNL-based tests | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next, async
netdev/ynl fail Generated files up to date; build failed; build has 6 warnings/errors; GEN HAS DIFF 2 files changed, 15 insertions(+);
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 949 this patch: 949
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers success CCed 6 of 6 maintainers
netdev/build_clang success Errors and warnings before: 955 this patch: 955
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 961 this patch: 961
netdev/checkpatch warning WARNING: Missing or malformed SPDX-License-Identifier tag in line 1 WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest fail net-next-2024-04-02--12-00 (tests: 947)

Commit Message

Jakub Kicinski April 2, 2024, 1:05 a.m. UTC
Add drivers/net as a target for mixed-use tests.
The setup is expected to work similarly to the forwarding tests.
Since we only need one interface (unlike forwarding tests)
read the target device name from NETIF. If not present we'll
try to run the test against netdevsim.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 tools/testing/selftests/Makefile              |   3 +-
 tools/testing/selftests/drivers/net/Makefile  |   7 ++
 .../testing/selftests/drivers/net/README.rst  |  30 +++++
 .../selftests/drivers/net/lib/py/__init__.py  |  17 +++
 .../selftests/drivers/net/lib/py/env.py       |  41 ++++++
 .../testing/selftests/net/lib/py/__init__.py  |   1 +
 tools/testing/selftests/net/lib/py/nsim.py    | 118 ++++++++++++++++++
 7 files changed, 216 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/drivers/net/Makefile
 create mode 100644 tools/testing/selftests/drivers/net/README.rst
 create mode 100644 tools/testing/selftests/drivers/net/lib/py/__init__.py
 create mode 100644 tools/testing/selftests/drivers/net/lib/py/env.py
 create mode 100644 tools/testing/selftests/net/lib/py/nsim.py

Comments

David Wei April 3, 2024, 3:06 a.m. UTC | #1
On 2024-04-01 18:05, Jakub Kicinski wrote:
> Add drivers/net as a target for mixed-use tests.
> The setup is expected to work similarly to the forwarding tests.
> Since we only need one interface (unlike forwarding tests)
> read the target device name from NETIF. If not present we'll
> try to run the test against netdevsim.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  tools/testing/selftests/Makefile              |   3 +-
>  tools/testing/selftests/drivers/net/Makefile  |   7 ++
>  .../testing/selftests/drivers/net/README.rst  |  30 +++++
>  .../selftests/drivers/net/lib/py/__init__.py  |  17 +++
>  .../selftests/drivers/net/lib/py/env.py       |  41 ++++++
>  .../testing/selftests/net/lib/py/__init__.py  |   1 +
>  tools/testing/selftests/net/lib/py/nsim.py    | 118 ++++++++++++++++++
>  7 files changed, 216 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/drivers/net/Makefile
>  create mode 100644 tools/testing/selftests/drivers/net/README.rst
>  create mode 100644 tools/testing/selftests/drivers/net/lib/py/__init__.py
>  create mode 100644 tools/testing/selftests/drivers/net/lib/py/env.py
>  create mode 100644 tools/testing/selftests/net/lib/py/nsim.py
> 
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 0cffdfb4b116..d015ec14a85e 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -17,6 +17,7 @@ TARGETS += devices
>  TARGETS += dmabuf-heaps
>  TARGETS += drivers/dma-buf
>  TARGETS += drivers/s390x/uvdevice
> +TARGETS += drivers/net
>  TARGETS += drivers/net/bonding
>  TARGETS += drivers/net/team
>  TARGETS += dt
> @@ -117,7 +118,7 @@ TARGETS_HOTPLUG = cpu-hotplug
>  TARGETS_HOTPLUG += memory-hotplug
>  
>  # Networking tests want the net/lib target, include it automatically
> -ifneq ($(filter net ,$(TARGETS)),)
> +ifneq ($(filter net drivers/net,$(TARGETS)),)
>  ifeq ($(filter net/lib,$(TARGETS)),)
>  	override TARGETS := $(TARGETS) net/lib
>  endif
> diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
> new file mode 100644
> index 000000000000..379cdb1960a7
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +TEST_INCLUDES := $(wildcard lib/py/*.py)
> +
> +TEST_PROGS := stats.py
> +
> +include ../../lib.mk
> diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
> new file mode 100644
> index 000000000000..5ef7c417d431
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/README.rst
> @@ -0,0 +1,30 @@
> +Running tests
> +=============
> +
> +Tests are executed within kselftest framework like any other tests.
> +By default tests execute against software drivers such as netdevsim.
> +All tests must support running against a real device (SW-only tests
> +should instead be placed in net/ or drivers/net/netdevsim, HW-only
> +tests in drivers/net/hw).
> +
> +Set appropriate variables to point the tests at a real device.
> +
> +Variables
> +=========
> +
> +Variables can be set in the environment or by creating a net.config
> +file in the same directory as this README file. Example::
> +
> +  $ NETIF=eth0 ./some_test.sh
> +
> +or::
> +
> +  $ cat tools/testing/selftests/drivers/net/net.config
> +  # Variable set in a file
> +  NETIF=eth0
> +
> +NETIF
> +~~~~~
> +
> +Name of the netdevice against which the test should be executed.
> +When empty or not set software devices will be used.
> diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
> new file mode 100644
> index 000000000000..4653dffcd962
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import sys
> +from pathlib import Path
> +
> +KSFT_DIR = (Path(__file__).parent / "../../../..").resolve()
> +
> +try:
> +    sys.path.append(KSFT_DIR.as_posix())
> +    from net.lib.py import *
> +except ModuleNotFoundError as e:
> +    ksft_pr("Failed importing `net` library from kernel sources")
> +    ksft_pr(str(e))
> +    ktap_result(True, comment="SKIP")
> +    sys.exit(4)
> +
> +from .env import *
> diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
> new file mode 100644
> index 000000000000..ee4a44555d83
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/lib/py/env.py
> @@ -0,0 +1,41 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import os
> +import shlex
> +from pathlib import Path
> +from lib.py import ip
> +from lib.py import NetdevSimDev

nit: these could be on the same line.

> +
> +class NetDrvEnv:
> +    def __init__(self, src_path):
> +        self.env = os.environ.copy()
> +        self._load_env_file(src_path)
> +
> +        if 'NETIF' in self.env:
> +            self._ns = None

My brain interprets 'ns' as 'namespace'. How about something like
nsimdev/nsdev/nsim?

> +            self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0]
> +        else:
> +            self._ns = NetdevSimDev()
> +            self.dev = self._ns.nsims[0].dev
> +        self.ifindex = self.dev['ifindex']
> +
> +    def __del__(self):
> +        if self._ns:
> +            self._ns.remove()
> +
> +    def _load_env_file(self, src_path):
> +        src_dir = Path(src_path).parent.resolve()
> +        if not (src_dir / "net.config").exists():
> +            return
> +
> +        lexer = shlex.shlex(open((src_dir / "net.config").as_posix(), 'r').read())
> +        k = None
> +        for token in lexer:
> +            if k is None:
> +                k = token
> +                self.env[k] = ""
> +            elif token == "=":
> +                pass
> +            else:
> +                self.env[k] = token
> +                k = None
> diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
> index 81a8d14b68f0..99cfc8dc4dca 100644
> --- a/tools/testing/selftests/net/lib/py/__init__.py
> +++ b/tools/testing/selftests/net/lib/py/__init__.py
> @@ -3,4 +3,5 @@
>  from .ksft import *
>  from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
>  from .consts import KSRC
> +from .nsim import *
>  from .utils import *
> diff --git a/tools/testing/selftests/net/lib/py/nsim.py b/tools/testing/selftests/net/lib/py/nsim.py
> new file mode 100644
> index 000000000000..13eb42c82829
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/py/nsim.py
> @@ -0,0 +1,118 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import json
> +import os
> +import random
> +import re
> +import time
> +from .utils import cmd, ip
> +
> +
> +class NetdevSim:
> +    """
> +    Class for netdevsim netdevice and its attributes.
> +    """
> +
> +    def __init__(self, nsimdev, port_index, ifname, ns=None):
> +        # In case udev renamed the netdev to according to new schema,
> +        # check if the name matches the port_index.
> +        nsimnamere = re.compile("eni\d+np(\d+)")
> +        match = nsimnamere.match(ifname)
> +        if match and int(match.groups()[0]) != port_index + 1:
> +            raise Exception("netdevice name mismatches the expected one")
> +
> +        self.ifname = ifname
> +        self.nsimdev = nsimdev
> +        self.port_index = port_index
> +        self.ns = ns
> +        self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
> +        ret = ip("-j link show dev %s" % ifname, ns=ns)
> +        self.dev = json.loads(ret.stdout)[0]
> +
> +    def dfs_write(self, path, val):
> +        self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val)
> +
> +
> +class NetdevSimDev:
> +    """
> +    Class for netdevsim bus device and its attributes.
> +    """
> +    @staticmethod
> +    def ctrl_write(path, val):
> +        fullpath = os.path.join("/sys/bus/netdevsim/", path)
> +        with open(fullpath, "w") as f:
> +            f.write(val)
> +
> +    def dfs_write(self, path, val):
> +        fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path)
> +        with open(fullpath, "w") as f:
> +            f.write(val)
> +
> +    def __init__(self, port_count=1, ns=None):
> +        # nsim will spawn in init_net, we'll set to actual ns once we switch it the.sre
> +        self.ns = None
> +
> +        if not os.path.exists("/sys/bus/netdevsim"):
> +            cmd("modprobe netdevsim")
> +
> +        addr = random.randrange(1 << 15)
> +        while True:
> +            try:
> +                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
> +            except OSError as e:
> +                if e.errno == errno.ENOSPC:
> +                    addr = random.randrange(1 << 15)
> +                    continue
> +                raise e
> +            break
> +        self.addr = addr
> +
> +        # As probe of netdevsim device might happen from a workqueue,
> +        # so wait here until all netdevs appear.
> +        self.wait_for_netdevs(port_count)
> +
> +        if ns:
> +            cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}")
> +            self.ns = ns
> +
> +        cmd("udevadm settle", ns=self.ns)
> +        ifnames = self.get_ifnames()
> +
> +        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
> +
> +        self.nsims = []
> +        for port_index in range(port_count):
> +            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index],
> +                                        ns=ns))
> +
> +    def get_ifnames(self):
> +        ifnames = []
> +        listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/",
> +                      ns=self.ns).stdout.split()
> +        for ifname in listdir:
> +            ifnames.append(ifname)
> +        ifnames.sort()
> +        return ifnames
> +
> +    def wait_for_netdevs(self, port_count):
> +        timeout = 5
> +        timeout_start = time.time()
> +
> +        while True:
> +            try:
> +                ifnames = self.get_ifnames()
> +            except FileNotFoundError as e:
> +                ifnames = []
> +            if len(ifnames) == port_count:
> +                break
> +            if time.time() < timeout_start + timeout:
> +                continue
> +            raise Exception("netdevices did not appear within timeout")
> +
> +    def remove(self):
> +        self.ctrl_write("del_device", "%u" % (self.addr, ))

I really want this to be in the dtor, but couldn't get it to work
because Python doesn't let you open files in __del__(). :(

> +
> +    def remove_nsim(self, nsim):
> +        self.nsims.remove(nsim)
> +        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
> +                        "%u" % (nsim.port_index, ))
diff mbox series

Patch

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 0cffdfb4b116..d015ec14a85e 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -17,6 +17,7 @@  TARGETS += devices
 TARGETS += dmabuf-heaps
 TARGETS += drivers/dma-buf
 TARGETS += drivers/s390x/uvdevice
+TARGETS += drivers/net
 TARGETS += drivers/net/bonding
 TARGETS += drivers/net/team
 TARGETS += dt
@@ -117,7 +118,7 @@  TARGETS_HOTPLUG = cpu-hotplug
 TARGETS_HOTPLUG += memory-hotplug
 
 # Networking tests want the net/lib target, include it automatically
-ifneq ($(filter net ,$(TARGETS)),)
+ifneq ($(filter net drivers/net,$(TARGETS)),)
 ifeq ($(filter net/lib,$(TARGETS)),)
 	override TARGETS := $(TARGETS) net/lib
 endif
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
new file mode 100644
index 000000000000..379cdb1960a7
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_INCLUDES := $(wildcard lib/py/*.py)
+
+TEST_PROGS := stats.py
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
new file mode 100644
index 000000000000..5ef7c417d431
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/README.rst
@@ -0,0 +1,30 @@ 
+Running tests
+=============
+
+Tests are executed within kselftest framework like any other tests.
+By default tests execute against software drivers such as netdevsim.
+All tests must support running against a real device (SW-only tests
+should instead be placed in net/ or drivers/net/netdevsim, HW-only
+tests in drivers/net/hw).
+
+Set appropriate variables to point the tests at a real device.
+
+Variables
+=========
+
+Variables can be set in the environment or by creating a net.config
+file in the same directory as this README file. Example::
+
+  $ NETIF=eth0 ./some_test.sh
+
+or::
+
+  $ cat tools/testing/selftests/drivers/net/net.config
+  # Variable set in a file
+  NETIF=eth0
+
+NETIF
+~~~~~
+
+Name of the netdevice against which the test should be executed.
+When empty or not set software devices will be used.
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
new file mode 100644
index 000000000000..4653dffcd962
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
@@ -0,0 +1,17 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+import sys
+from pathlib import Path
+
+KSFT_DIR = (Path(__file__).parent / "../../../..").resolve()
+
+try:
+    sys.path.append(KSFT_DIR.as_posix())
+    from net.lib.py import *
+except ModuleNotFoundError as e:
+    ksft_pr("Failed importing `net` library from kernel sources")
+    ksft_pr(str(e))
+    ktap_result(True, comment="SKIP")
+    sys.exit(4)
+
+from .env import *
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
new file mode 100644
index 000000000000..ee4a44555d83
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -0,0 +1,41 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+import os
+import shlex
+from pathlib import Path
+from lib.py import ip
+from lib.py import NetdevSimDev
+
+class NetDrvEnv:
+    def __init__(self, src_path):
+        self.env = os.environ.copy()
+        self._load_env_file(src_path)
+
+        if 'NETIF' in self.env:
+            self._ns = None
+            self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0]
+        else:
+            self._ns = NetdevSimDev()
+            self.dev = self._ns.nsims[0].dev
+        self.ifindex = self.dev['ifindex']
+
+    def __del__(self):
+        if self._ns:
+            self._ns.remove()
+
+    def _load_env_file(self, src_path):
+        src_dir = Path(src_path).parent.resolve()
+        if not (src_dir / "net.config").exists():
+            return
+
+        lexer = shlex.shlex(open((src_dir / "net.config").as_posix(), 'r').read())
+        k = None
+        for token in lexer:
+            if k is None:
+                k = token
+                self.env[k] = ""
+            elif token == "=":
+                pass
+            else:
+                self.env[k] = token
+                k = None
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index 81a8d14b68f0..99cfc8dc4dca 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -3,4 +3,5 @@ 
 from .ksft import *
 from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
 from .consts import KSRC
+from .nsim import *
 from .utils import *
diff --git a/tools/testing/selftests/net/lib/py/nsim.py b/tools/testing/selftests/net/lib/py/nsim.py
new file mode 100644
index 000000000000..13eb42c82829
--- /dev/null
+++ b/tools/testing/selftests/net/lib/py/nsim.py
@@ -0,0 +1,118 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+import json
+import os
+import random
+import re
+import time
+from .utils import cmd, ip
+
+
+class NetdevSim:
+    """
+    Class for netdevsim netdevice and its attributes.
+    """
+
+    def __init__(self, nsimdev, port_index, ifname, ns=None):
+        # In case udev renamed the netdev to according to new schema,
+        # check if the name matches the port_index.
+        nsimnamere = re.compile("eni\d+np(\d+)")
+        match = nsimnamere.match(ifname)
+        if match and int(match.groups()[0]) != port_index + 1:
+            raise Exception("netdevice name mismatches the expected one")
+
+        self.ifname = ifname
+        self.nsimdev = nsimdev
+        self.port_index = port_index
+        self.ns = ns
+        self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
+        ret = ip("-j link show dev %s" % ifname, ns=ns)
+        self.dev = json.loads(ret.stdout)[0]
+
+    def dfs_write(self, path, val):
+        self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val)
+
+
+class NetdevSimDev:
+    """
+    Class for netdevsim bus device and its attributes.
+    """
+    @staticmethod
+    def ctrl_write(path, val):
+        fullpath = os.path.join("/sys/bus/netdevsim/", path)
+        with open(fullpath, "w") as f:
+            f.write(val)
+
+    def dfs_write(self, path, val):
+        fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path)
+        with open(fullpath, "w") as f:
+            f.write(val)
+
+    def __init__(self, port_count=1, ns=None):
+        # nsim will spawn in init_net, we'll set to actual ns once we switch it the.sre
+        self.ns = None
+
+        if not os.path.exists("/sys/bus/netdevsim"):
+            cmd("modprobe netdevsim")
+
+        addr = random.randrange(1 << 15)
+        while True:
+            try:
+                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
+            except OSError as e:
+                if e.errno == errno.ENOSPC:
+                    addr = random.randrange(1 << 15)
+                    continue
+                raise e
+            break
+        self.addr = addr
+
+        # As probe of netdevsim device might happen from a workqueue,
+        # so wait here until all netdevs appear.
+        self.wait_for_netdevs(port_count)
+
+        if ns:
+            cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}")
+            self.ns = ns
+
+        cmd("udevadm settle", ns=self.ns)
+        ifnames = self.get_ifnames()
+
+        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
+
+        self.nsims = []
+        for port_index in range(port_count):
+            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index],
+                                        ns=ns))
+
+    def get_ifnames(self):
+        ifnames = []
+        listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/",
+                      ns=self.ns).stdout.split()
+        for ifname in listdir:
+            ifnames.append(ifname)
+        ifnames.sort()
+        return ifnames
+
+    def wait_for_netdevs(self, port_count):
+        timeout = 5
+        timeout_start = time.time()
+
+        while True:
+            try:
+                ifnames = self.get_ifnames()
+            except FileNotFoundError as e:
+                ifnames = []
+            if len(ifnames) == port_count:
+                break
+            if time.time() < timeout_start + timeout:
+                continue
+            raise Exception("netdevices did not appear within timeout")
+
+    def remove(self):
+        self.ctrl_write("del_device", "%u" % (self.addr, ))
+
+    def remove_nsim(self, nsim):
+        self.nsims.remove(nsim)
+        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
+                        "%u" % (nsim.port_index, ))