diff --git a/munet/base.py b/munet/base.py index efede4b..932d5af 100644 --- a/munet/base.py +++ b/munet/base.py @@ -2674,6 +2674,34 @@ def add_host(self, name, cls=LinuxNamespace, **kwargs): return self.hosts[name] + def add_dummy(self, node1, if1, mtu=None, **intf_constraints): + """Add a dummy for an interface with no link.""" + + try: + name1 = node1.name + except AttributeError: + if node1 in self.switches: + node1 = self.switches[node1] + else: + node1 = self.hosts[node1] + name1 = node1.name + + lname = "{}:{}".format(name1, if1) + self.logger.debug("%s: add_dummy %s", self, lname) + lhost = self.hosts[name1] + + nsif1 = lhost.get_ns_ifname(if1) + lhost.cmd_raises_nsonly(f"ip link add name {nsif1} type dummy") + + if mtu: + lhost.cmd_raises_nsonly(f"ip link set {nsif1} mtu {mtu}") + lhost.cmd_raises_nsonly(f"ip link set {nsif1} up") + lhost.register_interface(if1) + + # Setup interface constraints if provided + if intf_constraints: + node1.set_intf_constraints(if1, **intf_constraints) + def add_link(self, node1, node2, if1, if2, mtu=None, **intf_constraints): """Add a link between switch and node or 2 nodes. diff --git a/munet/native.py b/munet/native.py index 3d6fde5..2605747 100644 --- a/munet/native.py +++ b/munet/native.py @@ -864,6 +864,31 @@ def get_ifname(self, netname): return c["name"] return None + def set_dummy_addr(self, cconf): + if ip := cconf.get("ip"): + ipaddr = ipaddress.ip_interface(ip) + assert ipaddr.version == 4 + else: + ipaddr = None + + if ip := cconf.get("ipv6"): + ip6addr = ipaddress.ip_interface(ip) + assert ipaddr.version == 6 + else: + ip6addr = None + + if "physical" in cconf or self.is_vm: + return + + ifname = cconf["name"] + for ip in (ipaddr, ip6addr): + if ip is None: + continue + self.set_intf_addr(ifname, ip) + ipcmd = "ip " if ip.version == 4 else "ip -6 " + self.logger.debug("%s: adding %s to unconnected intf %s", self, ip, ifname) + self.intf_ip_cmd(ifname, ipcmd + f"addr add {ip} dev {ifname}") + def set_lan_addr(self, switch, cconf): if ip := cconf.get("ip"): ipaddr = ipaddress.ip_interface(ip) @@ -2756,8 +2781,9 @@ async def _async_build(self, logger=None): if "connections" not in nconf: continue for cconf in nconf["connections"]: - # Eventually can add support for unconnected intf here. if "to" not in cconf: + # unconnected intf + await self.add_dummy_link(node, cconf) continue to = cconf["to"] if to in self.switches: @@ -2778,6 +2804,25 @@ def autonumber(self): def autonumber(self, value): self.topoconf["networks-autonumber"] = bool(value) + async def add_dummy_link(self, node1, c1=None): + c1 = {} if c1 is None else c1 + + if "name" not in c1: + c1["name"] = node1.get_next_intf_name() + if1 = c1["name"] + + do_add_dummy = True + if "hostintf" in c1: + await n.add_host_intf(c1["hostintf"], c1["name"], mtu=c1.get("mtu")) + do_add_dummy = False + elif "physical" in c1: + await n.add_phy_intf(c1["physical"], c1["name"]) + do_add_dummy = False + + if do_add_dummy: + super().add_dummy(node1, if1, **c1) + node1.set_dummy_addr(c1) + async def add_native_link(self, node1, node2, c1=None, c2=None): """Add a link between switch and node or 2 nodes.""" isp2p = False diff --git a/tests/unconnected/munet.yaml b/tests/unconnected/munet.yaml new file mode 100644 index 0000000..90a4961 --- /dev/null +++ b/tests/unconnected/munet.yaml @@ -0,0 +1,31 @@ +version: 1 +topology: + networks-autonumber: true + ipv6-enable: true + networks: + - name: net0 + mtu: 5000 + nodes: + - name: r1 + connections: + - to: net0 + name: eth0 + mtu: 4500 + - name: unconnected + ip: 172.16.0.1/24 + mtu: 9000 + cmd: | + ip addr show + ip -6 addr show + which ping + tail -f /dev/null + - name: r2 + connections: + - to: "net0" + name: eth0 + mtu: 4500 + cmd: | + ip addr show + ip -6 addr show + which ping + tail -f /dev/null diff --git a/tests/unconnected/test_unconnected.py b/tests/unconnected/test_unconnected.py new file mode 100644 index 0000000..6884032 --- /dev/null +++ b/tests/unconnected/test_unconnected.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# June 28 2023, Eric Kinzie +# +# Copyright 2023, LabN Consulting, L.L.C. +# +"Testing of unconnected interface." +import logging + +import pytest + +# All tests are coroutines +pytestmark = pytest.mark.asyncio + + +@pytest.mark.parametrize( + "unet_perfunc", ["munet", "noinit", "noinit-noshell"], indirect=["unet_perfunc"] +) +async def test_unconnected_presence(unet_perfunc): + unet = unet_perfunc + rc, o, e = await unet.hosts["r1"].async_cmd_status(f"ip addr show dev unconnected") + assert rc == 0 + assert o.find("mtu 9000") > -1 + assert o.find("inet 172.16.0.1/24") > -1 + + +async def test_basic_ping(unet_perfunc): + unet = unet_perfunc + r1eth0 = unet.hosts["r1"].get_intf_addr("eth0").ip + logging.debug("r1eth0 is %s", r1eth0) + rc, o, e = await unet.hosts["r2"].async_cmd_status( + f"ip ro add 172.16.0.0/24 via {r1eth0}" + ) + assert rc == 0 + o = await unet.hosts["r2"].async_cmd_raises(f"ping -w1 -c1 172.16.0.1") + logging.debug("ping r2 output: %s", o)