mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-06 15:21:13 +02:00
Refactor how clients access PCAP capture files. Fixes https://github.com/GNS3/gns3-gui/issues/2438.
* The PCAP file is directly accessed if controller and client are on the same host. * The PCAP file is streamed from the compute server to the client with the controller as a proxy when the controller is remote for the client.
This commit is contained in:
1 parent
bf1b801cc0
commit
2764828f38
47 files changed
+1071
-473
No files matched your search
@@ -428,6 +428,47 @@ class BaseManager:
|
||||
assert nio is not None
|
||||
return nio
|
||||
|
||||
async def stream_pcap_file(self, nio, project_id, request, response):
|
||||
"""
|
||||
Streams a PCAP file.
|
||||
|
||||
:param nio: NIO object
|
||||
:param project_id: Project identifier
|
||||
:param request: request object
|
||||
:param response: response object
|
||||
"""
|
||||
|
||||
if not nio.capturing:
|
||||
raise aiohttp.web.HTTPConflict(text="Nothing to stream because there is no packet capture active")
|
||||
|
||||
project = ProjectManager.instance().get_project(project_id)
|
||||
path = os.path.normpath(os.path.join(project.capture_working_directory(), nio.pcap_output_file))
|
||||
# Raise an error if user try to escape
|
||||
#if path[0] == ".":
|
||||
# raise aiohttp.web.HTTPForbidden()
|
||||
#path = os.path.join(project.path, path)
|
||||
|
||||
response.content_type = "application/vnd.tcpdump.pcap"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
await response.prepare(request)
|
||||
while nio.capturing:
|
||||
data = f.read(4096)
|
||||
if not data:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
try:
|
||||
await response.write(data)
|
||||
except ConnectionError:
|
||||
break
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
def get_abs_image_path(self, path):
|
||||
"""
|
||||
Get the absolute path of an image
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
import subprocess
|
||||
|
||||
from ...error import NodeError
|
||||
@@ -461,13 +460,13 @@ class Cloud(BaseNode):
|
||||
await self.start()
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"):
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: allocated port number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]:
|
||||
@@ -479,6 +478,18 @@ class Cloud(BaseNode):
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param port_number: allocated port number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
if nio.capturing:
|
||||
raise NodeError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||
nio.startPacketCapture(output_file)
|
||||
@@ -496,14 +507,7 @@ class Cloud(BaseNode):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]:
|
||||
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name,
|
||||
port_number=port_number))
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise NodeError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
nio.stopPacketCapture()
|
||||
bridge_name = "{}-{}".format(self._id, port_number)
|
||||
await self._ubridge_send("bridge stop_capture {name}".format(name=bridge_name))
|
||||
|
||||
@@ -853,10 +853,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Update an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update the adapter
|
||||
"""
|
||||
|
||||
if self.ubridge:
|
||||
@@ -895,6 +895,28 @@ class DockerVM(BaseNode):
|
||||
nio=adapter.host_ifc,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise DockerError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
@property
|
||||
def adapters(self):
|
||||
"""
|
||||
@@ -967,17 +989,7 @@ class DockerVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise DockerError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise DockerError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@@ -997,19 +1009,8 @@ class DockerVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise DockerError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._stop_ubridge_capture(adapter_number)
|
||||
|
||||
|
||||
@@ -223,6 +223,25 @@ class ATMSwitch(Device):
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
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))
|
||||
|
||||
return nio
|
||||
|
||||
async def set_mappings(self, mappings):
|
||||
"""
|
||||
Applies VC mappings
|
||||
@@ -424,11 +443,7 @@ class ATMSwitch(Device):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_ATM_RFC1483
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
data_link_type = data_link_type[4:]
|
||||
@@ -450,10 +465,7 @@ class ATMSwitch(Device):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('ATM switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
Hub object that uses the Bridge interface to create a hub with ports.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from .bridge import Bridge
|
||||
from ..nios.nio_udp import NIOUDP
|
||||
from ..dynamips_error import DynamipsError
|
||||
@@ -177,6 +175,25 @@ class EthernetHub(Bridge):
|
||||
del self._mappings[port_number]
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._mappings:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._mappings[port_number]
|
||||
|
||||
if not nio:
|
||||
raise DynamipsError("Port {} is not connected".format(port_number))
|
||||
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_EN10MB"):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
@@ -186,11 +203,7 @@ class EthernetHub(Bridge):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
if port_number not in self._mappings:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._mappings[port_number]
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
data_link_type = data_link_type[4:]
|
||||
@@ -212,10 +225,7 @@ class EthernetHub(Bridge):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._mappings:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._mappings[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('Ethernet hub "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
||||
@@ -300,6 +300,25 @@ class EthernetSwitch(Device):
|
||||
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
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))
|
||||
|
||||
return nio
|
||||
|
||||
async def set_port_settings(self, port_number, settings):
|
||||
"""
|
||||
Applies port settings to a specific port.
|
||||
@@ -413,14 +432,7 @@ class EthernetSwitch(Device):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
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))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
data_link_type = data_link_type[4:]
|
||||
@@ -442,14 +454,7 @@ class EthernetSwitch(Device):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
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))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
||||
@@ -209,6 +209,25 @@ class FrameRelaySwitch(Device):
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
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))
|
||||
|
||||
return nio
|
||||
|
||||
async def set_mappings(self, mappings):
|
||||
"""
|
||||
Applies VC mappings
|
||||
@@ -309,10 +328,7 @@ class FrameRelaySwitch(Device):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_FRELAY
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
|
||||
data_link_type = data_link_type.lower()
|
||||
if data_link_type.startswith("dlt_"):
|
||||
@@ -335,10 +351,7 @@ class FrameRelaySwitch(Device):
|
||||
:param port_number: allocated port number
|
||||
"""
|
||||
|
||||
if port_number not in self._nios:
|
||||
raise DynamipsError("Port {} is not allocated".format(port_number))
|
||||
|
||||
nio = self._nios[port_number]
|
||||
nio = self.get_nio(port_number)
|
||||
await nio.unbind_filter("both")
|
||||
log.info('Frame relay switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
||||
@@ -1264,7 +1264,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError("Adapter is missing in slot {slot_number}".format(slot_number=slot_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
try:
|
||||
@@ -1299,6 +1299,7 @@ class Router(BaseNode):
|
||||
:param port_number: port number
|
||||
:param nio: NIO instance to add to the slot/port
|
||||
"""
|
||||
|
||||
await nio.update()
|
||||
|
||||
async def slot_remove_nio_binding(self, slot_number, port_number):
|
||||
@@ -1321,7 +1322,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError("Adapter is missing in slot {slot_number}".format(slot_number=slot_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
await self.slot_disable_nio(slot_number, port_number)
|
||||
@@ -1362,6 +1363,32 @@ class Router(BaseNode):
|
||||
slot_number=slot_number,
|
||||
port_number=port_number))
|
||||
|
||||
def get_nio(self, slot_number, port_number):
|
||||
"""
|
||||
Gets an slot NIO binding.
|
||||
|
||||
:param slot_number: slot number
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._slots[slot_number]
|
||||
except IndexError:
|
||||
raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name,
|
||||
slot_number=slot_number))
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
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))
|
||||
return nio
|
||||
|
||||
async def slot_disable_nio(self, slot_number, port_number):
|
||||
"""
|
||||
Disables a slot NIO binding.
|
||||
@@ -1402,7 +1429,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name,
|
||||
slot_number=slot_number))
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
data_link_type = data_link_type.lower()
|
||||
@@ -1442,7 +1469,7 @@ class Router(BaseNode):
|
||||
raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name,
|
||||
slot_number=slot_number))
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
|
||||
@@ -840,7 +840,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
async def adapter_add_nio_binding(self, adapter_number, port_number, nio):
|
||||
"""
|
||||
Adds a adapter NIO binding.
|
||||
Adds an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
@@ -854,7 +854,7 @@ class IOUVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
adapter.add_nio(port_number, nio)
|
||||
@@ -877,7 +877,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, port_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Updates an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
@@ -913,6 +913,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
@@ -923,7 +924,7 @@ class IOUVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
@@ -944,6 +945,33 @@ class IOUVM(BaseNode):
|
||||
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number, port_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
@property
|
||||
def l1_keepalives(self):
|
||||
"""
|
||||
@@ -1221,21 +1249,7 @@ class IOUVM(BaseNode):
|
||||
:param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self.get_nio(adapter_number, port_number)
|
||||
if nio.capturing:
|
||||
raise IOUError("Packet capture is already activated on {adapter_number}/{port_number}".format(adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
@@ -1263,21 +1277,7 @@ class IOUVM(BaseNode):
|
||||
:param port_number: port number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self.get_nio(adapter_number, port_number)
|
||||
nio.stopPacketCapture()
|
||||
log.info('IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
||||
@@ -239,12 +239,12 @@ class Project:
|
||||
|
||||
def capture_working_directory(self):
|
||||
"""
|
||||
Returns a working directory where to temporary store packet capture files.
|
||||
Returns a working directory where to store packet capture files.
|
||||
|
||||
:returns: path to the directory
|
||||
"""
|
||||
|
||||
workdir = os.path.join(self._path, "tmp", "captures")
|
||||
workdir = os.path.join(self._path, "project-files", "captures")
|
||||
if not self._deleted:
|
||||
try:
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
|
||||
@@ -1209,7 +1209,7 @@ class QemuVM(BaseNode):
|
||||
|
||||
async def adapter_add_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Adds a port NIO binding.
|
||||
Adds an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
@@ -1239,10 +1239,10 @@ class QemuVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Update an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update the adapter
|
||||
"""
|
||||
|
||||
if self.is_running():
|
||||
@@ -1260,7 +1260,7 @@ class QemuVM(BaseNode):
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
Removes a port NIO binding.
|
||||
Removes an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
@@ -1288,12 +1288,13 @@ class QemuVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
return nio
|
||||
|
||||
async def start_capture(self, adapter_number, output_file):
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -1307,6 +1308,17 @@ class QemuVM(BaseNode):
|
||||
if not nio:
|
||||
raise QemuError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
async def start_capture(self, adapter_number, output_file):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise QemuError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@@ -1327,16 +1339,7 @@ class QemuVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise QemuError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
||||
@@ -322,8 +322,15 @@ class TraceNGVM(BaseNode):
|
||||
return nio
|
||||
|
||||
async def port_update_nio_binding(self, port_number, nio):
|
||||
"""
|
||||
Updates a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
:param nio: NIO instance to update on the slot/port
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
if self.is_running():
|
||||
await self.update_ubridge_udp_connection("TraceNG-{}".format(self._id), self._local_udp_tunnel[1], nio)
|
||||
@@ -355,6 +362,23 @@ class TraceNGVM(BaseNode):
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
nio = self._ethernet_adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise TraceNGError("Port {} is not connected".format(port_number))
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
@@ -363,15 +387,7 @@ class TraceNGVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise TraceNGError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
if nio.capturing:
|
||||
raise TraceNGError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||
|
||||
@@ -392,15 +408,7 @@ class TraceNGVM(BaseNode):
|
||||
:param port_number: port number
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise TraceNGError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
||||
@@ -1014,10 +1014,10 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Update an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update on the adapter
|
||||
"""
|
||||
|
||||
if self.is_running():
|
||||
@@ -1030,10 +1030,8 @@ class VirtualBoxVM(BaseNode):
|
||||
else:
|
||||
await 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,
|
||||
adapter_number=adapter_number
|
||||
))
|
||||
raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@@ -1067,6 +1065,28 @@ class VirtualBoxVM(BaseNode):
|
||||
adapter_number=adapter_number))
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
def is_running(self):
|
||||
"""
|
||||
:returns: True if the vm is not stopped
|
||||
@@ -1081,17 +1101,7 @@ class VirtualBoxVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise VirtualBoxError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@@ -1112,17 +1122,7 @@ class VirtualBoxVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
||||
@@ -750,20 +750,18 @@ class VMwareVM(BaseNode):
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
Update a port NIO binding.
|
||||
Updates an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
:param nio: NIO instance to add to the adapter
|
||||
:param nio: NIO instance to update on the adapter
|
||||
"""
|
||||
|
||||
if self._ubridge_hypervisor:
|
||||
try:
|
||||
await self._update_ubridge_connection(adapter_number, nio)
|
||||
except IndexError:
|
||||
raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(
|
||||
name=self._name,
|
||||
adapter_number=adapter_number
|
||||
))
|
||||
raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@@ -794,6 +792,27 @@ class VMwareVM(BaseNode):
|
||||
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
Gets an adapter NIO binding.
|
||||
|
||||
:param adapter_number: adapter number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
if not nio:
|
||||
raise VMwareError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
return nio
|
||||
|
||||
def _get_pipe_name(self):
|
||||
"""
|
||||
Returns the pipe name to create a serial connection.
|
||||
@@ -875,17 +894,7 @@ class VMwareVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VMwareError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
if nio.capturing:
|
||||
raise VMwareError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||
|
||||
@@ -905,17 +914,7 @@ class VMwareVM(BaseNode):
|
||||
:param adapter_number: adapter number
|
||||
"""
|
||||
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VMwareError("Adapter {} is not connected".format(adapter_number))
|
||||
|
||||
nio = self.get_nio(adapter_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self._started:
|
||||
|
||||
@@ -367,7 +367,7 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
if self.is_running():
|
||||
@@ -382,8 +382,15 @@ class VPCSVM(BaseNode):
|
||||
return nio
|
||||
|
||||
async def port_update_nio_binding(self, port_number, nio):
|
||||
"""
|
||||
Updates a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
:param nio: NIO instance to update on the slot/port
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
if self.is_running():
|
||||
await self.update_ubridge_udp_connection("VPCS-{}".format(self._id), self._local_udp_tunnel[1], nio)
|
||||
@@ -398,7 +405,7 @@ class VPCSVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
if self.is_running():
|
||||
@@ -415,6 +422,23 @@ class VPCSVM(BaseNode):
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
"""
|
||||
Gets a port NIO binding.
|
||||
|
||||
:param port_number: port number
|
||||
|
||||
:returns: NIO instance
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
nio = self._ethernet_adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise VPCSError("Port {} is not connected".format(port_number))
|
||||
return nio
|
||||
|
||||
async def start_capture(self, port_number, output_file):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
@@ -423,17 +447,9 @@ class VPCSVM(BaseNode):
|
||||
:param output_file: PCAP destination file for the capture
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VPCSError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
if nio.capturing:
|
||||
raise VPCSError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||
raise VPCSError("Packet capture is already active on port {port_number}".format(port_number=port_number))
|
||||
|
||||
nio.startPacketCapture(output_file)
|
||||
|
||||
@@ -452,15 +468,7 @@ class VPCSVM(BaseNode):
|
||||
:param port_number: port number
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise VPCSError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VPCSError("Port {} is not connected".format(port_number))
|
||||
|
||||
nio = self.get_nio(port_number)
|
||||
nio.stopPacketCapture()
|
||||
|
||||
if self.ubridge:
|
||||
|
||||
@@ -330,31 +330,17 @@ class Compute:
|
||||
:returns: A file stream
|
||||
"""
|
||||
|
||||
# Due to Python 3.4 limitation we can't use with and asyncio
|
||||
# https://www.python.org/dev/peps/pep-0492/
|
||||
# that why we wrap the answer
|
||||
class StreamResponse:
|
||||
|
||||
def __init__(self, response):
|
||||
self._response = response
|
||||
|
||||
def __enter__(self):
|
||||
return self._response.content
|
||||
|
||||
def __exit__(self):
|
||||
self._response.close()
|
||||
|
||||
url = self._getUrl("/projects/{}/stream/{}".format(project.id, path))
|
||||
response = await self._session().request("GET", url, auth=self._auth, timeout=timeout)
|
||||
if response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(path))
|
||||
raise aiohttp.web.HTTPNotFound(text="file '{}' not found on compute".format(path))
|
||||
elif response.status == 403:
|
||||
raise aiohttp.web.HTTPForbidden(text="forbidden to open {} on compute".format(path))
|
||||
raise aiohttp.web.HTTPForbidden(text="forbidden to open '{}' on compute".format(path))
|
||||
elif response.status != 200:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Unexpected error {}: {}: while opening {} on compute".format(response.status,
|
||||
response.reason,
|
||||
path))
|
||||
return StreamResponse(response)
|
||||
return response
|
||||
|
||||
async def http_query(self, method, path, data=None, dont_connect=False, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -19,7 +19,6 @@ import os
|
||||
import re
|
||||
import uuid
|
||||
import html
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
import logging
|
||||
@@ -118,6 +117,7 @@ class Link:
|
||||
self._nodes = []
|
||||
self._project = project
|
||||
self._capturing = False
|
||||
self._capture_node = None
|
||||
self._capture_file_name = None
|
||||
self._streaming_pcap = None
|
||||
self._created = False
|
||||
@@ -139,6 +139,34 @@ class Link:
|
||||
"""
|
||||
return self._nodes
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
"""
|
||||
Get the project this link belongs to.
|
||||
|
||||
:return: Project instance.
|
||||
"""
|
||||
return self._project
|
||||
|
||||
@property
|
||||
def capture_node(self):
|
||||
"""
|
||||
Get the capturing node
|
||||
|
||||
:return: Node instance.
|
||||
"""
|
||||
return self._capture_node
|
||||
|
||||
@property
|
||||
def compute(self):
|
||||
"""
|
||||
Get the capturing node
|
||||
|
||||
:return: Node instance.
|
||||
"""
|
||||
assert self.capture_node
|
||||
return self.capture_node["node"].compute
|
||||
|
||||
def get_active_filters(self):
|
||||
"""
|
||||
Return the active filters.
|
||||
@@ -289,44 +317,8 @@ class Link:
|
||||
|
||||
self._capturing = True
|
||||
self._capture_file_name = capture_file_name
|
||||
self._streaming_pcap = asyncio.ensure_future(self._start_streaming_pcap())
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
async def _start_streaming_pcap(self):
|
||||
"""
|
||||
Dump a pcap file on disk
|
||||
"""
|
||||
|
||||
if os.path.exists(self.capture_file_path):
|
||||
try:
|
||||
os.remove(self.capture_file_path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not delete old capture file '{}': {}".format(self.capture_file_path, e))
|
||||
|
||||
try:
|
||||
stream_content = await self.read_pcap_from_source()
|
||||
except aiohttp.web.HTTPException as e:
|
||||
error_msg = "Could not stream PCAP file: error {}: {}".format(e.status, e.text)
|
||||
log.error(error_msg)
|
||||
self._capturing = False
|
||||
self._project.notification.project_emit("log.error", {"message": error_msg})
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
with stream_content as stream:
|
||||
try:
|
||||
with open(self.capture_file_path, "wb") as f:
|
||||
while self._capturing:
|
||||
# We read 1 bytes by 1 otherwise the remaining data is not read if the traffic stops
|
||||
data = await stream.read(1)
|
||||
if data:
|
||||
f.write(data)
|
||||
# Flush to disk otherwise the live is not really live
|
||||
f.flush()
|
||||
else:
|
||||
break
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not write capture file '{}': {}".format(self.capture_file_path, e))
|
||||
|
||||
async def stop_capture(self):
|
||||
"""
|
||||
Stop capture on the link
|
||||
@@ -335,12 +327,26 @@ class Link:
|
||||
self._capturing = False
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
async def _read_pcap_from_source(self):
|
||||
def pcap_streaming_url(self):
|
||||
"""
|
||||
Return a FileStream of the Pcap from the compute server
|
||||
Get the PCAP streaming URL on compute
|
||||
|
||||
:returns: URL
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
assert self.capture_node
|
||||
compute = self.capture_node["node"].compute
|
||||
node_type = self.capture_node["node"].node_type
|
||||
node_id = self.capture_node["node"].id
|
||||
adapter_number = self.capture_node["adapter_number"]
|
||||
port_number = self.capture_node["port_number"]
|
||||
url = "/projects/{project_id}/{node_type}/nodes/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap".format(project_id=self.project.id,
|
||||
node_type=node_type,
|
||||
node_id=node_id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number)
|
||||
|
||||
return compute._getUrl(url)
|
||||
|
||||
async def node_updated(self, node):
|
||||
"""
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ class UDPLink(Link):
|
||||
|
||||
def __init__(self, project, link_id=None):
|
||||
super().__init__(project, link_id=link_id)
|
||||
self._capture_node = None
|
||||
self._created = False
|
||||
self._link_data = []
|
||||
|
||||
@@ -164,10 +163,8 @@ class UDPLink(Link):
|
||||
if not capture_file_name:
|
||||
capture_file_name = self.default_capture_file_name()
|
||||
self._capture_node = self._choose_capture_side()
|
||||
data = {
|
||||
"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type
|
||||
}
|
||||
data = {"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type}
|
||||
await self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/start_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]), data=data)
|
||||
await super().start_capture(data_link_type=data_link_type, capture_file_name=capture_file_name)
|
||||
|
||||
@@ -210,14 +207,6 @@ class UDPLink(Link):
|
||||
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot capture because there is no running device on this link")
|
||||
|
||||
async def read_pcap_from_source(self):
|
||||
"""
|
||||
Return a FileStream of the Pcap from the compute node
|
||||
"""
|
||||
if self._capture_node:
|
||||
compute = self._capture_node["node"].compute
|
||||
return compute.stream_file(self._project, "tmp/captures/" + self._capture_file_name)
|
||||
|
||||
async def node_updated(self, node):
|
||||
"""
|
||||
Called when a node member of the link is updated
|
||||
|
||||
@@ -288,3 +288,25 @@ class ATMSwitchHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/atm_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -222,21 +222,16 @@ class CloudHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Cloud instance")
|
||||
description="Update a NIO on a Cloud instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
|
||||
try:
|
||||
nio = node.nios[adapter_number]
|
||||
except KeyError:
|
||||
raise HTTPConflict(text="NIO `{}` doesn't exist".format(adapter_number))
|
||||
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await node.update_nio(int(request.match_info["port_number"]), nio)
|
||||
await node.update_nio(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@@ -307,3 +302,25 @@ class CloudHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/cloud/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the cloud"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await builtin_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -238,8 +238,9 @@ class DockerHandler:
|
||||
nio_type = request.json["type"]
|
||||
if nio_type != "nio_udp":
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = docker_manager.create_nio(request.json)
|
||||
await container.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
await container.adapter_add_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@@ -249,7 +250,7 @@ class DockerHandler:
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
"port_number": "Port from where the nio should be updated (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
@@ -258,15 +259,16 @@ class DockerHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Docker instance")
|
||||
description="Update a NIO on a Docker instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = container.ethernet_adapters[int(request.match_info["adapter_number"])].get_nio(0)
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = container.get_nio(adapter_number)
|
||||
if "filters" in request.json and nio:
|
||||
nio.filters = request.json["filters"]
|
||||
await container.adapter_update_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await container.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@@ -276,7 +278,7 @@ class DockerHandler:
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter"
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
@@ -287,7 +289,8 @@ class DockerHandler:
|
||||
async def delete_nio(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await container.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.put(
|
||||
@@ -333,7 +336,7 @@ class DockerHandler:
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
@@ -349,7 +352,6 @@ class DockerHandler:
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
pcap_file_path = os.path.join(container.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
|
||||
await container.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": str(pcap_file_path)})
|
||||
|
||||
@@ -372,11 +374,32 @@ class DockerHandler:
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await container.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = container.get_nio(adapter_number)
|
||||
await docker_manager.stream_pcap_file(nio, container.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/docker/images",
|
||||
status_codes={
|
||||
|
||||
@@ -285,15 +285,15 @@ class DynamipsVMHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Dynamips instance")
|
||||
description="Update a NIO on a Dynamips instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.slots[slot_number].get_nio(port_number)
|
||||
if "filters" in request.json and nio:
|
||||
nio = vm.get_nio(slot_number, port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.slot_update_nio_binding(slot_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
@@ -379,6 +379,29 @@ class DynamipsVMHandler:
|
||||
await vm.stop_capture(slot_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(slot_number, port_number)
|
||||
await dynamips_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/idlepc_proposals",
|
||||
parameters={
|
||||
@@ -485,10 +508,8 @@ class DynamipsVMHandler:
|
||||
description="Duplicate a dynamips instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@@ -94,10 +94,8 @@ class EthernetHubHandler:
|
||||
description="Duplicate an ethernet hub instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@@ -292,3 +290,25 @@ class EthernetHubHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/ethernet_hub/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the hub"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -105,10 +105,8 @@ class EthernetSwitchHandler:
|
||||
description="Duplicate an ethernet switch instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@@ -319,3 +317,25 @@ class EthernetSwitchHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -288,3 +288,25 @@ class FrameRelaySwitchHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/frame_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await node.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -290,7 +290,7 @@ class IOUHandler:
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Update a NIO from a IOU instance",
|
||||
description="Update a NIO on an IOU instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def update_nio(request, response):
|
||||
@@ -299,13 +299,10 @@ class IOUHandler:
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.adapters[adapter_number].get_nio(port_number)
|
||||
if "filters" in request.json and nio:
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.adapter_update_nio_binding(
|
||||
adapter_number,
|
||||
port_number,
|
||||
nio)
|
||||
await vm.adapter_update_nio_binding(adapter_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@@ -375,12 +372,34 @@ class IOUHandler:
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(adapter_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
await iou_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/iou/images",
|
||||
status_codes={
|
||||
|
||||
@@ -21,6 +21,7 @@ from gns3server.web.route import Route
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.compute.builtin import Builtin
|
||||
from aiohttp.web import HTTPConflict
|
||||
|
||||
from gns3server.schemas.nat import (
|
||||
NAT_CREATE_SCHEMA,
|
||||
@@ -213,15 +214,16 @@ class NatHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a NAT instance")
|
||||
description="Update a NIO on a NAT instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = node.nios[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await node.update_nio(int(request.match_info["port_number"]), nio)
|
||||
await node.update_nio(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@@ -292,3 +294,25 @@ class NatHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nat/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the nat"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await builtin_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -295,6 +295,7 @@ class ProjectHandler:
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
|
||||
# FIXME: file streaming is never stopped
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
await response.prepare(request)
|
||||
@@ -303,7 +304,6 @@ class ProjectHandler:
|
||||
if not data:
|
||||
await asyncio.sleep(0.1)
|
||||
await response.write(data)
|
||||
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
|
||||
@@ -334,17 +334,18 @@ class QEMUHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Qemu instance")
|
||||
description="Update a NIO on a Qemu instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json and nio:
|
||||
if "suspend" in request.json:
|
||||
nio.suspend = request.json["suspend"]
|
||||
await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@@ -366,7 +367,8 @@ class QEMUHandler:
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@@ -415,6 +417,28 @@ class QEMUHandler:
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await qemu_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/binaries",
|
||||
status_codes={
|
||||
|
||||
@@ -261,15 +261,16 @@ class TraceNGHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a TraceNG instance")
|
||||
description="Update a NIO on a TraceNG instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapter.get_nio(int(request.match_info["port_number"]))
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.port_update_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await vm.port_update_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@@ -291,7 +292,8 @@ class TraceNGHandler:
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.port_remove_nio_binding(int(request.match_info["port_number"]))
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.port_remove_nio_binding(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@@ -339,3 +341,25 @@ class TraceNGHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
await traceng_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
@@ -309,17 +309,18 @@ class VirtualBoxHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Virtualbox instance")
|
||||
description="Update a NIO on a Virtualbox instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json and nio:
|
||||
if "suspend" in request.json:
|
||||
nio.suspend = request.json["suspend"]
|
||||
await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@@ -341,7 +342,8 @@ class VirtualBoxHandler:
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@@ -386,9 +388,32 @@ class VirtualBoxHandler:
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop_capture(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await virtualbox_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/virtualbox/vms",
|
||||
status_codes={
|
||||
|
||||
@@ -276,17 +276,18 @@ class VMwareHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a Virtualbox instance")
|
||||
description="Update a NIO on a VMware VM instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
|
||||
if "filters" in request.json and nio:
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
@@ -306,7 +307,8 @@ class VMwareHandler:
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@@ -355,6 +357,28 @@ class VMwareHandler:
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await vmware_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/interfaces/vmnet",
|
||||
parameters={
|
||||
|
||||
@@ -239,8 +239,9 @@ class VPCSHandler:
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_tap"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vpcs_manager.create_nio(request.json)
|
||||
await vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await vm.port_add_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@@ -259,15 +260,16 @@ class VPCSHandler:
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO from a VPCS instance")
|
||||
description="Update a NIO on a VPCS instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = vm.ethernet_adapter.get_nio(int(request.match_info["port_number"]))
|
||||
if "filters" in request.json and nio:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.port_update_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
await vm.port_update_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@@ -289,7 +291,8 @@ class VPCSHandler:
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.port_remove_nio_binding(int(request.match_info["port_number"]))
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.port_remove_nio_binding(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
@@ -337,3 +340,25 @@ class VPCSHandler:
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
await vpcs_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
@@ -15,9 +15,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import multidict
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
@@ -160,7 +159,8 @@ class LinkHandler:
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
await link.start_capture(data_link_type=request.json.get("data_link_type", "DLT_EN10MB"), capture_file_name=request.json.get("capture_file_name"))
|
||||
await link.start_capture(data_link_type=request.json.get("data_link_type", "DLT_EN10MB"),
|
||||
capture_file_name=request.json.get("capture_file_name"))
|
||||
response.set_status(201)
|
||||
response.json(link)
|
||||
|
||||
@@ -206,7 +206,7 @@ class LinkHandler:
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
description="Stream the pcap capture file",
|
||||
description="Stream the PCAP capture file from compute",
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
@@ -216,25 +216,25 @@ class LinkHandler:
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
if not link.capturing:
|
||||
raise aiohttp.web.HTTPConflict(text="This link has no active packet capture")
|
||||
|
||||
while link.capture_file_path is None:
|
||||
raise aiohttp.web.HTTPNotFound(text="pcap file not found")
|
||||
compute = link.compute
|
||||
pcap_streaming_url = link.pcap_streaming_url()
|
||||
headers = multidict.MultiDict(request.headers)
|
||||
headers['Host'] = compute.host
|
||||
headers['Router-Host'] = request.host
|
||||
body = await request.read()
|
||||
|
||||
while not os.path.isfile(link.capture_file_path):
|
||||
await asyncio.sleep(0.5)
|
||||
connector = aiohttp.TCPConnector(limit=None, force_close=True)
|
||||
async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
|
||||
async with session.request(request.method, pcap_streaming_url, timeout=None, data=body) as response:
|
||||
proxied_response = aiohttp.web.Response(headers=response.headers, status=response.status)
|
||||
if response.headers.get('Transfer-Encoding', '').lower() == 'chunked':
|
||||
proxied_response.enable_chunked_encoding()
|
||||
|
||||
try:
|
||||
with open(link.capture_file_path, "rb") as f:
|
||||
|
||||
response.content_type = "application/vnd.tcpdump.pcap"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
await response.prepare(request)
|
||||
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
await asyncio.sleep(0.1)
|
||||
await response.write(chunk)
|
||||
except OSError:
|
||||
raise aiohttp.web.HTTPNotFound(text="pcap file {} not found or not accessible".format(link.capture_file_path))
|
||||
await proxied_response.prepare(request)
|
||||
async for data in response.content.iter_any():
|
||||
if not data:
|
||||
break
|
||||
await proxied_response.write(data)
|
||||
@@ -248,9 +248,10 @@ class Route(object):
|
||||
"""
|
||||
To avoid strange effect we prevent concurrency
|
||||
between the same instance of the node
|
||||
(excepting when streaming a PCAP file).
|
||||
"""
|
||||
|
||||
if "node_id" in request.match_info:
|
||||
if "node_id" in request.match_info and not "pcap" in request.path:
|
||||
node_id = request.match_info.get("node_id")
|
||||
|
||||
if "compute" in request.path:
|
||||
|
||||
Reference in new issue
Block a user