Skip to content

Commit 8bbbd4f

Browse files
authored
Merge pull request #23 from eklabn/ekinzie/ip-p2p
peer addresses and unconnected interfaces
2 parents 6976db9 + 58e72ab commit 8bbbd4f

File tree

6 files changed

+255
-8
lines changed

6 files changed

+255
-8
lines changed

munet/base.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,7 @@ def __init__(self, *args, **kwargs):
16111611
# logging.warning("InterfaceMixin: args: %s kwargs: %s", args, kwargs)
16121612

16131613
self._intf_addrs = defaultdict(lambda: [None, None])
1614+
self._peer_intf_addrs = defaultdict(lambda: [None, None])
16141615
self.net_intfs = {}
16151616
self.next_intf_index = 0
16161617
self.basename = "eth"
@@ -1630,9 +1631,16 @@ def get_intf_addr(self, ifname, ipv6=False):
16301631
return None
16311632
return self._intf_addrs[ifname][bool(ipv6)]
16321633

1633-
def set_intf_addr(self, ifname, ifaddr):
1634+
def get_peer_intf_addr(self, ifname, ipv6=False):
1635+
if ifname not in self._peer_intf_addrs:
1636+
return None
1637+
return self._peer_intf_addrs[ifname][bool(ipv6)]
1638+
1639+
def set_intf_addr(self, ifname, ifaddr, peer_ifaddr=None):
16341640
ifaddr = ipaddress.ip_interface(ifaddr)
16351641
self._intf_addrs[ifname][ifaddr.version == 6] = ifaddr
1642+
if peer_ifaddr is not None:
1643+
self._peer_intf_addrs[ifname][peer_ifaddr.version == 6] = peer_ifaddr
16361644

16371645
def net_addr(self, netname, ipv6=False):
16381646
if netname not in self.net_intfs:
@@ -2883,6 +2891,33 @@ def add_host(self, name, cls=LinuxNamespace, **kwargs):
28832891

28842892
return self.hosts[name]
28852893

2894+
def add_dummy(self, node1, if1, mtu=None, **intf_constraints):
2895+
"""Add a dummy for an interface with no link."""
2896+
try:
2897+
name1 = node1.name
2898+
except AttributeError:
2899+
if node1 in self.switches:
2900+
node1 = self.switches[node1]
2901+
else:
2902+
node1 = self.hosts[node1]
2903+
name1 = node1.name
2904+
2905+
lname = "{}:{}".format(name1, if1)
2906+
self.logger.debug("%s: add_dummy %s", self, lname)
2907+
lhost = self.hosts[name1]
2908+
2909+
nsif1 = lhost.get_ns_ifname(if1)
2910+
lhost.cmd_raises_nsonly(f"ip link add name {nsif1} type dummy")
2911+
2912+
if mtu:
2913+
lhost.cmd_raises_nsonly(f"ip link set {nsif1} mtu {mtu}")
2914+
lhost.cmd_raises_nsonly(f"ip link set {nsif1} up")
2915+
lhost.register_interface(if1)
2916+
2917+
# Setup interface constraints if provided
2918+
if intf_constraints:
2919+
node1.set_intf_constraints(if1, **intf_constraints)
2920+
28862921
def add_link(self, node1, node2, if1, if2, mtu=None, **intf_constraints):
28872922
"""Add a link between switch and node or 2 nodes.
28882923

munet/native.py

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,31 @@ def get_ifname(self, netname):
980980
return c["name"]
981981
return None
982982

983+
def set_dummy_addr(self, cconf):
984+
if ip := cconf.get("ip"):
985+
ipaddr = ipaddress.ip_interface(ip)
986+
assert ipaddr.version == 4
987+
else:
988+
ipaddr = None
989+
990+
if ip := cconf.get("ipv6"):
991+
ip6addr = ipaddress.ip_interface(ip)
992+
assert ip6addr.version == 6
993+
else:
994+
ip6addr = None
995+
996+
if "physical" in cconf or self.is_vm:
997+
return
998+
999+
ifname = cconf["name"]
1000+
for ip in (ipaddr, ip6addr):
1001+
if ip is None:
1002+
continue
1003+
self.set_intf_addr(ifname, ip)
1004+
ipcmd = "ip " if ip.version == 4 else "ip -6 "
1005+
self.logger.debug("%s: adding %s to unconnected intf %s", self, ip, ifname)
1006+
self.intf_ip_cmd(ifname, ipcmd + f"addr add {ip} dev {ifname}")
1007+
9831008
def set_lan_addr(self, switch, cconf):
9841009
if ip := cconf.get("ip"):
9851010
ipaddr = ipaddress.ip_interface(ip)
@@ -1055,20 +1080,42 @@ def _set_p2p_addr(self, other, cconf, occonf, ipv6=False):
10551080
return
10561081

10571082
if ipaddr:
1083+
# Check if the two sides of this link are assigned
1084+
# different subnets. If so, set the peer address.
1085+
set_peer = False
1086+
if oipaddr and ipaddr.network != oipaddr.network:
1087+
set_peer = True
10581088
ifname = cconf["name"]
1059-
self.set_intf_addr(ifname, ipaddr)
1089+
self.set_intf_addr(ifname, ipaddr, oipaddr)
10601090
self.logger.debug("%s: adding %s to p2p intf %s", self, ipaddr, ifname)
10611091
if "physical" not in cconf and not self.is_vm:
1062-
self.intf_ip_cmd(ifname, f"ip addr add {ipaddr} dev {ifname}")
1092+
if set_peer:
1093+
self.logger.debug("%s: setting peer address %s", self, oipaddr)
1094+
self.intf_ip_cmd(
1095+
ifname,
1096+
f"ip addr add {ipaddr.ip} peer {oipaddr.network} dev {ifname}",
1097+
)
1098+
else:
1099+
self.intf_ip_cmd(ifname, f"ip addr add {ipaddr} dev {ifname}")
10631100

10641101
if oipaddr:
1102+
set_peer = False
1103+
if ipaddr and ipaddr.network != oipaddr.network:
1104+
set_peer = True
10651105
oifname = occonf["name"]
1066-
other.set_intf_addr(oifname, oipaddr)
1106+
other.set_intf_addr(oifname, oipaddr, ipaddr)
10671107
self.logger.debug(
10681108
"%s: adding %s to other p2p intf %s", other, oipaddr, oifname
10691109
)
10701110
if "physical" not in occonf and not other.is_vm:
1071-
other.intf_ip_cmd(oifname, f"ip addr add {oipaddr} dev {oifname}")
1111+
if set_peer:
1112+
other.logger.debug("%s: setting peer address %s", other, ipaddr)
1113+
other.intf_ip_cmd(
1114+
oifname,
1115+
f"ip addr add {oipaddr.ip} peer {ipaddr.network} dev {oifname}",
1116+
)
1117+
else:
1118+
other.intf_ip_cmd(oifname, f"ip addr add {oipaddr} dev {oifname}")
10721119

10731120
def set_p2p_addr(self, other, cconf, occonf):
10741121
self._set_p2p_addr(other, cconf, occonf, ipv6=False)
@@ -2225,13 +2272,33 @@ async def renumber_interfaces(self):
22252272
con.cmd_raises(f"ip -4 addr flush dev {ifname}")
22262273
sw_is_nat = switch and hasattr(switch, "is_nat") and switch.is_nat
22272274
if ifaddr := self.get_intf_addr(ifname, ipv6=False):
2228-
con.cmd_raises(f"ip addr add {ifaddr} dev {ifname}")
2275+
oifaddr = self.get_peer_intf_addr(ifname, ipv6=False)
2276+
if (
2277+
not switch
2278+
and oifaddr is not None
2279+
and ifaddr.network != oifaddr.network
2280+
):
2281+
con.cmd_raises(
2282+
f"ip addr add {ifaddr.ip} peer {oifaddr.network} dev {ifname}"
2283+
)
2284+
else:
2285+
con.cmd_raises(f"ip addr add {ifaddr} dev {ifname}")
22292286
if sw_is_nat:
22302287
# In case there was some preconfig e.g., cloud-init
22312288
con.cmd_raises("ip route flush exact default")
22322289
con.cmd_raises(f"ip route add default via {switch.ip_address}")
22332290
if ifaddr := self.get_intf_addr(ifname, ipv6=True):
2234-
con.cmd_raises(f"ip -6 addr add {ifaddr} dev {ifname}")
2291+
oifaddr = self.get_peer_intf_addr(ifname, ipv6=True)
2292+
if (
2293+
not switch
2294+
and oifaddr is not None
2295+
and ifaddr.network != oifaddr.network
2296+
):
2297+
con.cmd_raises(
2298+
f"ip addr add {ifaddr.ip} peer {oifaddr.network} dev {ifname}"
2299+
)
2300+
else:
2301+
con.cmd_raises(f"ip -6 addr add {ifaddr} dev {ifname}")
22352302
if sw_is_nat:
22362303
# In case there was some preconfig e.g., cloud-init
22372304
con.cmd_raises("ip -6 route flush exact default")
@@ -3204,8 +3271,9 @@ async def _async_build(self, logger=None):
32043271
if "connections" not in nconf:
32053272
continue
32063273
for cconf in nconf["connections"]:
3207-
# Eventually can add support for unconnected intf here.
32083274
if "to" not in cconf:
3275+
# unconnected intf
3276+
await self.add_dummy_link(node, cconf)
32093277
continue
32103278
to = cconf["to"]
32113279
if to in self.switches:
@@ -3236,6 +3304,25 @@ def autonumber(self):
32363304
def autonumber(self, value):
32373305
self.topoconf["networks-autonumber"] = bool(value)
32383306

3307+
async def add_dummy_link(self, node1, c1=None):
3308+
c1 = {} if c1 is None else c1
3309+
3310+
if "name" not in c1:
3311+
c1["name"] = node1.get_next_intf_name()
3312+
if1 = c1["name"]
3313+
3314+
do_add_dummy = True
3315+
if "hostintf" in c1:
3316+
await node1.add_host_intf(c1["hostintf"], c1["name"], mtu=c1.get("mtu"))
3317+
do_add_dummy = False
3318+
elif "physical" in c1:
3319+
await node1.add_phy_intf(c1["physical"], c1["name"])
3320+
do_add_dummy = False
3321+
3322+
if do_add_dummy:
3323+
super().add_dummy(node1, if1, **c1)
3324+
node1.set_dummy_addr(c1)
3325+
32393326
async def add_native_link(self, node1, node2, c1=None, c2=None):
32403327
"""Add a link between switch and node or 2 nodes."""
32413328
isp2p = False

tests/config/p2p-addr/munet.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
version: 1
2+
topology:
3+
ipv6-enable: true
4+
nodes:
5+
- name: r1
6+
connections:
7+
- to: r2
8+
name: eth0
9+
mtu: 4500
10+
ip: 172.16.0.1/32
11+
ipv6: 2001:db8::1/128
12+
cmd: |
13+
ip addr show
14+
ip -6 addr show
15+
which ping
16+
tail -f /dev/null
17+
- name: r2
18+
connections:
19+
- to: r1
20+
name: eth0
21+
mtu: 4500
22+
ip: 172.16.1.2/24
23+
ipv6: 2001:db8::1:1/112
24+
cmd: |
25+
ip addr show
26+
ip -6 addr show
27+
which ping
28+
tail -f /dev/null
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
#
4+
# June 28 2023, Eric Kinzie <ekinzie@labn.net>
5+
#
6+
# Copyright 2023, LabN Consulting, L.L.C.
7+
#
8+
"Test p2p peer addresses"
9+
import logging
10+
import pytest
11+
12+
# All tests are coroutines
13+
pytestmark = pytest.mark.asyncio
14+
15+
16+
async def test_peer_address(unet):
17+
rc, o, e = await unet.hosts["r1"].async_cmd_status(f"ip addr show dev eth0")
18+
assert rc == 0
19+
assert o.find("mtu 4500") > -1
20+
assert o.find("inet 172.16.0.1 peer 172.16.1.0/24") > -1
21+
assert o.find("inet6 2001:db8::1 peer 2001:db8::1:0/112") > -1
22+
23+
rc, o, e = await unet.hosts["r2"].async_cmd_status(f"ip addr show dev eth0")
24+
assert rc == 0
25+
assert o.find("mtu 4500") > -1
26+
assert o.find("inet 172.16.1.2 peer 172.16.0.1/32") > -1
27+
assert o.find("inet6 2001:db8::1:1 peer 2001:db8::1/128") > -1
28+
29+
30+
async def test_peer_ping(unet):
31+
r1eth0 = unet.hosts["r1"].get_intf_addr("eth0").ip
32+
logging.debug("r1eth0 is %s", r1eth0)
33+
o = await unet.hosts["r2"].async_cmd_raises(f"ping -w1 -c1 172.16.0.1")
34+
logging.debug("ping r2 output: %s", o)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
version: 1
2+
topology:
3+
networks-autonumber: true
4+
ipv6-enable: true
5+
networks:
6+
- name: net0
7+
mtu: 5000
8+
nodes:
9+
- name: r1
10+
connections:
11+
- to: net0
12+
name: eth0
13+
mtu: 4500
14+
- name: unconnected
15+
ip: 172.16.0.1/24
16+
mtu: 9000
17+
cmd: |
18+
ip addr show
19+
ip -6 addr show
20+
which ping
21+
tail -f /dev/null
22+
- name: r2
23+
connections:
24+
- to: "net0"
25+
name: eth0
26+
mtu: 4500
27+
cmd: |
28+
ip addr show
29+
ip -6 addr show
30+
which ping
31+
tail -f /dev/null
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
#
4+
# June 28 2023, Eric Kinzie <ekinzie@labn.net>
5+
#
6+
# Copyright 2023, LabN Consulting, L.L.C.
7+
#
8+
"Testing of unconnected interface."
9+
import logging
10+
11+
import pytest
12+
13+
# All tests are coroutines
14+
pytestmark = pytest.mark.asyncio
15+
16+
17+
async def test_unconnected_presence(unet):
18+
rc, o, e = await unet.hosts["r1"].async_cmd_status(f"ip addr show dev unconnected")
19+
assert rc == 0
20+
assert o.find("mtu 9000") > -1
21+
assert o.find("inet 172.16.0.1/24") > -1
22+
23+
24+
async def test_basic_ping(unet):
25+
r1eth0 = unet.hosts["r1"].get_intf_addr("eth0").ip
26+
logging.debug("r1eth0 is %s", r1eth0)
27+
rc, o, e = await unet.hosts["r2"].async_cmd_status(
28+
f"ip ro add 172.16.0.0/24 via {r1eth0}"
29+
)
30+
assert rc == 0
31+
o = await unet.hosts["r2"].async_cmd_raises(f"ping -w1 -c1 172.16.0.1")
32+
logging.debug("ping r2 output: %s", o)

0 commit comments

Comments
 (0)