diff --git a/CHANGELOG b/CHANGELOG index abf6dca2..cea6d503 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,23 @@ # Change Log +## 1.3.4 02/06/15 + +* Drop useless dependencie dateutil +* Check if port or adapter is connected before starting/stopping a packet capture. Fixes #196. +* Prevent users to add links to running Qemu VMs and start a capture on running VirtualBox VMs. +* Fixes bug: couldn't set PCMCIA disk1 size for IOS routers. +* Fix crash if you pass an invalid hostname +* Catch VPCS kill errors +* Raise a VirtualBox error if adapter doesn't exists +* Ignore VirtualBox VM Name with a carriage return in name +* Cleanup the temporary project after modules have been notified of the path change +* Do not return error if we can't remove the old project directory +* Catch encoding errors in windows logger +* Use setter for the qemu_path (allow to pass only the binary name) +* Fixes TAP connection when using VPCS. +* Fix crash launching qemu on OSX from another location. +* Adds NAT NIO in device schema validation so they can return an error that it is not supported. + ## 1.3.3 14/05/15 * Check for empty iourc path. diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py index fd6621ef..e106b0c5 100644 --- a/gns3server/crash_report.py +++ b/gns3server/crash_report.py @@ -52,7 +52,7 @@ class CrashReport: Report crash to a third party service """ - DSN = "sync+https://9e6f04df72c74b6894a6dcd2928d069e:2035d1beb1654136b170f1e91f05ee51@app.getsentry.com/38482" + DSN = "sync+https://1d821222775c4cf3a66ee462e22780df:4f95f621d9b54d6a8afe0d92ed076969@app.getsentry.com/38482" if hasattr(sys, "frozen"): cacert = get_resource("cacert.pem") if cacert is not None and os.path.isfile(cacert): diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index fc74e09c..2f8f0d87 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -298,6 +298,9 @@ class EthernetSwitch(Device): nio = self._nios[port_number] + if not nio: + raise DynamipsError("Port {} is not connected".format(port_number)) + data_link_type = data_link_type.lower() if data_link_type.startswith("dlt_"): data_link_type = data_link_type[4:] @@ -324,6 +327,10 @@ class EthernetSwitch(Device): raise DynamipsError("Port {} is not allocated".format(port_number)) nio = self._nios[port_number] + + if not nio: + raise DynamipsError("Port {} is not connected".format(port_number)) + yield from nio.unbind_filter("both") log.info('Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name, id=self._id, diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index b4f15d72..bdd3e053 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -1318,6 +1318,10 @@ class Router(BaseVM): nio = adapter.get_nio(port_number) + if not nio: + raise DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number, + port_number=port_number)) + if nio.input_filter[0] is not None and nio.output_filter[0] is not None: raise DynamipsError("Port {port_number} has already a filter applied on {adapter}".format(adapter=adapter, port_number=port_number)) @@ -1350,6 +1354,11 @@ class Router(BaseVM): port_number=port_number)) nio = adapter.get_nio(port_number) + + if not nio: + raise DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number, + port_number=port_number)) + yield from nio.unbind_filter("both") log.info('Router "{name}" [{id}]: stopping packet capture on port {slot_number}/{port_number}'.format(name=self._name, diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 6f569c1c..af1b9c95 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -46,13 +46,18 @@ class Qemu(BaseManager): :returns: List of folders where Qemu binaries MAY reside. """ +<<<<<<< HEAD paths = [] +======= + qemus = [] + paths = set() +>>>>>>> master try: - paths.append(os.getcwd()) + paths.add(os.getcwd()) except FileNotFoundError: log.warning("The current working directory doesn't exist") if "PATH" in os.environ: - paths.extend(os.environ["PATH"].split(os.pathsep)) + paths.update(os.environ["PATH"].split(os.pathsep)) else: log.warning("The PATH environment variable doesn't exist") # look for Qemu binaries in the current working directory and $PATH @@ -63,20 +68,25 @@ class Qemu(BaseManager): exec_dir = os.path.dirname(os.path.abspath(sys.executable)) for f in os.listdir(exec_dir): if f.lower().startswith("qemu"): - paths.append(os.path.join(exec_dir, f)) + paths.add(os.path.join(exec_dir, f)) if "PROGRAMFILES(X86)" in os.environ and os.path.exists(os.environ["PROGRAMFILES(X86)"]): - paths.append(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu")) + paths.add(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu")) if "PROGRAMFILES" in os.environ and os.path.exists(os.environ["PROGRAMFILES"]): - paths.append(os.path.join(os.environ["PROGRAMFILES"], "qemu")) + paths.add(os.path.join(os.environ["PROGRAMFILES"], "qemu")) elif sys.platform.startswith("darwin"): # add specific locations on Mac OS X regardless of what's in $PATH +<<<<<<< HEAD paths.extend(["/usr/bin", "/usr/local/bin", "/opt/local/bin"]) +======= + paths.update(["/usr/local/bin", "/opt/local/bin"]) +>>>>>>> master if hasattr(sys, "frozen"): try: - paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/"))) + paths.add(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/"))) # If the user run the server by hand from outside except FileNotFoundError: +<<<<<<< HEAD paths.append(["/Applications/GNS3.app/Contents/Resources/qemu/bin"]) return paths @@ -90,6 +100,10 @@ class Qemu(BaseManager): qemus = [] for path in Qemu.paths_list(): +======= + paths.add("/Applications/GNS3.app/Contents/Resources/qemu/bin") + for path in paths: +>>>>>>> master try: for f in os.listdir(path): if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \ diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 766239be..8c03fa1f 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -168,7 +168,7 @@ class VirtualBox(BaseManager): vms = [] result = yield from self.execute("list", ["vms"]) for line in result: - if line[0] != '"' or line[-1:] != "}": + if len(line) == 0 or line[0] != '"' or line[-1:] != "}": continue # Broken output (perhaps a carriage return in VM name vmname, _ = line.rsplit(' ', 1) vmname = vmname.strip('"') diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index ab0911f7..c0f8ce3b 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -837,12 +837,16 @@ class VirtualBoxVM(BaseVM): vm_state = yield from self._get_vm_state() if vm_state == "running": - # dynamically configure an UDP tunnel on the VirtualBox adapter - yield from self._control_vm("nic{} generic UDPTunnel".format(adapter_number + 1)) - yield from self._control_vm("nicproperty{} sport={}".format(adapter_number + 1, nio.lport)) - yield from self._control_vm("nicproperty{} dest={}".format(adapter_number + 1, nio.rhost)) - yield from self._control_vm("nicproperty{} dport={}".format(adapter_number + 1, nio.rport)) - yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1)) + if isinstance(nio, NIOUDP): + # dynamically configure an UDP tunnel on the VirtualBox adapter + yield from self._control_vm("nic{} generic UDPTunnel".format(adapter_number + 1)) + yield from self._control_vm("nicproperty{} sport={}".format(adapter_number + 1, nio.lport)) + yield from self._control_vm("nicproperty{} dest={}".format(adapter_number + 1, nio.rhost)) + yield from self._control_vm("nicproperty{} dport={}".format(adapter_number + 1, nio.rport)) + yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1)) + elif isinstance(nio, NIONAT): + yield from self._control_vm("nic{} nat".format(adapter_number + 1)) + yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1)) adapter.add_nio(0, nio) log.info("VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name, @@ -903,6 +907,10 @@ class VirtualBoxVM(BaseVM): raise VirtualBoxError("Sorry, packet capturing on a started VirtualBox VM is not supported.") nio = adapter.get_nio(0) + + if not nio: + raise VirtualBoxError("Adapter {} is not connected".format(adapter_number)) + if nio.capturing: raise VirtualBoxError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number)) @@ -925,6 +933,10 @@ class VirtualBoxVM(BaseVM): adapter_number=adapter_number)) nio = adapter.get_nio(0) + + if not nio: + raise VirtualBoxError("Adapter {} is not connected".format(adapter_number)) + nio.stopPacketCapture() log.info("VirtualBox VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name, diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 7601bd6b..0216ba42 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -83,6 +83,28 @@ class Route(object): def delete(cls, path, *args, **kw): return cls._route('DELETE', path, *args, **kw) + @classmethod + def authenticate(cls, request, route, server_config): + """ + Ask user for authentication + + :returns: Response if you need to auth the user otherwise None + """ + user = server_config.get("user", "").strip() + password = server_config.get("password", "").strip() + + if len(user) == 0: + return + + if "AUTHORIZATION" in request.headers: + if request.headers["AUTHORIZATION"] == aiohttp.helpers.BasicAuth(user, password).encode(): + return + + response = Response(request=request, route=route) + response.set_status(401) + response.headers["WWW-Authenticate"] = 'Basic realm="GNS3 server"' + return response + @classmethod def _route(cls, method, path, *args, **kw): # This block is executed only the first time @@ -119,6 +141,13 @@ class Route(object): def control_schema(request): # This block is executed at each method call + server_config = Config.instance().get_section_config("Server") + + # Authenticate + response = cls.authenticate(request, route, server_config) + if response: + return response + # Non API call if api_version is None or raw is True: response = Response(request=request, route=route, output_schema=output_schema) @@ -129,7 +158,6 @@ class Route(object): # API call try: request = yield from parse_request(request, input_schema) - server_config = Config.instance().get_section_config("Server") record_file = server_config.get("record") if record_file: try: diff --git a/requirements.txt b/requirements.txt index 34a020ca..5040739b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ jsonschema>=2.4.0 aiohttp>=0.15.1 Jinja2>=2.7.3 raven>=5.2.0 +netifaces>=0.10.4 diff --git a/tests/modules/virtualbox/test_virtualbox_manager.py b/tests/modules/virtualbox/test_virtualbox_manager.py index 8d453137..2dfda065 100644 --- a/tests/modules/virtualbox/test_virtualbox_manager.py +++ b/tests/modules/virtualbox/test_virtualbox_manager.py @@ -75,6 +75,7 @@ def test_list_images(manager, loop): vm_list = ['"Windows 8.1" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', '"Carriage', 'Return" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', + '', '"" {42b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', '"Linux Microcore 4.7.1" {ccd8c50b-c172-457d-99fa-dd69371ede0e}']