Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion munet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,7 @@ def __init__(self, *args, **kwargs):
# logging.warning("InterfaceMixin: args: %s kwargs: %s", args, kwargs)

self._intf_addrs = defaultdict(lambda: [None, None])
self._peer_intf_addrs = defaultdict(lambda: [None, None])
self.net_intfs = {}
self.next_intf_index = 0
self.basename = "eth"
Expand All @@ -1630,9 +1631,16 @@ def get_intf_addr(self, ifname, ipv6=False):
return None
return self._intf_addrs[ifname][bool(ipv6)]

def set_intf_addr(self, ifname, ifaddr):
def get_peer_intf_addr(self, ifname, ipv6=False):
if ifname not in self._peer_intf_addrs:
return None
return self._peer_intf_addrs[ifname][bool(ipv6)]

def set_intf_addr(self, ifname, ifaddr, peer_ifaddr=None):
ifaddr = ipaddress.ip_interface(ifaddr)
self._intf_addrs[ifname][ifaddr.version == 6] = ifaddr
if peer_ifaddr is not None:
self._peer_intf_addrs[ifname][peer_ifaddr.version == 6] = peer_ifaddr

def net_addr(self, netname, ipv6=False):
if netname not in self.net_intfs:
Expand Down Expand Up @@ -2883,6 +2891,33 @@ 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.

Expand Down
101 changes: 94 additions & 7 deletions munet/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,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 ip6addr.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)
Expand Down Expand Up @@ -1055,20 +1080,42 @@ def _set_p2p_addr(self, other, cconf, occonf, ipv6=False):
return

if ipaddr:
# Check if the two sides of this link are assigned
# different subnets. If so, set the peer address.
set_peer = False
if oipaddr and ipaddr.network != oipaddr.network:
set_peer = True
ifname = cconf["name"]
self.set_intf_addr(ifname, ipaddr)
self.set_intf_addr(ifname, ipaddr, oipaddr)
self.logger.debug("%s: adding %s to p2p intf %s", self, ipaddr, ifname)
if "physical" not in cconf and not self.is_vm:
self.intf_ip_cmd(ifname, f"ip addr add {ipaddr} dev {ifname}")
if set_peer:
self.logger.debug("%s: setting peer address %s", self, oipaddr)
self.intf_ip_cmd(
ifname,
f"ip addr add {ipaddr.ip} peer {oipaddr.network} dev {ifname}",
)
else:
self.intf_ip_cmd(ifname, f"ip addr add {ipaddr} dev {ifname}")

if oipaddr:
set_peer = False
if ipaddr and ipaddr.network != oipaddr.network:
set_peer = True
oifname = occonf["name"]
other.set_intf_addr(oifname, oipaddr)
other.set_intf_addr(oifname, oipaddr, ipaddr)
self.logger.debug(
"%s: adding %s to other p2p intf %s", other, oipaddr, oifname
)
if "physical" not in occonf and not other.is_vm:
other.intf_ip_cmd(oifname, f"ip addr add {oipaddr} dev {oifname}")
if set_peer:
other.logger.debug("%s: setting peer address %s", other, ipaddr)
other.intf_ip_cmd(
oifname,
f"ip addr add {oipaddr.ip} peer {ipaddr.network} dev {oifname}",
)
else:
other.intf_ip_cmd(oifname, f"ip addr add {oipaddr} dev {oifname}")

def set_p2p_addr(self, other, cconf, occonf):
self._set_p2p_addr(other, cconf, occonf, ipv6=False)
Expand Down Expand Up @@ -2225,13 +2272,33 @@ async def renumber_interfaces(self):
con.cmd_raises(f"ip -4 addr flush dev {ifname}")
sw_is_nat = switch and hasattr(switch, "is_nat") and switch.is_nat
if ifaddr := self.get_intf_addr(ifname, ipv6=False):
con.cmd_raises(f"ip addr add {ifaddr} dev {ifname}")
oifaddr = self.get_peer_intf_addr(ifname, ipv6=False)
if (
not switch
and oifaddr is not None
and ifaddr.network != oifaddr.network
):
con.cmd_raises(
f"ip addr add {ifaddr.ip} peer {oifaddr.network} dev {ifname}"
)
else:
con.cmd_raises(f"ip addr add {ifaddr} dev {ifname}")
if sw_is_nat:
# In case there was some preconfig e.g., cloud-init
con.cmd_raises("ip route flush exact default")
con.cmd_raises(f"ip route add default via {switch.ip_address}")
if ifaddr := self.get_intf_addr(ifname, ipv6=True):
con.cmd_raises(f"ip -6 addr add {ifaddr} dev {ifname}")
oifaddr = self.get_peer_intf_addr(ifname, ipv6=True)
if (
not switch
and oifaddr is not None
and ifaddr.network != oifaddr.network
):
con.cmd_raises(
f"ip addr add {ifaddr.ip} peer {oifaddr.network} dev {ifname}"
)
else:
con.cmd_raises(f"ip -6 addr add {ifaddr} dev {ifname}")
if sw_is_nat:
# In case there was some preconfig e.g., cloud-init
con.cmd_raises("ip -6 route flush exact default")
Expand Down Expand Up @@ -3204,8 +3271,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:
Expand Down Expand Up @@ -3236,6 +3304,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 node1.add_host_intf(c1["hostintf"], c1["name"], mtu=c1.get("mtu"))
do_add_dummy = False
elif "physical" in c1:
await node1.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
Expand Down
28 changes: 28 additions & 0 deletions tests/config/p2p-addr/munet.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
version: 1
topology:
ipv6-enable: true
nodes:
- name: r1
connections:
- to: r2
name: eth0
mtu: 4500
ip: 172.16.0.1/32
ipv6: 2001:db8::1/128
cmd: |
ip addr show
ip -6 addr show
which ping
tail -f /dev/null
- name: r2
connections:
- to: r1
name: eth0
mtu: 4500
ip: 172.16.1.2/24
ipv6: 2001:db8::1:1/112
cmd: |
ip addr show
ip -6 addr show
which ping
tail -f /dev/null
34 changes: 34 additions & 0 deletions tests/config/p2p-addr/test_p2p_peer_addr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# June 28 2023, Eric Kinzie <ekinzie@labn.net>
#
# Copyright 2023, LabN Consulting, L.L.C.
#
"Test p2p peer addresses"
import logging
import pytest

# All tests are coroutines
pytestmark = pytest.mark.asyncio


async def test_peer_address(unet):
rc, o, e = await unet.hosts["r1"].async_cmd_status(f"ip addr show dev eth0")
assert rc == 0
assert o.find("mtu 4500") > -1
assert o.find("inet 172.16.0.1 peer 172.16.1.0/24") > -1
assert o.find("inet6 2001:db8::1 peer 2001:db8::1:0/112") > -1

rc, o, e = await unet.hosts["r2"].async_cmd_status(f"ip addr show dev eth0")
assert rc == 0
assert o.find("mtu 4500") > -1
assert o.find("inet 172.16.1.2 peer 172.16.0.1/32") > -1
assert o.find("inet6 2001:db8::1:1 peer 2001:db8::1/128") > -1


async def test_peer_ping(unet):
r1eth0 = unet.hosts["r1"].get_intf_addr("eth0").ip
logging.debug("r1eth0 is %s", r1eth0)
o = await unet.hosts["r2"].async_cmd_raises(f"ping -w1 -c1 172.16.0.1")
logging.debug("ping r2 output: %s", o)
31 changes: 31 additions & 0 deletions tests/config/unconnected/munet.yaml
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions tests/config/unconnected/test_unconnected.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# June 28 2023, Eric Kinzie <ekinzie@labn.net>
#
# Copyright 2023, LabN Consulting, L.L.C.
#
"Testing of unconnected interface."
import logging

import pytest

# All tests are coroutines
pytestmark = pytest.mark.asyncio


async def test_unconnected_presence(unet):
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):
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)
Loading