From 8b91894fa4a8647627242688f06978b143fe0716 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 19 Mar 2018 16:26:12 +0700 Subject: [PATCH] Improve suspend a link for Qemu and VirtualBox VMs. A suspended link will be unplugged allowing the VMs to be notified of the change. --- gns3server/compute/base_manager.py | 5 ++- gns3server/compute/dynamips/nios/nio.py | 43 ++++++++++++++++++ gns3server/compute/dynamips/nios/nio_udp.py | 11 +---- gns3server/compute/dynamips/nodes/router.py | 2 - gns3server/compute/nios/nio.py | 45 +++++++++++++++++++ gns3server/compute/nios/nio_udp.py | 15 +------ gns3server/compute/qemu/qemu_vm.py | 10 +++++ .../compute/virtualbox/virtualbox_vm.py | 16 ++++--- gns3server/controller/link.py | 13 +++--- gns3server/controller/udp_link.py | 38 +++++++++++----- .../handlers/api/compute/qemu_handler.py | 4 +- .../api/compute/virtualbox_handler.py | 2 + gns3server/schemas/nio.py | 4 ++ 13 files changed, 156 insertions(+), 52 deletions(-) diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index bcb799e8..969104f8 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -418,8 +418,9 @@ class BaseManager: sock.connect(sa) except OSError as e: raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e)) - filters = nio_settings.get("filters", {}) - nio = NIOUDP(lport, rhost, rport, filters) + nio = NIOUDP(lport, rhost, rport) + nio.filters = nio_settings.get("filters", {}) + nio.suspend = nio_settings.get("suspend", False) elif nio_settings["type"] == "nio_tap": tap_device = nio_settings["tap_device"] # if not is_interface_up(tap_device): diff --git a/gns3server/compute/dynamips/nios/nio.py b/gns3server/compute/dynamips/nios/nio.py index 2f978f19..d6a4f9b0 100644 --- a/gns3server/compute/dynamips/nios/nio.py +++ b/gns3server/compute/dynamips/nios/nio.py @@ -39,6 +39,8 @@ class NIO: self._hypervisor = hypervisor self._name = name + self._filters = {} + self._suspended = False self._bandwidth = None # no bandwidth constraint by default self._input_filter = None # no input filter applied by default self._output_filter = None # no output filter applied by default @@ -236,6 +238,47 @@ class NIO: yield from self._hypervisor.send("nio set_bandwidth {name} {bandwidth}".format(name=self._name, bandwidth=bandwidth)) self._bandwidth = bandwidth + @property + def suspend(self): + """ + Returns if this link is suspended or not. + + :returns: boolean + """ + + return self._suspended + + @suspend.setter + def suspend(self, suspended): + """ + Suspend this link. + + :param suspended: boolean + """ + + self._suspended = suspended + + @property + def filters(self): + """ + Returns the list of packet filters for this NIO. + + :returns: packet filters (dictionary) + """ + + return self._filters + + @filters.setter + def filters(self, new_filters): + """ + Set a list of packet filters for this NIO. + + :param new_filters: packet filters (dictionary) + """ + + assert isinstance(new_filters, dict) + self._filters = new_filters + def __str__(self): """ NIO string representation. diff --git a/gns3server/compute/dynamips/nios/nio_udp.py b/gns3server/compute/dynamips/nios/nio_udp.py index 60ddafa0..3cf37818 100644 --- a/gns3server/compute/dynamips/nios/nio_udp.py +++ b/gns3server/compute/dynamips/nios/nio_udp.py @@ -41,27 +41,18 @@ class NIOUDP(NIO): :param rport: remote port number """ - def __init__(self, node, lport, rhost, rport, filters): + def __init__(self, node, lport, rhost, rport): # create an unique name name = 'udp-{}'.format(uuid.uuid4()) self._lport = lport self._rhost = rhost self._rport = rport - self._filters = filters self._local_tunnel_lport = None self._local_tunnel_rport = None self._node = node super().__init__(name, node.hypervisor) - @property - def filters(self): - return self._filters - - @filters.setter - def filters(self, val): - self._filters = val - @asyncio.coroutine def create(self): if not self._hypervisor: diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index ab9dadeb..0cf25aa1 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -36,8 +36,6 @@ log = logging.getLogger(__name__) from ...base_node import BaseNode from ..dynamips_error import DynamipsError -from ..nios.nio_udp import NIOUDP - from gns3server.utils.file_watcher import FileWatcher from gns3server.utils.asyncio import wait_run_in_executor, monitor_process, asyncio_ensure_future diff --git a/gns3server/compute/nios/nio.py b/gns3server/compute/nios/nio.py index b1ab24ae..4df1576e 100644 --- a/gns3server/compute/nios/nio.py +++ b/gns3server/compute/nios/nio.py @@ -29,6 +29,8 @@ class NIO(object): def __init__(self): self._capturing = False + self._suspended = False + self._filters = {} self._pcap_output_file = "" self._pcap_data_link_type = "" @@ -61,6 +63,7 @@ class NIO(object): def pcap_output_file(self): """ Returns the path to the PCAP output file. + :returns: path to the PCAP output file """ @@ -70,7 +73,49 @@ class NIO(object): def pcap_data_link_type(self): """ Returns the PCAP data link type + :returns: PCAP data link type (DLT_* value) """ return self._pcap_data_link_type + + @property + def suspend(self): + """ + Returns if this link is suspended or not. + + :returns: boolean + """ + + return self._suspended + + @suspend.setter + def suspend(self, suspended): + """ + Suspend this link. + + :param suspended: boolean + """ + + self._suspended = suspended + + @property + def filters(self): + """ + Returns the list of packet filters for this NIO. + + :returns: packet filters (dictionary) + """ + + return self._filters + + @filters.setter + def filters(self, new_filters): + """ + Set a list of packet filters for this NIO. + + :param new_filters: packet filters (dictionary) + """ + + assert isinstance(new_filters, dict) + self._filters = new_filters diff --git a/gns3server/compute/nios/nio_udp.py b/gns3server/compute/nios/nio_udp.py index 36305312..a87875fe 100644 --- a/gns3server/compute/nios/nio_udp.py +++ b/gns3server/compute/nios/nio_udp.py @@ -32,25 +32,12 @@ class NIOUDP(NIO): :param rport: remote port number """ - def __init__(self, lport, rhost, rport, filters): + def __init__(self, lport, rhost, rport): super().__init__() self._lport = lport self._rhost = rhost self._rport = rport - assert isinstance(filters, dict) - self._filters = filters - - @property - def filters(self): - """ - Return the list of filter on this NIO - """ - return self._filters - - @filters.setter - def filters(self, val): - self._filters = val @property def lport(self): diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 811768e0..983171c5 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -957,6 +957,8 @@ class QemuVM(BaseNode): yield from self.add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number), self._local_udp_tunnels[adapter_number][1], nio) + if nio.suspend: + yield from self._control_vm("set_link gns3-{} off".format(adapter_number)) else: yield from self._control_vm("set_link gns3-{} off".format(adapter_number)) @@ -1191,6 +1193,10 @@ class QemuVM(BaseNode): yield from self.update_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number), self._local_udp_tunnels[adapter_number][1], nio) + if nio.suspend: + yield from self._control_vm("set_link gns3-{} off".format(adapter_number)) + else: + yield from self._control_vm("set_link gns3-{} on".format(adapter_number)) except IndexError: raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, adapter_number=adapter_number)) @@ -1595,6 +1601,8 @@ class QemuVM(BaseNode): nio.rport, "127.0.0.1", nio.lport)]) + elif isinstance(nio, NIOTAP): + network_options.extend(["-net", "tap,name=gns3-{},ifname={}".format(adapter_number, nio.tap_device)]) else: network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)]) @@ -1614,6 +1622,8 @@ class QemuVM(BaseNode): nio.rport, "127.0.0.1", nio.lport)]) + elif isinstance(nio, NIOTAP): + network_options.extend(["-netdev", "tap,id=gns3-{},ifname={},script=no,downscript=no".format(adapter_number, nio.tap_device)]) else: network_options.extend(["-device", device_string]) diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index b765872d..eff54fb4 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -873,7 +873,10 @@ class VirtualBoxVM(BaseNode): yield from self._modify_vm("--nicproperty{} sport={}".format(adapter_number + 1, nio.lport)) yield from self._modify_vm("--nicproperty{} dest={}".format(adapter_number + 1, nio.rhost)) yield from self._modify_vm("--nicproperty{} dport={}".format(adapter_number + 1, nio.rport)) - yield from self._modify_vm("--cableconnected{} on".format(adapter_number + 1)) + if nio.suspend: + yield from self._modify_vm("--cableconnected{} off".format(adapter_number + 1)) + else: + yield from self._modify_vm("--cableconnected{} on".format(adapter_number + 1)) if nio.capturing: yield from self._modify_vm("--nictrace{} on".format(adapter_number + 1)) @@ -1016,10 +1019,13 @@ class VirtualBoxVM(BaseNode): if self.is_running(): try: - yield from self.update_ubridge_udp_connection( - "VBOX-{}-{}".format(self._id, adapter_number), - self._local_udp_tunnels[adapter_number][1], - nio) + yield from self.update_ubridge_udp_connection("VBOX-{}-{}".format(self._id, adapter_number), + self._local_udp_tunnels[adapter_number][1], + nio) + if nio.suspend: + yield from self._control_vm("setlinkstate{} off".format(adapter_number + 1)) + else: + yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1)) except IndexError: raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format( name=self._name, diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index f92e6079..8578f3ae 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -124,7 +124,7 @@ class Link: self._streaming_pcap = None self._created = False self._link_type = "ethernet" - self._suspend = False + self._suspended = False self._filters = {} @property @@ -146,7 +146,8 @@ class Link: Return the active filters. Filters are overridden if the link is suspended. """ - if self._suspend: + if self._suspended: + # this is to allow all node types to support suspend link return {"frequency_drop": [-1]} return self._filters @@ -178,8 +179,8 @@ class Link: @asyncio.coroutine def update_suspend(self, value): - if value != self._suspend: - self._suspend = value + if value != self._suspended: + self._suspended = value yield from self.update() self._project.controller.notification.emit("link.updated", self.__json__()) self._project.dump() @@ -452,7 +453,7 @@ class Link: "nodes": res, "link_id": self._id, "filters": self._filters, - "suspend": self._suspend + "suspend": self._suspended } return { "nodes": res, @@ -463,5 +464,5 @@ class Link: "capture_file_path": self.capture_file_path, "link_type": self._link_type, "filters": self._filters, - "suspend": self._suspend + "suspend": self._suspended } diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index 479d66b5..a60be7f2 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -76,7 +76,8 @@ class UDPLink(Link): "rhost": node2_host, "rport": self._node2_port, "type": "nio_udp", - "filters": node1_filters + "filters": node1_filters, + "suspend": self._suspended }) yield from node1.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120) @@ -85,7 +86,8 @@ class UDPLink(Link): "rhost": node1_host, "rport": self._node1_port, "type": "nio_udp", - "filters": node2_filters + "filters": node2_filters, + "suspend": self._suspended }) try: yield from node2.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=120) @@ -97,22 +99,34 @@ class UDPLink(Link): @asyncio.coroutine def update(self): + """ + Update the link on the nodes + """ + if len(self._link_data) == 0: return node1 = self._nodes[0]["node"] node2 = self._nodes[1]["node"] + + node1_filters = {} + node2_filters = {} filter_node = self._get_filter_node() + if filter_node == node1: + node1_filters = self.get_active_filters() + elif filter_node == node2: + node2_filters = self.get_active_filters() - if node1 == filter_node: - adapter_number1 = self._nodes[0]["adapter_number"] - port_number1 = self._nodes[0]["port_number"] - self._link_data[0]["filters"] = self.get_active_filters() - yield from node1.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120) - elif node2 == filter_node: - adapter_number2 = self._nodes[1]["adapter_number"] - port_number2 = self._nodes[1]["port_number"] - self._link_data[1]["filters"] = self.get_active_filters() - yield from node2.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=221) + adapter_number1 = self._nodes[0]["adapter_number"] + port_number1 = self._nodes[0]["port_number"] + self._link_data[0]["filters"] = node1_filters + self._link_data[0]["suspend"] = self._suspended + yield from node1.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120) + + adapter_number2 = self._nodes[1]["adapter_number"] + port_number2 = self._nodes[1]["port_number"] + self._link_data[1]["filters"] = node2_filters + self._link_data[1]["suspend"] = self._suspended + yield from node2.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=221) @asyncio.coroutine def delete(self): diff --git a/gns3server/handlers/api/compute/qemu_handler.py b/gns3server/handlers/api/compute/qemu_handler.py index 129ac0d9..12db4596 100644 --- a/gns3server/handlers/api/compute/qemu_handler.py +++ b/gns3server/handlers/api/compute/qemu_handler.py @@ -284,7 +284,7 @@ class QEMUHandler: qemu_manager = Qemu.instance() vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) nio_type = request.json["type"] - if nio_type not in ("nio_udp", "nio_tap", "nio_nat"): + if nio_type not in ("nio_udp"): raise aiohttp.web.HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) nio = qemu_manager.create_nio(request.json) yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) @@ -314,6 +314,8 @@ class QEMUHandler: nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])] if "filters" in request.json and nio: nio.filters = request.json["filters"] + if "suspend" in request.json and nio: + nio.suspend = request.json["suspend"] yield from vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio) response.set_status(201) response.json(request.json) diff --git a/gns3server/handlers/api/compute/virtualbox_handler.py b/gns3server/handlers/api/compute/virtualbox_handler.py index 0d35ba6b..f704a6c1 100644 --- a/gns3server/handlers/api/compute/virtualbox_handler.py +++ b/gns3server/handlers/api/compute/virtualbox_handler.py @@ -313,6 +313,8 @@ class VirtualBoxHandler: nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])] if "filters" in request.json and nio: nio.filters = request.json["filters"] + if "suspend" in request.json and nio: + nio.suspend = request.json["suspend"] yield from vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio) response.set_status(201) response.json(request.json) diff --git a/gns3server/schemas/nio.py b/gns3server/schemas/nio.py index e45211a7..edee0eab 100644 --- a/gns3server/schemas/nio.py +++ b/gns3server/schemas/nio.py @@ -46,6 +46,10 @@ NIO_SCHEMA = { "minimum": 1, "maximum": 65535 }, + "suspend": { + "type": "boolean", + "description": "Suspend the link" + }, "filters": FILTER_OBJECT_SCHEMA }, "required": ["type", "lport", "rhost", "rport"],