From 702fea89fbdbdd607b71cc2d0f833256080e6248 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 12 Sep 2023 16:14:44 +0700 Subject: [PATCH] Automatically add vboxnet and DHCP server if not present for VirtualBox GNS3 VM. Ref #2266 --- .../controller/gns3vm/virtualbox_gns3_vm.py | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py index ac9045b9..029060fb 100644 --- a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py +++ b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py @@ -15,11 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import re import sys import aiohttp import logging import asyncio import socket +import ipaddress from .base_gns3_vm import BaseGNS3VM from .gns3_vm_error import GNS3VMError @@ -80,9 +82,6 @@ class VirtualBoxGNS3VM(BaseGNS3VM): except ValueError: continue self._system_properties[name.strip()] = value.strip() - if "API Version" in self._system_properties: - # API version is not consistent between VirtualBox versions, the key is named "API Version" in VirtualBox 7 - self._system_properties["API version"] = self._system_properties.pop("API Version") async def _check_requirements(self): """ @@ -92,16 +91,16 @@ class VirtualBoxGNS3VM(BaseGNS3VM): if not self._system_properties: await self._get_system_properties() if "API version" not in self._system_properties: - raise VirtualBoxError("Can't access to VirtualBox API version:\n{}".format(self._system_properties)) + raise GNS3VMError("Can't access to VirtualBox API version:\n{}".format(self._system_properties)) from cpuinfo import get_cpu_info cpu_info = await wait_run_in_executor(get_cpu_info) vendor_id = cpu_info.get('vendor_id_raw') if vendor_id == "GenuineIntel": if parse_version(self._system_properties["API version"]) < parse_version("6_1"): - raise VirtualBoxError("VirtualBox version 6.1 or above is required to run the GNS3 VM with nested virtualization enabled on Intel processors") + raise GNS3VMError("VirtualBox version 6.1 or above is required to run the GNS3 VM with nested virtualization enabled on Intel processors") elif vendor_id == "AuthenticAMD": if parse_version(self._system_properties["API version"]) < parse_version("6_0"): - raise VirtualBoxError("VirtualBox version 6.0 or above is required to run the GNS3 VM with nested virtualization enabled on AMD processors") + raise GNS3VMError("VirtualBox version 6.0 or above is required to run the GNS3 VM with nested virtualization enabled on AMD processors") else: log.warning("Could not determine CPU vendor: {}".format(vendor_id)) @@ -162,6 +161,44 @@ class VirtualBoxGNS3VM(BaseGNS3VM): return True return False + async def _add_dhcp_server(self, vboxnet): + """ + Add a DHCP server for vboxnet. + + :param vboxnet: vboxnet name + """ + + hostonlyifs = await self._execute("list", ["hostonlyifs"]) + pattern = r"IPAddress:\s+(\d+\.\d+\.\d+\.\d+)\nNetworkMask:\s+(\d+\.\d+\.\d+\.\d+)" + match = re.search(pattern, hostonlyifs) + + if match: + ip_address = match.group(1) + netmask = match.group(2) + else: + raise GNS3VMError("Could not find IP address and netmask for vboxnet {}".format(vboxnet)) + + try: + interface = ipaddress.IPv4Interface(f"{ip_address}/{netmask}") + subnet = ipaddress.IPv4Network(str(interface.network)) + dhcp_server_ip = str(interface.ip + 1) + netmask = str(subnet.netmask) + lower_ip = str(interface.ip + 2) + upper_ip = str(subnet.network_address + subnet.num_addresses - 2) + except ValueError: + raise GNS3VMError("Invalid IP address and netmask for vboxnet {}: {}/{}".format(vboxnet, ip_address, netmask)) + + dhcp_server_args = [ + "add", + "--network=HostInterfaceNetworking-{}".format(vboxnet), + "--server-ip={}".format(dhcp_server_ip), + "--netmask={}".format(netmask), + "--lower-ip={}".format(lower_ip), + "--upper-ip={}".format(upper_ip), + "--enable" + ] + await self._execute("dhcpserver", dhcp_server_args) + async def _check_vboxnet_exists(self, vboxnet, vboxnet_type): """ Check if the vboxnet interface exists @@ -264,12 +301,20 @@ class VirtualBoxGNS3VM(BaseGNS3VM): await self.set_hostonly_network(interface_number, first_available_vboxnet) vboxnet = first_available_vboxnet else: - raise GNS3VMError('VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format(vboxnet, - interface_number, - self._vmname)) + try: + await self._execute("hostonlyif", ["create"]) + except GNS3VMError: + raise GNS3VMError('VirtualBox host-only network "{}" does not exist and could not be automatically created, please make the sure the network adapter {} configuration is valid for "{}"'.format( + vboxnet, + interface_number, + self._vmname + )) if backend_type == "hostonlyadapter" and not (await self._check_dhcp_server(vboxnet)): - raise GNS3VMError('DHCP must be enabled on VirtualBox host-only network "{}"'.format(vboxnet)) + try: + await self._add_dhcp_server(vboxnet) + except GNS3VMError as e: + raise GNS3VMError("Could not add DHCP server for vboxnet {}: {}, please configure manually".format(vboxnet, e)) vm_state = await self._get_state() log.info('"{}" state is {}'.format(self._vmname, vm_state)) @@ -302,7 +347,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): except OSError as e: raise GNS3VMError("Error while getting random port: {}".format(e)) - if (await self._check_vbox_port_forwarding()): + if await self._check_vbox_port_forwarding(): # delete the GNS3VM NAT port forwarding rule if it exists log.info("Removing GNS3VM NAT port forwarding rule from interface {}".format(nat_interface_number)) await self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number), "delete", "GNS3VM"])