Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

peer addresses and unconnected interfaces #23

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
28 changes: 28 additions & 0 deletions munet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
71 changes: 68 additions & 3 deletions munet/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -939,20 +964,40 @@ 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.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} 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)
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} 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 @@ -2736,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:
Expand All @@ -2758,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
Expand Down
28 changes: 28 additions & 0 deletions tests/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
40 changes: 40 additions & 0 deletions tests/p2p-addr/test_p2p_peer_addr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# June 28 2023, Eric Kinzie <[email protected]>
#
# 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_peer_address(unet_perfunc):
unet = unet_perfunc
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.2/24") > -1
assert o.find("inet6 2001:db8::1 peer 2001:db8::1:1/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_perfunc):
unet = unet_perfunc
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/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
37 changes: 37 additions & 0 deletions tests/unconnected/test_unconnected.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# June 28 2023, Eric Kinzie <[email protected]>
#
# 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)