mirror of
https://github.com/GNS3/gns3-server.git
synced 2024-12-19 04:47:54 +00:00
Packet capture support for VirtualBox.
This commit is contained in:
parent
ff63530f52
commit
365af02f37
@ -15,10 +15,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
from ..web.route import Route
|
from ..web.route import Route
|
||||||
from ..schemas.virtualbox import VBOX_CREATE_SCHEMA
|
from ..schemas.virtualbox import VBOX_CREATE_SCHEMA
|
||||||
from ..schemas.virtualbox import VBOX_UPDATE_SCHEMA
|
from ..schemas.virtualbox import VBOX_UPDATE_SCHEMA
|
||||||
from ..schemas.virtualbox import VBOX_NIO_SCHEMA
|
from ..schemas.virtualbox import VBOX_NIO_SCHEMA
|
||||||
|
from ..schemas.virtualbox import VBOX_CAPTURE_SCHEMA
|
||||||
from ..schemas.virtualbox import VBOX_OBJECT_SCHEMA
|
from ..schemas.virtualbox import VBOX_OBJECT_SCHEMA
|
||||||
from ..modules.virtualbox import VirtualBox
|
from ..modules.virtualbox import VirtualBox
|
||||||
|
|
||||||
@ -262,3 +264,45 @@ class VirtualBoxHandler:
|
|||||||
vm = vbox_manager.get_vm(request.match_info["uuid"])
|
vm = vbox_manager.get_vm(request.match_info["uuid"])
|
||||||
vm.port_remove_nio_binding(int(request.match_info["port_id"]))
|
vm.port_remove_nio_binding(int(request.match_info["port_id"]))
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
|
@Route.post(
|
||||||
|
r"/virtualbox/{uuid}/capture/{port_id:\d+}/start",
|
||||||
|
parameters={
|
||||||
|
"uuid": "Instance UUID",
|
||||||
|
"port_id": "ID of the port to start a packet capture"
|
||||||
|
},
|
||||||
|
status_codes={
|
||||||
|
200: "Capture started",
|
||||||
|
400: "Invalid instance UUID",
|
||||||
|
404: "Instance doesn't exist"
|
||||||
|
},
|
||||||
|
description="Start a packet capture on a VirtualBox VM instance",
|
||||||
|
input=VBOX_CAPTURE_SCHEMA)
|
||||||
|
def start_capture(request, response):
|
||||||
|
|
||||||
|
vbox_manager = VirtualBox.instance()
|
||||||
|
vm = vbox_manager.get_vm(request.match_info["uuid"])
|
||||||
|
port_id = int(request.match_info["port_id"])
|
||||||
|
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["filename"])
|
||||||
|
vm.start_capture(port_id, pcap_file_path)
|
||||||
|
response.json({"port_id": port_id,
|
||||||
|
"pcap_file_path": pcap_file_path})
|
||||||
|
|
||||||
|
@Route.post(
|
||||||
|
r"/virtualbox/{uuid}/capture/{port_id:\d+}/stop",
|
||||||
|
parameters={
|
||||||
|
"uuid": "Instance UUID",
|
||||||
|
"port_id": "ID of the port to stop a packet capture"
|
||||||
|
},
|
||||||
|
status_codes={
|
||||||
|
204: "Capture stopped",
|
||||||
|
400: "Invalid instance UUID",
|
||||||
|
404: "Instance doesn't exist"
|
||||||
|
},
|
||||||
|
description="Stop a packet capture on a VirtualBox VM instance")
|
||||||
|
def start_capture(request, response):
|
||||||
|
|
||||||
|
vbox_manager = VirtualBox.instance()
|
||||||
|
vm = vbox_manager.get_vm(request.match_info["uuid"])
|
||||||
|
vm.stop_capture(int(request.match_info["port_id"]))
|
||||||
|
response.set_status(204)
|
||||||
|
@ -30,8 +30,8 @@ from uuid import UUID, uuid4
|
|||||||
from ..config import Config
|
from ..config import Config
|
||||||
from .project_manager import ProjectManager
|
from .project_manager import ProjectManager
|
||||||
|
|
||||||
from .nios.nio_udp import NIO_UDP
|
from .nios.nio_udp import NIOUDP
|
||||||
from .nios.nio_tap import NIO_TAP
|
from .nios.nio_tap import NIOTAP
|
||||||
|
|
||||||
|
|
||||||
class BaseManager:
|
class BaseManager:
|
||||||
@ -237,11 +237,11 @@ class BaseManager:
|
|||||||
sock.connect((rhost, rport))
|
sock.connect((rhost, rport))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
|
raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
|
||||||
nio = NIO_UDP(lport, rhost, rport)
|
nio = NIOUDP(lport, rhost, rport)
|
||||||
elif nio_settings["type"] == "nio_tap":
|
elif nio_settings["type"] == "nio_tap":
|
||||||
tap_device = nio_settings["tap_device"]
|
tap_device = nio_settings["tap_device"]
|
||||||
if not self._has_privileged_access(executable):
|
if not self._has_privileged_access(executable):
|
||||||
raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device))
|
raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device))
|
||||||
nio = NIO_TAP(tap_device)
|
nio = NIOTAP(tap_device)
|
||||||
assert nio is not None
|
assert nio is not None
|
||||||
return nio
|
return nio
|
||||||
|
65
gns3server/modules/nios/nio.py
Normal file
65
gns3server/modules/nios/nio.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base interface for NIOs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class NIO(object):
|
||||||
|
"""
|
||||||
|
Network Input/Output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self._capturing = False
|
||||||
|
self._pcap_output_file = ""
|
||||||
|
|
||||||
|
def startPacketCapture(self, pcap_output_file):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param pcap_output_file: PCAP destination file for the capture
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._capturing = True
|
||||||
|
self._pcap_output_file = pcap_output_file
|
||||||
|
|
||||||
|
def stopPacketCapture(self):
|
||||||
|
|
||||||
|
self._capturing = False
|
||||||
|
self._pcap_output_file = ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capturing(self):
|
||||||
|
"""
|
||||||
|
Returns either a capture is configured on this NIO.
|
||||||
|
|
||||||
|
:returns: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._capturing
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pcap_output_file(self):
|
||||||
|
"""
|
||||||
|
Returns the path to the PCAP output file.
|
||||||
|
|
||||||
|
:returns: path to the PCAP output file
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._pcap_output_file
|
@ -19,8 +19,10 @@
|
|||||||
Interface for TAP NIOs (UNIX based OSes only).
|
Interface for TAP NIOs (UNIX based OSes only).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .nio import NIO
|
||||||
|
|
||||||
class NIO_TAP(object):
|
|
||||||
|
class NIOTAP(NIO):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
TAP NIO.
|
TAP NIO.
|
||||||
@ -30,6 +32,7 @@ class NIO_TAP(object):
|
|||||||
|
|
||||||
def __init__(self, tap_device):
|
def __init__(self, tap_device):
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
self._tap_device = tap_device
|
self._tap_device = tap_device
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -19,8 +19,10 @@
|
|||||||
Interface for UDP NIOs.
|
Interface for UDP NIOs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .nio import NIO
|
||||||
|
|
||||||
class NIO_UDP(object):
|
|
||||||
|
class NIOUDP(NIO):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
UDP NIO.
|
UDP NIO.
|
||||||
@ -30,10 +32,9 @@ class NIO_UDP(object):
|
|||||||
:param rport: remote port number
|
:param rport: remote port number
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_instance_count = 0
|
|
||||||
|
|
||||||
def __init__(self, lport, rhost, rport):
|
def __init__(self, lport, rhost, rport):
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
self._lport = lport
|
self._lport = lport
|
||||||
self._rhost = rhost
|
self._rhost = rhost
|
||||||
self._rport = rport
|
self._rport = rport
|
||||||
|
@ -122,7 +122,21 @@ class Project:
|
|||||||
try:
|
try:
|
||||||
os.makedirs(workdir, exist_ok=True)
|
os.makedirs(workdir, exist_ok=True)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create VM working directory: {}".format(e))
|
raise aiohttp.web.HTTPInternalServerError(text="Could not create the VM working directory: {}".format(e))
|
||||||
|
return workdir
|
||||||
|
|
||||||
|
def capture_working_directory(self):
|
||||||
|
"""
|
||||||
|
Return a working directory where to store packet capture files.
|
||||||
|
|
||||||
|
:returns: path to the directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
workdir = os.path.join(self._path, "captures")
|
||||||
|
try:
|
||||||
|
os.makedirs(workdir, exist_ok=True)
|
||||||
|
except OSError as e:
|
||||||
|
raise aiohttp.web.HTTPInternalServerError(text="Could not create the capture working directory: {}".format(e))
|
||||||
return workdir
|
return workdir
|
||||||
|
|
||||||
def mark_vm_for_destruction(self, vm):
|
def mark_vm_for_destruction(self, vm):
|
||||||
|
@ -23,7 +23,6 @@ import sys
|
|||||||
import shlex
|
import shlex
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
@ -833,13 +832,7 @@ class VirtualBoxVM(BaseVM):
|
|||||||
if nio.capturing:
|
if nio.capturing:
|
||||||
raise VirtualBoxError("Packet capture is already activated on adapter {adapter_id}".format(adapter_id=adapter_id))
|
raise VirtualBoxError("Packet capture is already activated on adapter {adapter_id}".format(adapter_id=adapter_id))
|
||||||
|
|
||||||
try:
|
|
||||||
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
|
||||||
except OSError as e:
|
|
||||||
raise VirtualBoxError("Could not create captures directory {}".format(e))
|
|
||||||
|
|
||||||
nio.startPacketCapture(output_file)
|
nio.startPacketCapture(output_file)
|
||||||
|
|
||||||
log.info("VirtualBox VM '{name}' [{uuid}]: starting packet capture on adapter {adapter_id}".format(name=self.name,
|
log.info("VirtualBox VM '{name}' [{uuid}]: starting packet capture on adapter {adapter_id}".format(name=self.name,
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
adapter_id=adapter_id))
|
adapter_id=adapter_id))
|
||||||
|
@ -154,6 +154,21 @@ VBOX_NIO_SCHEMA = {
|
|||||||
"required": ["type"]
|
"required": ["type"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VBOX_CAPTURE_SCHEMA = {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "Request validation to start a packet capture on a VirtualBox VM instance port",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"capture_filename": {
|
||||||
|
"description": "Capture file name",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"required": ["capture_filename"]
|
||||||
|
}
|
||||||
|
|
||||||
VBOX_OBJECT_SCHEMA = {
|
VBOX_OBJECT_SCHEMA = {
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"description": "VirtualBox VM instance",
|
"description": "VirtualBox VM instance",
|
||||||
|
Loading…
Reference in New Issue
Block a user