2
0
mirror of https://github.com/GNS3/gns3-server.git synced 2025-02-25 01:13:47 +02:00

Use uBridge for VPCS connections. Ref .

This commit is contained in:
grossmj 2016-06-22 19:40:46 -06:00
parent b456a363ca
commit 00da15e4af
7 changed files with 253 additions and 17 deletions
gns3server

View File

@ -48,7 +48,7 @@ class BaseNode:
:param allocate_aux: Boolean if true will allocate an aux console port :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._name = name
self._usage = "" self._usage = ""
@ -61,6 +61,7 @@ class BaseNode:
self._temporary_directory = None self._temporary_directory = None
self._hw_virtualization = False self._hw_virtualization = False
self._ubridge_hypervisor = None self._ubridge_hypervisor = None
self._use_ubridge = use_ubridge
self._closed = False self._closed = False
self._node_status = "stopped" self._node_status = "stopped"
self._command_line = "" self._command_line = ""
@ -271,10 +272,9 @@ class BaseNode:
if self._closed: if self._closed:
return False return False
log.info("{module}: '{name}' [{id}]: is closing".format( log.info("{module}: '{name}' [{id}]: is closing".format(module=self.manager.module_name,
module=self.manager.module_name, name=self.name,
name=self.name, id=self.id))
id=self.id))
if self._console: if self._console:
self._manager.port_manager.release_tcp_port(self._console, self._project) self._manager.port_manager.release_tcp_port(self._console, self._project)
@ -403,6 +403,36 @@ class BaseNode:
id=self.id, id=self.id,
console_type=console_type)) 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 @property
def ubridge_path(self): def ubridge_path(self):
""" """
@ -445,7 +475,8 @@ class BaseNode:
server_config = self._manager.config.get_section_config("Server") server_config = self._manager.config.get_section_config("Server")
server_host = server_config.get("host") 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)) log.info("Starting new uBridge hypervisor {}:{}".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
yield from self._ubridge_hypervisor.start() yield from self._ubridge_hypervisor.start()
log.info("Hypervisor {}:{} has successfully started".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port)) log.info("Hypervisor {}:{} has successfully started".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))

View File

@ -77,7 +77,6 @@ class DockerVM(BaseNode):
self._environment = environment self._environment = environment
self._cid = None self._cid = None
self._ethernet_adapters = [] self._ethernet_adapters = []
self._ubridge_hypervisor = None
self._temporary_directory = None self._temporary_directory = None
self._telnet_servers = [] self._telnet_servers = []
self._x11vnc_process = None self._x11vnc_process = None

View File

@ -240,6 +240,26 @@ class Project:
self._nodes.add(node) 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): def remove_node(self, node):
""" """
Removes a node from the project. Removes a node from the project.

View File

@ -28,16 +28,19 @@ import re
import asyncio import asyncio
import shutil import shutil
from ...utils.asyncio import wait_for_process_termination from gns3server.utils.asyncio import wait_for_process_termination
from ...utils.asyncio import monitor_process from gns3server.utils.asyncio import monitor_process
from ...utils.asyncio import subprocess_check_output from gns3server.utils.asyncio import subprocess_check_output
from gns3server.utils import parse_version from gns3server.utils import parse_version
from gns3server.compute.port_manager import PortManager
from .vpcs_error import VPCSError from .vpcs_error import VPCSError
from ..adapters.ethernet_adapter import EthernetAdapter from ..adapters.ethernet_adapter import EthernetAdapter
from ..nios.nio_udp import NIOUDP from ..nios.nio_udp import NIOUDP
from ..nios.nio_tap import NIOTAP from ..nios.nio_tap import NIOTAP
from ..base_node import BaseNode from ..base_node import BaseNode
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -63,6 +66,7 @@ class VPCSVM(BaseNode):
self._vpcs_stdout_file = "" self._vpcs_stdout_file = ""
self._vpcs_version = None self._vpcs_version = None
self._started = False self._started = False
self._local_udp_tunnel = None
# VPCS settings # VPCS settings
if startup_script is not None: if startup_script is not None:
@ -82,6 +86,13 @@ class VPCSVM(BaseNode):
if isinstance(nio, NIOUDP): if isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project) 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(): if self.is_running():
self._terminate_process() self._terminate_process()
@ -221,7 +232,8 @@ class VPCSVM(BaseNode):
yield from self._check_requirements() yield from self._check_requirements()
if not self.is_running(): 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") raise VPCSError("This VPCS instance must be connected in order to start")
command = self._build_command() command = self._build_command()
@ -240,6 +252,12 @@ class VPCSVM(BaseNode):
cwd=self.working_dir, cwd=self.working_dir,
creationflags=flags) creationflags=flags)
monitor_process(self._process, self._termination_callback) 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)) log.info("VPCS instance {} started PID={}".format(self.name, self._process.pid))
self._started = True self._started = True
self.status = "started" self.status = "started"
@ -268,6 +286,7 @@ class VPCSVM(BaseNode):
Stops the VPCS process. Stops the VPCS process.
""" """
yield from self._stop_ubridge()
if self.is_running(): if self.is_running():
self._terminate_process() self._terminate_process()
if self._process.returncode is None: if self._process.returncode is None:
@ -336,6 +355,38 @@ class VPCSVM(BaseNode):
return True return True
return False 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): def port_add_nio_binding(self, port_number, nio):
""" """
Adds a port NIO binding. Adds a port NIO binding.
@ -353,8 +404,27 @@ class VPCSVM(BaseNode):
id=self.id, id=self.id,
nio=nio, nio=nio,
port_number=port_number)) port_number=port_number))
if self._started and self.ubridge:
yield from self._add_ubridge_connection(self._local_udp_tunnel[1], nio)
return 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): def port_remove_nio_binding(self, port_number):
""" """
Removes a port NIO binding. Removes a port NIO binding.
@ -373,12 +443,74 @@ class VPCSVM(BaseNode):
self.manager.port_manager.release_udp_port(nio.lport, self._project) self.manager.port_manager.release_udp_port(nio.lport, self._project)
self._ethernet_adapter.remove_nio(port_number) 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, log.info('VPCS "{name}" [{id}]: {nio} removed from port {port_number}'.format(name=self._name,
id=self.id, id=self.id,
nio=nio, nio=nio,
port_number=port_number)) port_number=port_number))
return nio 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): def _build_command(self):
""" """
Command to start the VPCS process. Command to start the VPCS process.
@ -425,8 +557,14 @@ class VPCSVM(BaseNode):
else: else:
log.warn("The VPCS relay feature could not be disabled because the VPCS version is below 0.8b") 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 nio:
if isinstance(nio, NIOUDP): if isinstance(nio, NIOUDP):
# UDP tunnel # UDP tunnel
command.extend(["-s", str(nio.lport)]) # source UDP port command.extend(["-s", str(nio.lport)]) # source UDP port
@ -434,6 +572,7 @@ class VPCSVM(BaseNode):
command.extend(["-t", nio.rhost]) # destination host command.extend(["-t", nio.rhost]) # destination host
elif isinstance(nio, NIOTAP): elif isinstance(nio, NIOTAP):
# FIXME: remove old code
# TAP interface # TAP interface
command.extend(["-e"]) command.extend(["-e"])
command.extend(["-d", nio.tap_device]) command.extend(["-d", nio.tap_device])

View File

@ -122,11 +122,11 @@ class UDPLink(Link):
# use the local node first to save bandwidth # use the local node first to save bandwidth
for node in self._nodes: 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 return node
for node in self._nodes: for node in self._nodes:
if node["node"].node_type not in ["qemu", "vpcs"]: if node["node"].node_type not in ["qemu"]:
return node return node
raise aiohttp.web.HTTPConflict(text="Capture is not supported for this link") raise aiohttp.web.HTTPConflict(text="Capture is not supported for this link")

View File

@ -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 aiohttp.web import HTTPConflict from aiohttp.web import HTTPConflict
from gns3server.web.route import Route from gns3server.web.route import Route
from gns3server.schemas.nio import NIO_SCHEMA from gns3server.schemas.nio import NIO_SCHEMA
from gns3server.compute.vpcs import VPCS from gns3server.compute.vpcs import VPCS
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
from gns3server.schemas.vpcs import ( from gns3server.schemas.vpcs import (
VPCS_CREATE_SCHEMA, VPCS_CREATE_SCHEMA,
@ -200,7 +202,7 @@ class VPCSHandler:
if nio_type not in ("nio_udp", "nio_tap"): if nio_type not in ("nio_udp", "nio_tap"):
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
nio = vpcs_manager.create_nio(vm.vpcs_path, request.json) 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.set_status(201)
response.json(nio) response.json(nio)
@ -222,5 +224,51 @@ class VPCSHandler:
vpcs_manager = VPCS.instance() vpcs_manager = VPCS.instance()
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) 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) 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)

View File

@ -17,7 +17,6 @@
import json import json
import jsonschema import jsonschema
import asyncio
import aiohttp.web import aiohttp.web
import asyncio import asyncio
import logging import logging