mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +00:00
Use uBridge for VPCS connections. Ref #267.
This commit is contained in:
parent
b456a363ca
commit
00da15e4af
@ -48,7 +48,7 @@ class BaseNode:
|
||||
:param allocate_aux: Boolean if true will allocate an aux console port
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, allocate_aux=False):
|
||||
def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, allocate_aux=False, use_ubridge=True):
|
||||
|
||||
self._name = name
|
||||
self._usage = ""
|
||||
@ -61,6 +61,7 @@ class BaseNode:
|
||||
self._temporary_directory = None
|
||||
self._hw_virtualization = False
|
||||
self._ubridge_hypervisor = None
|
||||
self._use_ubridge = use_ubridge
|
||||
self._closed = False
|
||||
self._node_status = "stopped"
|
||||
self._command_line = ""
|
||||
@ -271,10 +272,9 @@ class BaseNode:
|
||||
if self._closed:
|
||||
return False
|
||||
|
||||
log.info("{module}: '{name}' [{id}]: is closing".format(
|
||||
module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id))
|
||||
log.info("{module}: '{name}' [{id}]: is closing".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id))
|
||||
|
||||
if self._console:
|
||||
self._manager.port_manager.release_tcp_port(self._console, self._project)
|
||||
@ -403,6 +403,36 @@ class BaseNode:
|
||||
id=self.id,
|
||||
console_type=console_type))
|
||||
|
||||
@property
|
||||
def use_ubridge(self):
|
||||
"""
|
||||
Returns if uBridge is used for this node or not
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return self._use_ubridge
|
||||
|
||||
@property
|
||||
def ubridge(self):
|
||||
"""
|
||||
Returns the uBridge hypervisor.
|
||||
|
||||
:returns: path to uBridge
|
||||
"""
|
||||
|
||||
return self._ubridge_hypervisor
|
||||
|
||||
@ubridge.setter
|
||||
def ubridge(self, ubride_hypervisor):
|
||||
"""
|
||||
Set an uBridge hypervisor.
|
||||
|
||||
:param ubride_hypervisor: uBridge hypervisor
|
||||
"""
|
||||
|
||||
self._ubridge_hypervisor = ubride_hypervisor
|
||||
|
||||
@property
|
||||
def ubridge_path(self):
|
||||
"""
|
||||
@ -445,7 +475,8 @@ class BaseNode:
|
||||
|
||||
server_config = self._manager.config.get_section_config("Server")
|
||||
server_host = server_config.get("host")
|
||||
self._ubridge_hypervisor = Hypervisor(self._project, self.ubridge_path, self.working_dir, server_host)
|
||||
if not self._ubridge_hypervisor:
|
||||
self._ubridge_hypervisor = Hypervisor(self._project, self.ubridge_path, self.working_dir, server_host)
|
||||
log.info("Starting new uBridge hypervisor {}:{}".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
|
||||
yield from self._ubridge_hypervisor.start()
|
||||
log.info("Hypervisor {}:{} has successfully started".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
|
||||
|
@ -77,7 +77,6 @@ class DockerVM(BaseNode):
|
||||
self._environment = environment
|
||||
self._cid = None
|
||||
self._ethernet_adapters = []
|
||||
self._ubridge_hypervisor = None
|
||||
self._temporary_directory = None
|
||||
self._telnet_servers = []
|
||||
self._x11vnc_process = None
|
||||
|
@ -240,6 +240,26 @@ class Project:
|
||||
|
||||
self._nodes.add(node)
|
||||
|
||||
def get_node(self, node_id):
|
||||
"""
|
||||
Returns a Node instance.
|
||||
|
||||
:param node_id: Node identifier
|
||||
|
||||
:returns: Node instance
|
||||
"""
|
||||
|
||||
try:
|
||||
UUID(node_id, version=4)
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPBadRequest(text="Node ID {} is not a valid UUID".format(node_id))
|
||||
|
||||
for node in self._nodes:
|
||||
if node.id == node_id:
|
||||
return node
|
||||
|
||||
raise aiohttp.web.HTTPNotFound(text="Node ID {} doesn't exist".format(node_id))
|
||||
|
||||
def remove_node(self, node):
|
||||
"""
|
||||
Removes a node from the project.
|
||||
|
@ -28,16 +28,19 @@ import re
|
||||
import asyncio
|
||||
import shutil
|
||||
|
||||
from ...utils.asyncio import wait_for_process_termination
|
||||
from ...utils.asyncio import monitor_process
|
||||
from ...utils.asyncio import subprocess_check_output
|
||||
from gns3server.utils.asyncio import wait_for_process_termination
|
||||
from gns3server.utils.asyncio import monitor_process
|
||||
from gns3server.utils.asyncio import subprocess_check_output
|
||||
from gns3server.utils import parse_version
|
||||
from gns3server.compute.port_manager import PortManager
|
||||
|
||||
from .vpcs_error import VPCSError
|
||||
from ..adapters.ethernet_adapter import EthernetAdapter
|
||||
from ..nios.nio_udp import NIOUDP
|
||||
from ..nios.nio_tap import NIOTAP
|
||||
from ..base_node import BaseNode
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -63,6 +66,7 @@ class VPCSVM(BaseNode):
|
||||
self._vpcs_stdout_file = ""
|
||||
self._vpcs_version = None
|
||||
self._started = False
|
||||
self._local_udp_tunnel = None
|
||||
|
||||
# VPCS settings
|
||||
if startup_script is not None:
|
||||
@ -82,6 +86,13 @@ class VPCSVM(BaseNode):
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
|
||||
if self._local_udp_tunnel:
|
||||
self.manager.port_manager.release_udp_port(self._local_udp_tunnel[0].lport, self._project)
|
||||
self.manager.port_manager.release_udp_port(self._local_udp_tunnel[1].lport, self._project)
|
||||
self._local_udp_tunnel = None
|
||||
|
||||
yield from self._stop_ubridge()
|
||||
|
||||
if self.is_running():
|
||||
self._terminate_process()
|
||||
|
||||
@ -221,7 +232,8 @@ class VPCSVM(BaseNode):
|
||||
|
||||
yield from self._check_requirements()
|
||||
if not self.is_running():
|
||||
if not self._ethernet_adapter.get_nio(0):
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
if not self.use_ubridge and not nio:
|
||||
raise VPCSError("This VPCS instance must be connected in order to start")
|
||||
|
||||
command = self._build_command()
|
||||
@ -240,6 +252,12 @@ class VPCSVM(BaseNode):
|
||||
cwd=self.working_dir,
|
||||
creationflags=flags)
|
||||
monitor_process(self._process, self._termination_callback)
|
||||
|
||||
if self.use_ubridge:
|
||||
yield from self._start_ubridge()
|
||||
if nio:
|
||||
yield from self._add_ubridge_connection(self._local_udp_tunnel[1], nio)
|
||||
|
||||
log.info("VPCS instance {} started PID={}".format(self.name, self._process.pid))
|
||||
self._started = True
|
||||
self.status = "started"
|
||||
@ -268,6 +286,7 @@ class VPCSVM(BaseNode):
|
||||
Stops the VPCS process.
|
||||
"""
|
||||
|
||||
yield from self._stop_ubridge()
|
||||
if self.is_running():
|
||||
self._terminate_process()
|
||||
if self._process.returncode is None:
|
||||
@ -336,6 +355,38 @@ class VPCSVM(BaseNode):
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def _add_ubridge_connection(self, source_nio, destination_nio):
|
||||
"""
|
||||
Creates a connection in uBridge.
|
||||
|
||||
:param nio: NIO instance
|
||||
:param port_number: port number
|
||||
"""
|
||||
|
||||
bridge_name = "VPCS-{}".format(self._id)
|
||||
yield from self._ubridge_send("bridge create {name}".format(name=bridge_name))
|
||||
|
||||
if not isinstance(destination_nio, NIOUDP):
|
||||
raise VPCSError("Destination NIO is not UDP")
|
||||
|
||||
yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name,
|
||||
lport=source_nio.lport,
|
||||
rhost=source_nio.rhost,
|
||||
rport=source_nio.rport))
|
||||
|
||||
yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=bridge_name,
|
||||
lport=destination_nio.lport,
|
||||
rhost=destination_nio.rhost,
|
||||
rport=destination_nio.rport))
|
||||
|
||||
if destination_nio.capturing:
|
||||
yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=bridge_name,
|
||||
pcap_file=destination_nio.pcap_output_file))
|
||||
|
||||
yield from self._ubridge_send('bridge start {name}'.format(name=bridge_name))
|
||||
|
||||
@asyncio.coroutine
|
||||
def port_add_nio_binding(self, port_number, nio):
|
||||
"""
|
||||
Adds a port NIO binding.
|
||||
@ -353,8 +404,27 @@ class VPCSVM(BaseNode):
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
port_number=port_number))
|
||||
if self._started and self.ubridge:
|
||||
yield from self._add_ubridge_connection(self._local_udp_tunnel[1], nio)
|
||||
|
||||
return nio
|
||||
|
||||
def _create_local_udp_tunnel(self):
|
||||
|
||||
m = PortManager.instance()
|
||||
lport = m.get_free_udp_port(self.project)
|
||||
rport = m.get_free_udp_port(self.project)
|
||||
source_nio_settings = {'lport': lport, 'rhost': '127.0.0.1', 'rport': rport, 'type': 'nio_udp'}
|
||||
destination_nio_settings = {'lport': rport, 'rhost': '127.0.0.1', 'rport': lport, 'type': 'nio_udp'}
|
||||
source_nio = self.manager.create_nio(self.ubridge_path, source_nio_settings)
|
||||
destination_nio = self.manager.create_nio(self.ubridge_path, destination_nio_settings)
|
||||
self._local_udp_tunnel = (source_nio, destination_nio)
|
||||
log.info('VPCS "{name}" [{id}]: local UDP tunnel created between port {port1} and {port2}'.format(name=self._name,
|
||||
id=self.id,
|
||||
port1=lport,
|
||||
port2=rport))
|
||||
|
||||
@asyncio.coroutine
|
||||
def port_remove_nio_binding(self, port_number):
|
||||
"""
|
||||
Removes a port NIO binding.
|
||||
@ -373,12 +443,74 @@ class VPCSVM(BaseNode):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
self._ethernet_adapter.remove_nio(port_number)
|
||||
|
||||
if self._started and self.ubridge:
|
||||
yield from self._ubridge_send("bridge delete {name}".format(name="VPCS-{}".format(self._id)))
|
||||
|
||||
log.info('VPCS "{name}" [{id}]: {nio} removed from port {port_number}'.format(name=self._name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
port_number=port_number))
|
||||
return nio
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_capture(self, port_number, output_file):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param port_number: adapter number
|
||||
: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))
|
||||
|
||||
if not self.use_ubridge:
|
||||
raise VPCSError("uBridge must be enabled in order to start packet capture")
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
|
||||
if not nio:
|
||||
raise VPCSError("Port {} is not connected".format(port_number))
|
||||
|
||||
if nio.capturing:
|
||||
raise VPCSError("Packet capture is already activated on port {port_number}".format(port_number=port_number))
|
||||
|
||||
nio.startPacketCapture(output_file)
|
||||
|
||||
if self._started and self.ubridge:
|
||||
yield from self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name="VPCS-{}".format(self._id),
|
||||
output_file=output_file))
|
||||
|
||||
log.info("VPCS '{name}' [{id}]: starting packet capture on port {port_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number))
|
||||
|
||||
def stop_capture(self, port_number):
|
||||
"""
|
||||
Stops a packet capture.
|
||||
|
||||
: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.stopPacketCapture()
|
||||
|
||||
if self._started and self.ubridge:
|
||||
yield from self._ubridge_send('bridge stop_capture {name}'.format(name="VPCS-{}".format(self._id)))
|
||||
|
||||
log.info("VPCS '{name}' [{id}]: stopping packet capture on port {port_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number))
|
||||
|
||||
def _build_command(self):
|
||||
"""
|
||||
Command to start the VPCS process.
|
||||
@ -425,8 +557,14 @@ class VPCSVM(BaseNode):
|
||||
else:
|
||||
log.warn("The VPCS relay feature could not be disabled because the VPCS version is below 0.8b")
|
||||
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
if self.use_ubridge:
|
||||
# use the local UDP tunnel to uBridge instead
|
||||
self._create_local_udp_tunnel()
|
||||
nio = self._local_udp_tunnel[0]
|
||||
else:
|
||||
nio = self._ethernet_adapter.get_nio(0)
|
||||
if nio:
|
||||
|
||||
if isinstance(nio, NIOUDP):
|
||||
# UDP tunnel
|
||||
command.extend(["-s", str(nio.lport)]) # source UDP port
|
||||
@ -434,6 +572,7 @@ class VPCSVM(BaseNode):
|
||||
command.extend(["-t", nio.rhost]) # destination host
|
||||
|
||||
elif isinstance(nio, NIOTAP):
|
||||
# FIXME: remove old code
|
||||
# TAP interface
|
||||
command.extend(["-e"])
|
||||
command.extend(["-d", nio.tap_device])
|
||||
|
@ -122,11 +122,11 @@ class UDPLink(Link):
|
||||
|
||||
# use the local node first to save bandwidth
|
||||
for node in self._nodes:
|
||||
if node["node"].compute.id == "local" and node["node"].node_type not in ["qemu", "vpcs"]:
|
||||
if node["node"].compute.id == "local" and node["node"].node_type not in ["qemu"]:
|
||||
return node
|
||||
|
||||
for node in self._nodes:
|
||||
if node["node"].node_type not in ["qemu", "vpcs"]:
|
||||
if node["node"].node_type not in ["qemu"]:
|
||||
return node
|
||||
|
||||
raise aiohttp.web.HTTPConflict(text="Capture is not supported for this link")
|
||||
|
@ -15,10 +15,12 @@
|
||||
# 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
|
||||
from aiohttp.web import HTTPConflict
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.compute.vpcs import VPCS
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
|
||||
from gns3server.schemas.vpcs import (
|
||||
VPCS_CREATE_SCHEMA,
|
||||
@ -200,7 +202,7 @@ class VPCSHandler:
|
||||
if nio_type not in ("nio_udp", "nio_tap"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = vpcs_manager.create_nio(vm.vpcs_path, request.json)
|
||||
vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
yield from vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@ -222,5 +224,51 @@ class VPCSHandler:
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
vm.port_remove_nio_binding(int(request.match_info["port_number"]))
|
||||
yield from vm.port_remove_nio_binding(int(request.match_info["port_number"]))
|
||||
response.set_status(204)
|
||||
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
},
|
||||
description="Start a packet capture on a VPCS instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
def start_capture(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"])
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
yield from vm.start_capture(port_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to stop a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
},
|
||||
description="Stop a packet capture on a VPCS instance")
|
||||
def stop_capture(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"])
|
||||
yield from vm.stop_capture(port_number)
|
||||
response.set_status(204)
|
@ -17,7 +17,6 @@
|
||||
|
||||
import json
|
||||
import jsonschema
|
||||
import asyncio
|
||||
import aiohttp.web
|
||||
import asyncio
|
||||
import logging
|
||||
|
Loading…
Reference in New Issue
Block a user