Skip to content

Commit

Permalink
DHCP and static MAC support for management interface (#310)
Browse files Browse the repository at this point in the history
* common/vrnetlab.py: added DHCP support for management interface

If CLAB_MGMT_DHCP environment variable is set, we assume that a DHCP client
inside of the VM will take care about setting the management IP and gateway.

From the implementation point of view the IPv4 and IPv6 addresses and gateways
are simply set to 'dhcp', and the platform specific `launch.py` script needs
to check it to decide if the interface needs to be configured manually or to
to enable dhcp (if not enabled by default).

The initial implementation has been tested with Broadcom Enterprise SONiC 4.4.0
using the "dell_sonic" kind (which actually is a rebranded Broadcom SONiC).
The primary focus is the ability to test the Zero Touch Provisioning feature of
SONiC but could also be used for testing PXE boot scenarios of other systems
using containerlab.

Note:
This feature is only working when CLAB_MGMT_PASSTHROUGH is true.

The feature needs to be defined explicitely within the node configuration
like this:
--------------------------------
env:
  CLAB_MGMT_PASSTHROUGH: "true"
  CLAB_MGMT_DHCP: "true"
--------------------------------

* dell_sonic: changed to check if dhcp is enabled for the management interface

Manually set IPv4/IPv6 addresses and gateways only if dhcp is not enabled.

* common/vrnetlab.py: added get_mgmt_mac method to VM class

get_mgmt_mac() checks for the environment variable 'CLAB_MGMT_MAC' which
can be used to provide a static MAC address for the management interface.
If there is no static MAC address set the former behavior is preserved
by calling gen_mac(0) generating a MAC address.

* vrnetlab: Refactor mgmt MAC generation, handle exceptions for CSR1000v/ROS/SROS

* compresss env var based var assignment

---------

Co-authored-by: vista <vista@birb.network>
Co-authored-by: Roman Dodin <dodin.roman@gmail.com>
  • Loading branch information
3 people authored Jan 25, 2025
1 parent 46f5e20 commit ad44c11
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 36 deletions.
72 changes: 49 additions & 23 deletions common/vrnetlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def __init__(
cpu="host",
smp="1",
mgmt_passthrough=False,
mgmt_dhcp=False,
min_dp_nics=0,
):
self.logger = logging.getLogger()
Expand Down Expand Up @@ -115,15 +116,31 @@ def __init__(
# which **does not** match the eth0 interface of a container.
# In pass-through mode the VM container uses the same IP as the container's eth0 interface and transparently forwards traffic between the two interfaces.
# See https://github.com/hellt/vrnetlab/issues/286
self.mgmt_passthrough = mgmt_passthrough
mgmt_passthrough_override = os.environ.get("CLAB_MGMT_PASSTHROUGH", "")
if mgmt_passthrough_override:
self.mgmt_passthrough = mgmt_passthrough_override.lower() == "true"
self.mgmt_passthrough = (
os.environ.get("CLAB_MGMT_PASSTHROUGH", "").lower() == "true"
if os.environ.get("CLAB_MGMT_PASSTHROUGH")
else mgmt_passthrough
)

# Check if CLAB_MGMT_DHCP environment variable is set
self.mgmt_dhcp = (
os.environ.get("CLAB_MGMT_DHCP", "").lower() == "true"
if os.environ.get("CLAB_MGMT_DHCP")
else mgmt_dhcp
)

# Populate management IP and gateway
# If CLAB_MGMT_DHCP environment variable is set, we assume that a DHCP client
# inside of the VM will take care about setting the management IP and gateway.
if self.mgmt_passthrough:
self.mgmt_address_ipv4, self.mgmt_address_ipv6 = self.get_mgmt_address()
self.mgmt_gw_ipv4, self.mgmt_gw_ipv6 = self.get_mgmt_gw()
if self.mgmt_dhcp:
self.mgmt_address_ipv4 = "dhcp"
self.mgmt_address_ipv6 = "dhcp"
self.mgmt_gw_ipv4 = "dhcp"
self.mgmt_gw_ipv6 = "dhcp"
else:
self.mgmt_address_ipv4, self.mgmt_address_ipv6 = self.get_mgmt_address()
self.mgmt_gw_ipv4, self.mgmt_gw_ipv6 = self.get_mgmt_gw()
else:
self.mgmt_address_ipv4 = "10.0.0.15/24"
self.mgmt_address_ipv6 = "2001:db8::2/64"
Expand Down Expand Up @@ -362,6 +379,12 @@ def create_tc_tap_mgmt_ifup(self):
f.write(ifup_script)
os.chmod("/etc/tc-tap-mgmt-ifup", 0o777)

def get_mgmt_mac(self, last_octet=0) -> str:
"""Get the MAC address for the management interface from the envvar
`CLAB_MGMT_MAC` or generate a random one using `gen_mac(last_octet)`.
"""
return os.environ.get("CLAB_MGMT_MAC") or gen_mac(last_octet)

def gen_mgmt(self):
"""Generate qemu args for the mgmt interface(s)
Expand Down Expand Up @@ -389,11 +412,7 @@ def gen_mgmt(self):

res = []
res.append("-device")
self.mgmt_mac = (
"c0:00:01:00:ca:fe"
if getattr(self, "_static_mgmt_mac", False)
else gen_mac(0)
)
self.mgmt_mac = self.get_mgmt_mac()

res.append(self.nic_type + f",netdev=p00,mac={self.mgmt_mac}")
res.append("-netdev")
Expand Down Expand Up @@ -525,7 +544,7 @@ def gen_dummy_nics(self):

for i in range(0, nics):
# dummy interface naming
interface_name = f"dummy{str(i+self.num_provisioned_nics)}"
interface_name = f"dummy{str(i + self.num_provisioned_nics)}"

# PCI bus counter is to ensure pci bus index starts from 1
# and continuing in sequence regardles the eth index
Expand Down Expand Up @@ -825,24 +844,31 @@ def start(self):
else:
self.update_health(1, "starting")

#file-based signalling backdoor to trigger a system reset (via qemu-monitor) on all or specific VMs.
#if file is empty: reset whole VR (all VMs)
#if file is non-empty: reset only specified VMs (comma separated list)
if os.path.exists('/reset'):
with open('/reset','rt') as f:
fcontent=f.read().strip()
vm_num_list=fcontent.split(',')
# file-based signalling backdoor to trigger a system reset (via qemu-monitor) on all or specific VMs.
# if file is empty: reset whole VR (all VMs)
# if file is non-empty: reset only specified VMs (comma separated list)
if os.path.exists("/reset"):
with open("/reset", "rt") as f:
fcontent = f.read().strip()
vm_num_list = fcontent.split(",")
for vm in self.vms:
if (str(vm.num) in vm_num_list) or not fcontent:
try:
vm.qm.write("system_reset\r".encode())
self.logger.debug(f"Sent qemu-monitor system_reset to VM num {vm.num} ")
self.logger.debug(
f"Sent qemu-monitor system_reset to VM num {vm.num} "
)
except Exception as e:
self.logger.debug(f"Failed to send qemu-monitor system_reset to VM num {vm.num} ({e})")
self.logger.debug(
f"Failed to send qemu-monitor system_reset to VM num {vm.num} ({e})"
)
try:
os.remove('/reset')
os.remove("/reset")
except Exception as e:
self.logger.debug(f"Failed to cleanup /reset file({e}). qemu-monitor system_reset will likely be triggered again on VMs")
self.logger.debug(
f"Failed to cleanup /reset file({e}). qemu-monitor system_reset will likely be triggered again on VMs"
)


class QemuBroken(Exception):
"""Our Qemu instance is somehow broken"""
Expand Down
4 changes: 4 additions & 0 deletions csr/docker/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ def startup_config(self):
self.wait_write("end")
self.wait_write("copy running-config startup-config")

# Override management MAC with specific static MAC address
def get_mgmt_mac(self):
return "c0:00:01:00:ca:fe"


class CSR(vrnetlab.VR):
def __init__(self, hostname, username, password, nics, conn_mode):
Expand Down
19 changes: 11 additions & 8 deletions dell_sonic/docker/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,17 @@ def bootstrap_config(self):
"""Do the actual bootstrap config"""
self.logger.info("applying bootstrap configuration")
self.wait_write("sudo -i", "$")
# set ipv4/6 address to the management interface
self.wait_write(
f"sudo /usr/sbin/ip address add {self.mgmt_address_ipv4} dev eth0", "#"
)
# note, v6 address is not being applied for whatever reason
self.wait_write(
f"sudo /usr/sbin/ip -6 address add {self.mgmt_address_ipv6} dev eth0", "#"
)

# set ipv4/6 address of the management interface if it is not managed by dhcp
if not self.mgmt_address_ipv4 == "dhcp":
self.wait_write(
f"sudo /usr/sbin/ip address add {self.mgmt_address_ipv4} dev eth0", "#"
)
if not self.mgmt_address_ipv4 == "dhcp":
# note, v6 address is not being applied for whatever reason
self.wait_write(
f"sudo /usr/sbin/ip -6 address add {self.mgmt_address_ipv6} dev eth0", "#"
)
self.wait_write("passwd -q %s" % (self.username))
self.wait_write(self.password, "New password:")
self.wait_write(self.password, "password:")
Expand Down
2 changes: 1 addition & 1 deletion routeros/docker/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def gen_mgmt(self):
res.append("-device")

res.append(
self.nic_type + ",netdev=br-mgmt,mac=%(mac)s" % {"mac": vrnetlab.gen_mac(0)}
self.nic_type + ",netdev=br-mgmt,mac=%(mac)s" % {"mac": self.get_mgmt_mac()}
)
res.append("-netdev")
res.append("bridge,br=br-mgmt,id=br-mgmt" % {"i": 0})
Expand Down
6 changes: 2 additions & 4 deletions sros/docker/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -1329,8 +1329,7 @@ def gen_mgmt(self):

res = []

mac = vrnetlab.gen_mac(0)
self.mgmt_mac = mac
self.mgmt_mac = self.get_mgmt_mac()

if self.mgmt_passthrough:
res.append("-device")
Expand Down Expand Up @@ -1435,8 +1434,7 @@ def gen_mgmt(self):
"""
res = []

mac = vrnetlab.gen_mac(0)
self.mgmt_mac = mac
self.mgmt_mac = self.get_mgmt_mac()

if self.mgmt_passthrough:
res.append("-device")
Expand Down

0 comments on commit ad44c11

Please sign in to comment.