mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Use uBridge for Qemu connections. Ref #267.
Handle packet captures for VPCS & Qemu nodes. Fixes #548.
This commit is contained in:
parent
4eb03b5a99
commit
04022677bd
@ -25,9 +25,11 @@ import tempfile
|
|||||||
import psutil
|
import psutil
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
from ..compute.port_manager import PortManager
|
||||||
from ..utils.asyncio import wait_run_in_executor
|
from ..utils.asyncio import wait_run_in_executor
|
||||||
from ..ubridge.hypervisor import Hypervisor
|
from ..ubridge.hypervisor import Hypervisor
|
||||||
from ..ubridge.ubridge_error import UbridgeError
|
from ..ubridge.ubridge_error import UbridgeError
|
||||||
|
from .nios.nio_udp import NIOUDP
|
||||||
from .error import NodeError
|
from .error import NodeError
|
||||||
|
|
||||||
|
|
||||||
@ -491,6 +493,58 @@ class BaseNode:
|
|||||||
if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running():
|
if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running():
|
||||||
yield from self._ubridge_hypervisor.stop()
|
yield from self._ubridge_hypervisor.stop()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def _add_ubridge_udp_connection(self, bridge_name, source_nio, destination_nio):
|
||||||
|
"""
|
||||||
|
Creates a connection in uBridge.
|
||||||
|
|
||||||
|
:param bridge_name: bridge name in uBridge
|
||||||
|
:param source_nio: source NIO instance
|
||||||
|
:param destination_nio: destination NIO instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
yield from self._ubridge_send("bridge create {name}".format(name=bridge_name))
|
||||||
|
|
||||||
|
if not isinstance(destination_nio, NIOUDP):
|
||||||
|
raise NodeError("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))
|
||||||
|
|
||||||
|
def _create_local_udp_tunnel(self):
|
||||||
|
"""
|
||||||
|
Creates a local UDP tunnel (pair of 2 NIOs, one for each direction)
|
||||||
|
|
||||||
|
:returns: source NIO and destination NIO.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
log.info("{module}: '{name}' [{id}]:local UDP tunnel created between port {port1} and {port2}".format(module=self.manager.module_name,
|
||||||
|
name=self.name,
|
||||||
|
id=self.id,
|
||||||
|
port1=lport,
|
||||||
|
port2=rport))
|
||||||
|
return source_nio, destination_nio
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hw_virtualization(self):
|
def hw_virtualization(self):
|
||||||
"""
|
"""
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
Docker server module.
|
Docker server module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
@ -48,7 +48,8 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DockerVM(BaseNode):
|
class DockerVM(BaseNode):
|
||||||
"""Docker container implementation.
|
"""
|
||||||
|
Docker container implementation.
|
||||||
|
|
||||||
:param name: Docker container name
|
:param name: Docker container name
|
||||||
:param node_id: Node identifier
|
:param node_id: Node identifier
|
||||||
@ -63,13 +64,13 @@ class DockerVM(BaseNode):
|
|||||||
:param console_http_path: Url part with the path of the web interface
|
:param console_http_path: Url part with the path of the web interface
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, node_id, project, manager, image,
|
def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None,
|
||||||
console=None, aux=None, start_command=None,
|
adapters=None, environment=None, console_type="telnet", console_resolution="1024x768",
|
||||||
adapters=None, environment=None, console_type="telnet",
|
console_http_port=80, console_http_path="/"):
|
||||||
console_resolution="1024x768", console_http_port=80, console_http_path="/"):
|
|
||||||
super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type)
|
super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type)
|
||||||
|
|
||||||
# If no version is specified force latest
|
# force the latest image if no version is specified
|
||||||
if ":" not in image:
|
if ":" not in image:
|
||||||
image = "{}:latest".format(image)
|
image = "{}:latest".format(image)
|
||||||
self._image = image
|
self._image = image
|
||||||
@ -91,11 +92,9 @@ class DockerVM(BaseNode):
|
|||||||
else:
|
else:
|
||||||
self.adapters = adapters
|
self.adapters = adapters
|
||||||
|
|
||||||
log.debug(
|
log.debug("{module}: {name} [{image}] initialized.".format(module=self.manager.module_name,
|
||||||
"{module}: {name} [{image}] initialized.".format(
|
name=self.name,
|
||||||
module=self.manager.module_name,
|
image=self._image))
|
||||||
name=self.name,
|
|
||||||
image=self._image))
|
|
||||||
|
|
||||||
def __json__(self):
|
def __json__(self):
|
||||||
return {
|
return {
|
||||||
@ -401,14 +400,13 @@ class DockerVM(BaseNode):
|
|||||||
for volume in self._volumes:
|
for volume in self._volumes:
|
||||||
log.debug("Docker container '{name}' [{image}] fix ownership on {path}".format(
|
log.debug("Docker container '{name}' [{image}] fix ownership on {path}".format(
|
||||||
name=self._name, image=self._image, path=volume))
|
name=self._name, image=self._image, path=volume))
|
||||||
process = yield from asyncio.subprocess.create_subprocess_exec(
|
process = yield from asyncio.subprocess.create_subprocess_exec("docker",
|
||||||
"docker",
|
"exec",
|
||||||
"exec",
|
self._cid,
|
||||||
self._cid,
|
"/gns3/bin/busybox",
|
||||||
"/gns3/bin/busybox",
|
"sh",
|
||||||
"sh",
|
"-c",
|
||||||
"-c",
|
"(/gns3/bin/busybox find \"{path}\" -depth -print0 | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c '%a:%u:%g:%n' > \"{path}/.gns3_perms\") && /gns3/bin/busybox chmod -R u+rX \"{path}\" && /gns3/bin/busybox chown {uid}:{gid} -R \"{path}\"".format(uid=os.getuid(), gid=os.getgid(), path=volume))
|
||||||
"(/gns3/bin/busybox find \"{path}\" -depth -print0 | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c '%a:%u:%g:%n' > \"{path}/.gns3_perms\") && /gns3/bin/busybox chmod -R u+rX \"{path}\" && /gns3/bin/busybox chown {uid}:{gid} -R \"{path}\"".format(uid=os.getuid(), gid=os.getgid(), path=volume))
|
|
||||||
yield from process.wait()
|
yield from process.wait()
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -614,10 +612,11 @@ class DockerVM(BaseNode):
|
|||||||
"""
|
"""
|
||||||
Creates a connection in uBridge.
|
Creates a connection in uBridge.
|
||||||
|
|
||||||
:param nio: NIO instance or None if it's a dummu interface (if an interface is missing in ubridge you can't see it via ifconfig in the container)
|
:param nio: NIO instance or None if it's a dummy interface (if an interface is missing in ubridge you can't see it via ifconfig in the container)
|
||||||
:param adapter_number: adapter number
|
:param adapter_number: adapter number
|
||||||
:param namespace: Container namespace (pid)
|
:param namespace: Container namespace (pid)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
adapter = self._ethernet_adapters[adapter_number]
|
adapter = self._ethernet_adapters[adapter_number]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
@ -670,8 +669,7 @@ class DockerVM(BaseNode):
|
|||||||
adapter = self._ethernet_adapters[adapter_number]
|
adapter = self._ethernet_adapters[adapter_number]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield from self._ubridge_send("bridge delete bridge{name}".format(
|
yield from self._ubridge_send("bridge delete bridge{name}".format(name=adapter_number))
|
||||||
name=adapter_number))
|
|
||||||
except UbridgeError as e:
|
except UbridgeError as e:
|
||||||
log.debug(str(e))
|
log.debug(str(e))
|
||||||
try:
|
try:
|
||||||
|
@ -75,6 +75,7 @@ class QemuVM(BaseNode):
|
|||||||
self._monitor = None
|
self._monitor = None
|
||||||
self._stdout_file = ""
|
self._stdout_file = ""
|
||||||
self._execute_lock = asyncio.Lock()
|
self._execute_lock = asyncio.Lock()
|
||||||
|
self._local_udp_tunnels = {}
|
||||||
|
|
||||||
# QEMU VM settings
|
# QEMU VM settings
|
||||||
if qemu_path:
|
if qemu_path:
|
||||||
@ -882,8 +883,17 @@ class QemuVM(BaseNode):
|
|||||||
stdout=fd,
|
stdout=fd,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
cwd=self.working_dir)
|
cwd=self.working_dir)
|
||||||
log.info('QEMU VM "{}" started PID={}'.format(self._name, self._process.pid))
|
|
||||||
|
|
||||||
|
if self.use_ubridge:
|
||||||
|
yield from self._start_ubridge()
|
||||||
|
for adapter_number, adapter in enumerate(self._ethernet_adapters):
|
||||||
|
nio = adapter.get_nio(0)
|
||||||
|
if nio:
|
||||||
|
yield from self._add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number),
|
||||||
|
self._local_udp_tunnels[adapter_number][1],
|
||||||
|
nio)
|
||||||
|
|
||||||
|
log.info('QEMU VM "{}" started PID={}'.format(self._name, self._process.pid))
|
||||||
self.status = "started"
|
self.status = "started"
|
||||||
monitor_process(self._process, self._termination_callback)
|
monitor_process(self._process, self._termination_callback)
|
||||||
except (OSError, subprocess.SubprocessError, UnicodeEncodeError) as e:
|
except (OSError, subprocess.SubprocessError, UnicodeEncodeError) as e:
|
||||||
@ -919,6 +929,7 @@ class QemuVM(BaseNode):
|
|||||||
Stops this QEMU VM.
|
Stops this QEMU VM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
yield from self._stop_ubridge()
|
||||||
with (yield from self._execute_lock):
|
with (yield from self._execute_lock):
|
||||||
# stop the QEMU process
|
# stop the QEMU process
|
||||||
self._hw_virtualization = False
|
self._hw_virtualization = False
|
||||||
@ -1003,6 +1014,11 @@ class QemuVM(BaseNode):
|
|||||||
if nio and isinstance(nio, NIOUDP):
|
if nio and 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)
|
||||||
|
|
||||||
|
for udp_tunnel in self._local_udp_tunnels.values():
|
||||||
|
self.manager.port_manager.release_udp_port(udp_tunnel[0].lport, self._project)
|
||||||
|
self.manager.port_manager.release_udp_port(udp_tunnel[1].lport, self._project)
|
||||||
|
self._local_udp_tunnels = {}
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _get_vm_status(self):
|
def _get_vm_status(self):
|
||||||
"""
|
"""
|
||||||
@ -1081,27 +1097,12 @@ class QemuVM(BaseNode):
|
|||||||
adapter_number=adapter_number))
|
adapter_number=adapter_number))
|
||||||
|
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
raise QemuError("Sorry, adding a link to a started Qemu VM is not supported.")
|
if self.ubridge:
|
||||||
# FIXME: does the code below work? very undocumented feature...
|
yield from self._add_ubridge_udp_connection("QEMU-{}-{}".format(self._id, adapter_number),
|
||||||
# dynamically configure an UDP tunnel on the QEMU VM adapter
|
self._local_udp_tunnels[adapter_number][1],
|
||||||
# if nio and isinstance(nio, NIOUDP):
|
nio)
|
||||||
# if self._legacy_networking:
|
else:
|
||||||
# yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number))
|
raise QemuError("Sorry, adding a link to a started Qemu VM is not supported without using uBridge.")
|
||||||
# yield from self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number,
|
|
||||||
# adapter_number,
|
|
||||||
# nio.lport,
|
|
||||||
# nio.rport,
|
|
||||||
# nio.rhost))
|
|
||||||
# else:
|
|
||||||
# # Apparently there is a bug in Qemu...
|
|
||||||
# # netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device
|
|
||||||
# # netdev_del id -- remove host network device
|
|
||||||
# yield from self._control_vm("netdev_del gns3-{}".format(adapter_number))
|
|
||||||
# yield from self._control_vm("netdev_add socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
|
|
||||||
# nio.rhost,
|
|
||||||
# nio.rport,
|
|
||||||
# self._host,
|
|
||||||
# nio.lport))
|
|
||||||
|
|
||||||
adapter.add_nio(0, nio)
|
adapter.add_nio(0, nio)
|
||||||
log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name,
|
log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name,
|
||||||
@ -1126,21 +1127,83 @@ class QemuVM(BaseNode):
|
|||||||
adapter_number=adapter_number))
|
adapter_number=adapter_number))
|
||||||
|
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
# FIXME: does the code below work? very undocumented feature...
|
if self.ubridge:
|
||||||
# dynamically disable the QEMU VM adapter
|
yield from self._ubridge_send("bridge delete {name}".format(name="QEMU-{}-{}".format(self._id, adapter_number)))
|
||||||
yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number))
|
else:
|
||||||
yield from self._control_vm("host_net_add user vlan={},name=gns3-{}".format(adapter_number, adapter_number))
|
raise QemuError("Sorry, removing a link to a started Qemu VM is not supported without using uBridge.")
|
||||||
|
|
||||||
nio = adapter.get_nio(0)
|
nio = adapter.get_nio(0)
|
||||||
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)
|
||||||
adapter.remove_nio(0)
|
adapter.remove_nio(0)
|
||||||
|
|
||||||
log.info('QEMU VM "{name}" [{id}]: {nio} removed from adapter {adapter_number}'.format(name=self._name,
|
log.info('QEMU VM "{name}" [{id}]: {nio} removed from adapter {adapter_number}'.format(name=self._name,
|
||||||
id=self._id,
|
id=self._id,
|
||||||
nio=nio,
|
nio=nio,
|
||||||
adapter_number=adapter_number))
|
adapter_number=adapter_number))
|
||||||
return nio
|
return nio
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
if not self.use_ubridge:
|
||||||
|
raise QemuError("uBridge must be enabled in order to start packet capture")
|
||||||
|
|
||||||
|
nio = adapter.get_nio(0)
|
||||||
|
|
||||||
|
if not nio:
|
||||||
|
raise QemuError("Adapter {} is not connected".format(adapter_number))
|
||||||
|
|
||||||
|
if nio.capturing:
|
||||||
|
raise QemuError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
|
||||||
|
|
||||||
|
nio.startPacketCapture(output_file)
|
||||||
|
|
||||||
|
if self.is_running() and self.ubridge:
|
||||||
|
yield from self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name="QEMU-{}-{}".format(self._id, adapter_number),
|
||||||
|
output_file=output_file))
|
||||||
|
|
||||||
|
log.info("QEMU VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name,
|
||||||
|
id=self.id,
|
||||||
|
adapter_number=adapter_number))
|
||||||
|
|
||||||
|
def stop_capture(self, adapter_number):
|
||||||
|
"""
|
||||||
|
Stops a packet capture.
|
||||||
|
|
||||||
|
: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.stopPacketCapture()
|
||||||
|
|
||||||
|
if self.is_running() and self.ubridge:
|
||||||
|
yield from self._ubridge_send('bridge stop_capture {name}'.format(name="QEMU-{}-{}".format(self._id, adapter_number)))
|
||||||
|
|
||||||
|
log.info("QEMU VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
|
||||||
|
id=self.id,
|
||||||
|
adapter_number=adapter_number))
|
||||||
@property
|
@property
|
||||||
def started(self):
|
def started(self):
|
||||||
"""
|
"""
|
||||||
@ -1332,7 +1395,14 @@ class QemuVM(BaseNode):
|
|||||||
|
|
||||||
for adapter_number, adapter in enumerate(self._ethernet_adapters):
|
for adapter_number, adapter in enumerate(self._ethernet_adapters):
|
||||||
mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
|
mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
|
||||||
nio = adapter.get_nio(0)
|
|
||||||
|
if self.use_ubridge:
|
||||||
|
# use a local UDP tunnel to connect to uBridge instead
|
||||||
|
if adapter_number not in self._local_udp_tunnels:
|
||||||
|
self._local_udp_tunnels[adapter_number] = self._create_local_udp_tunnel()
|
||||||
|
nio = self._local_udp_tunnels[adapter_number][0]
|
||||||
|
else:
|
||||||
|
nio = adapter.get_nio(0)
|
||||||
if self._legacy_networking:
|
if self._legacy_networking:
|
||||||
# legacy QEMU networking syntax (-net)
|
# legacy QEMU networking syntax (-net)
|
||||||
if nio:
|
if nio:
|
||||||
|
@ -298,8 +298,7 @@ class VMwareVM(BaseNode):
|
|||||||
yield from self._ubridge_send("bridge create {name}".format(name=vnet))
|
yield from self._ubridge_send("bridge create {name}".format(name=vnet))
|
||||||
vmnet_interface = os.path.basename(self._vmx_pairs[vnet])
|
vmnet_interface = os.path.basename(self._vmx_pairs[vnet])
|
||||||
if sys.platform.startswith("linux"):
|
if sys.platform.startswith("linux"):
|
||||||
yield from self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=vnet,
|
yield from self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=vnet, interface=vmnet_interface))
|
||||||
interface=vmnet_interface))
|
|
||||||
elif sys.platform.startswith("win"):
|
elif sys.platform.startswith("win"):
|
||||||
windows_interfaces = interfaces()
|
windows_interfaces = interfaces()
|
||||||
npf = None
|
npf = None
|
||||||
@ -312,34 +311,30 @@ class VMwareVM(BaseNode):
|
|||||||
npf = interface["id"]
|
npf = interface["id"]
|
||||||
source_mac = interface["mac_address"]
|
source_mac = interface["mac_address"]
|
||||||
if npf:
|
if npf:
|
||||||
yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet,
|
yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet, interface=npf))
|
||||||
interface=npf))
|
|
||||||
else:
|
else:
|
||||||
raise VMwareError("Could not find NPF id for VMnet interface {}".format(vmnet_interface))
|
raise VMwareError("Could not find NPF id for VMnet interface {}".format(vmnet_interface))
|
||||||
|
|
||||||
if block_host_traffic:
|
if block_host_traffic:
|
||||||
if source_mac:
|
if source_mac:
|
||||||
yield from self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=vnet,
|
yield from self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=vnet, mac=source_mac))
|
||||||
mac=source_mac))
|
|
||||||
else:
|
else:
|
||||||
log.warn("Could not block host network traffic on {} (no MAC address found)".format(vmnet_interface))
|
log.warn("Could not block host network traffic on {} (no MAC address found)".format(vmnet_interface))
|
||||||
|
|
||||||
elif sys.platform.startswith("darwin"):
|
elif sys.platform.startswith("darwin"):
|
||||||
yield from self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=vnet,
|
yield from self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=vnet, interface=vmnet_interface))
|
||||||
interface=vmnet_interface))
|
|
||||||
else:
|
else:
|
||||||
yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet,
|
yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet,
|
||||||
interface=vmnet_interface))
|
interface=vmnet_interface))
|
||||||
|
|
||||||
if isinstance(nio, NIOUDP):
|
if isinstance(nio, NIOUDP):
|
||||||
yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=vnet,
|
yield from self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=vnet,
|
||||||
lport=nio.lport,
|
lport=nio.lport,
|
||||||
rhost=nio.rhost,
|
rhost=nio.rhost,
|
||||||
rport=nio.rport))
|
rport=nio.rport))
|
||||||
|
|
||||||
if nio.capturing:
|
if nio.capturing:
|
||||||
yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=vnet,
|
yield from self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(name=vnet, pcap_file=nio.pcap_output_file))
|
||||||
pcap_file=nio.pcap_output_file))
|
|
||||||
|
|
||||||
yield from self._ubridge_send('bridge start {name}'.format(name=vnet))
|
yield from self._ubridge_send('bridge start {name}'.format(name=vnet))
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ from gns3server.utils.asyncio import wait_for_process_termination
|
|||||||
from gns3server.utils.asyncio import monitor_process
|
from gns3server.utils.asyncio import monitor_process
|
||||||
from gns3server.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
|
||||||
@ -259,7 +258,7 @@ class VPCSVM(BaseNode):
|
|||||||
if self.use_ubridge:
|
if self.use_ubridge:
|
||||||
yield from self._start_ubridge()
|
yield from self._start_ubridge()
|
||||||
if nio:
|
if nio:
|
||||||
yield from self._add_ubridge_connection(self._local_udp_tunnel[1], nio)
|
yield from self._add_ubridge_udp_connection("VPCS-{}".format(self._id), 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
|
||||||
@ -358,37 +357,6 @@ 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
|
@asyncio.coroutine
|
||||||
def port_add_nio_binding(self, port_number, nio):
|
def port_add_nio_binding(self, port_number, nio):
|
||||||
"""
|
"""
|
||||||
@ -408,25 +376,10 @@ class VPCSVM(BaseNode):
|
|||||||
nio=nio,
|
nio=nio,
|
||||||
port_number=port_number))
|
port_number=port_number))
|
||||||
if self._started and self.ubridge:
|
if self._started and self.ubridge:
|
||||||
yield from self._add_ubridge_connection(self._local_udp_tunnel[1], nio)
|
yield from self._add_ubridge_udp_connection("VPCS-{}".format(self._id), 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
|
@asyncio.coroutine
|
||||||
def port_remove_nio_binding(self, port_number):
|
def port_remove_nio_binding(self, port_number):
|
||||||
"""
|
"""
|
||||||
@ -460,7 +413,7 @@ class VPCSVM(BaseNode):
|
|||||||
"""
|
"""
|
||||||
Starts a packet capture.
|
Starts a packet capture.
|
||||||
|
|
||||||
:param port_number: adapter number
|
:param port_number: port number
|
||||||
:param output_file: PCAP destination file for the capture
|
:param output_file: PCAP destination file for the capture
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -489,6 +442,7 @@ class VPCSVM(BaseNode):
|
|||||||
id=self.id,
|
id=self.id,
|
||||||
port_number=port_number))
|
port_number=port_number))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def stop_capture(self, port_number):
|
def stop_capture(self, port_number):
|
||||||
"""
|
"""
|
||||||
Stops a packet capture.
|
Stops a packet capture.
|
||||||
@ -562,7 +516,8 @@ class VPCSVM(BaseNode):
|
|||||||
|
|
||||||
if self.use_ubridge:
|
if self.use_ubridge:
|
||||||
# use the local UDP tunnel to uBridge instead
|
# use the local UDP tunnel to uBridge instead
|
||||||
self._create_local_udp_tunnel()
|
if not self._local_udp_tunnel:
|
||||||
|
self._local_udp_tunnel = self._create_local_udp_tunnel()
|
||||||
nio = self._local_udp_tunnel[0]
|
nio = self._local_udp_tunnel[0]
|
||||||
else:
|
else:
|
||||||
nio = self._ethernet_adapter.get_nio(0)
|
nio = self._ethernet_adapter.get_nio(0)
|
||||||
|
@ -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"]:
|
if node["node"].compute.id == "local" and node["node"].node_type not in [""]: # FIXME
|
||||||
return node
|
return node
|
||||||
|
|
||||||
for node in self._nodes:
|
for node in self._nodes:
|
||||||
if node["node"].node_type not in ["qemu"]:
|
if node["node"].node_type not in [""]: # FIXME
|
||||||
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")
|
||||||
|
@ -23,10 +23,14 @@ from aiohttp.web import HTTPConflict
|
|||||||
from gns3server.web.route import Route
|
from gns3server.web.route import Route
|
||||||
from gns3server.compute.project_manager import ProjectManager
|
from gns3server.compute.project_manager import ProjectManager
|
||||||
from gns3server.schemas.nio import NIO_SCHEMA
|
from gns3server.schemas.nio import NIO_SCHEMA
|
||||||
from gns3server.schemas.node import NODE_LIST_IMAGES_SCHEMA
|
|
||||||
from gns3server.compute.qemu import Qemu
|
from gns3server.compute.qemu import Qemu
|
||||||
from gns3server.config import Config
|
from gns3server.config import Config
|
||||||
|
|
||||||
|
from gns3server.schemas.node import (
|
||||||
|
NODE_LIST_IMAGES_SCHEMA,
|
||||||
|
NODE_CAPTURE_SCHEMA
|
||||||
|
)
|
||||||
|
|
||||||
from gns3server.schemas.qemu import (
|
from gns3server.schemas.qemu import (
|
||||||
QEMU_CREATE_SCHEMA,
|
QEMU_CREATE_SCHEMA,
|
||||||
QEMU_UPDATE_SCHEMA,
|
QEMU_UPDATE_SCHEMA,
|
||||||
@ -288,6 +292,53 @@ class QEMUHandler:
|
|||||||
yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
|
@Route.post(
|
||||||
|
r"/projects/{project_id}/qemu/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 (always 0)"
|
||||||
|
},
|
||||||
|
status_codes={
|
||||||
|
200: "Capture started",
|
||||||
|
400: "Invalid request",
|
||||||
|
404: "Instance doesn't exist",
|
||||||
|
},
|
||||||
|
description="Start a packet capture on a Qemu VM instance",
|
||||||
|
input=NODE_CAPTURE_SCHEMA)
|
||||||
|
def start_capture(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"])
|
||||||
|
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||||
|
yield from vm.start_capture(adapter_number, pcap_file_path)
|
||||||
|
response.json({"pcap_file_path": pcap_file_path})
|
||||||
|
|
||||||
|
|
||||||
|
@Route.post(
|
||||||
|
r"/projects/{project_id}/qemu/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 (always 0)"
|
||||||
|
},
|
||||||
|
status_codes={
|
||||||
|
204: "Capture stopped",
|
||||||
|
400: "Invalid request",
|
||||||
|
404: "Instance doesn't exist",
|
||||||
|
},
|
||||||
|
description="Stop a packet capture on a Qemu VM instance")
|
||||||
|
def stop_capture(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"])
|
||||||
|
yield from vm.stop_capture(adapter_number)
|
||||||
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.get(
|
@Route.get(
|
||||||
r"/qemu/binaries",
|
r"/qemu/binaries",
|
||||||
status_codes={
|
status_codes={
|
||||||
|
@ -19,8 +19,8 @@ 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.schemas.node import NODE_CAPTURE_SCHEMA
|
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||||
|
from gns3server.compute.vpcs import VPCS
|
||||||
|
|
||||||
from gns3server.schemas.vpcs import (
|
from gns3server.schemas.vpcs import (
|
||||||
VPCS_CREATE_SCHEMA,
|
VPCS_CREATE_SCHEMA,
|
||||||
@ -227,7 +227,6 @@ class VPCSHandler:
|
|||||||
yield from 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(
|
@Route.post(
|
||||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||||
parameters={
|
parameters={
|
||||||
@ -244,6 +243,7 @@ class VPCSHandler:
|
|||||||
description="Start a packet capture on a VPCS instance",
|
description="Start a packet capture on a VPCS instance",
|
||||||
input=NODE_CAPTURE_SCHEMA)
|
input=NODE_CAPTURE_SCHEMA)
|
||||||
def start_capture(request, response):
|
def start_capture(request, response):
|
||||||
|
|
||||||
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"])
|
||||||
port_number = int(request.match_info["port_number"])
|
port_number = int(request.match_info["port_number"])
|
||||||
@ -267,8 +267,9 @@ class VPCSHandler:
|
|||||||
},
|
},
|
||||||
description="Stop a packet capture on a VPCS instance")
|
description="Stop a packet capture on a VPCS instance")
|
||||||
def stop_capture(request, response):
|
def stop_capture(request, response):
|
||||||
|
|
||||||
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"])
|
||||||
port_number = int(request.match_info["port_number"])
|
port_number = int(request.match_info["port_number"])
|
||||||
yield from vm.stop_capture(port_number)
|
yield from vm.stop_capture(port_number)
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
Loading…
Reference in New Issue
Block a user