From cf723962af1c3ac0e2773fab06f482766b172900 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Fri, 4 Nov 2016 17:32:16 +0100 Subject: [PATCH] Support bridge in cloud Nat take advantage of that and the code is more simple. Fix #761 --- gns3server/compute/builtin/nodes/cloud.py | 55 ++++++++++++++++------- gns3server/compute/builtin/nodes/nat.py | 13 +----- gns3server/utils/interfaces.py | 8 ++++ tests/compute/builtin/nodes/test_cloud.py | 29 ++++++++++++ tests/compute/builtin/nodes/test_nat.py | 20 +-------- 5 files changed, 80 insertions(+), 45 deletions(-) diff --git a/gns3server/compute/builtin/nodes/cloud.py b/gns3server/compute/builtin/nodes/cloud.py index 81d89e90..1a391644 100644 --- a/gns3server/compute/builtin/nodes/cloud.py +++ b/gns3server/compute/builtin/nodes/cloud.py @@ -210,23 +210,11 @@ class Cloud(BaseNode): raise NodeError("Interface '{}' could not be found on this system".format(port_info["interface"])) if sys.platform.startswith("linux"): - # use raw sockets on Linux - yield from self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=bridge_name, - interface=port_info["interface"])) + yield from self._add_linux_ethernet(port_info, bridge_name) + elif sys.platform.startswith("darwin"): + yield from self._add_osx_ethernet(port_info, bridge_name) else: - if sys.platform.startswith("darwin"): - # Wireless adapters are not well supported by the libpcap on OSX - if (yield from self._is_wifi_adapter_osx(port_info["interface"])): - raise NodeError("Connecting to a Wireless adapter is not supported on Mac OS") - if sys.platform.startswith("darwin") and port_info["interface"].startswith("vmnet"): - # Use a special NIO to connect to VMware vmnet interfaces on OSX (libpcap doesn't support them) - yield from self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=bridge_name, - interface=port_info["interface"])) - else: - if not gns3server.utils.interfaces.has_netmask(port_info["interface"]): - raise NodeError("Interface {} don't have a netmask".format(port_info["interface"])) - yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, - interface=port_info["interface"])) + yield from self._add_windows_ethernet(port_info, bridge_name) elif port_info["type"] == "tap": yield from self._ubridge_send('bridge add_nio_tap {name} "{interface}"'.format(name=bridge_name, interface=port_info["interface"])) @@ -243,6 +231,41 @@ class Cloud(BaseNode): yield from self._ubridge_send('bridge start {name}'.format(name=bridge_name)) + @asyncio.coroutine + def _add_linux_ethernet(self, port_info, bridge_name): + """ + Use raw sockets on Linux. + + If interface is a bridge we connect a tap to it + """ + interface = port_info["interface"] + if gns3server.utils.interfaces.is_interface_bridge(interface): + tap = "gns3tap{}-{}".format(Cloud._cloud_id, port_info["port_number"]) + yield from self._ubridge_send('bridge add_nio_tap "{name}" "{interface}"'.format(name=bridge_name, interface=tap)) + yield from self._ubridge_send('brctl addif "{interface}" "{tap}"'.format(tap=tap, interface=interface)) + else: + yield from self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=bridge_name, interface=interface)) + + @asyncio.coroutine + def _add_osx_ethernet(self, port_info, bridge_name): + # Wireless adapters are not well supported by the libpcap on OSX + if (yield from self._is_wifi_adapter_osx(port_info["interface"])): + raise NodeError("Connecting to a Wireless adapter is not supported on Mac OS") + if port_info["interface"].startswith("vmnet"): + # Use a special NIO to connect to VMware vmnet interfaces on OSX (libpcap doesn't support them) + yield from self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=bridge_name, + interface=port_info["interface"])) + return + if not gns3server.utils.interfaces.has_netmask(port_info["interface"]): + raise NodeError("Interface {} don't have a netmask".format(port_info["interface"])) + yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=port_info["interface"])) + + @asyncio.coroutine + def _add_windows_ethernet(self, port_info, bridge_name): + if not gns3server.utils.interfaces.has_netmask(port_info["interface"]): + raise NodeError("Interface {} don't have a netmask".format(port_info["interface"])) + yield from self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=port_info["interface"])) + @asyncio.coroutine def add_nio(self, nio, port_number): """ diff --git a/gns3server/compute/builtin/nodes/nat.py b/gns3server/compute/builtin/nodes/nat.py index 6d059270..2aea7a41 100644 --- a/gns3server/compute/builtin/nodes/nat.py +++ b/gns3server/compute/builtin/nodes/nat.py @@ -29,19 +29,15 @@ class Nat(Cloud): nat access to the outside """ - _nat_id = 0 - def __init__(self, *args, **kwargs): if "virbr0" not in [interface["name"] for interface in gns3server.utils.interfaces.interfaces()]: raise NodeError("virbr0 is missing. You need to install libvirt") - self._interface = "gns3nat{}".format(Nat._nat_id) - Nat._nat_id += 1 ports = [ { "name": "nat0", - "type": "tap", - "interface": self._interface, + "type": "ethernet", + "interface": "virbr0", "port_number": 0 } ] @@ -56,11 +52,6 @@ class Nat(Cloud): # It's not allowed to change it pass - @asyncio.coroutine - def add_nio(self, nio, port_number): - yield from super().add_nio(nio, port_number) - yield from self._ubridge_send('brctl addif virbr0 "{interface}"'.format(interface=self._interface)) - @classmethod def is_supported(self): return sys.platform.startswith("linux") diff --git a/gns3server/utils/interfaces.py b/gns3server/utils/interfaces.py index 758b329e..5ac0490a 100644 --- a/gns3server/utils/interfaces.py +++ b/gns3server/utils/interfaces.py @@ -16,6 +16,7 @@ # along with this program. If not, see . +import os import sys import aiohttp import socket @@ -163,6 +164,13 @@ def is_interface_up(interface): return True +def is_interface_bridge(interface): + """ + :returns: True if interface is a bridge + """ + return os.path.exists(os.path.join("/sys/class/net/", interface, "bridge")) + + def _check_windows_service(service_name): import pywintypes diff --git a/tests/compute/builtin/nodes/test_cloud.py b/tests/compute/builtin/nodes/test_cloud.py index a519be64..93c54105 100644 --- a/tests/compute/builtin/nodes/test_cloud.py +++ b/tests/compute/builtin/nodes/test_cloud.py @@ -152,3 +152,32 @@ def test_linux_ethernet_raw_add_nio(linux_platform, project, async_run, nio): call("bridge add_nio_linux_raw {}-0 \"eth0\"".format(cloud._id)), call("bridge start {}-0".format(cloud._id)), ]) + + +def test_linux_ethernet_raw_add_nio_bridge(linux_platform, project, async_run, nio): + """ + Bridge can't be connected directly to a cloud we use a tap in the middle + """ + ports = [ + { + "interface": "bridge0", + "name": "bridge0", + "port_number": 0, + "type": "ethernet" + } + ] + cloud = Cloud("cloud1", str(uuid.uuid4()), project, MagicMock(), ports=ports) + + with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud._ubridge_send") as ubridge_mock: + with patch("gns3server.compute.builtin.nodes.cloud.Cloud._interfaces", return_value=[{"name": "bridge0"}]): + with patch("gns3server.utils.interfaces.is_interface_bridge", return_value=True): + async_run(cloud.add_nio(nio, 0)) + + tap = "gns3tap{}-0".format(cloud._cloud_id) + ubridge_mock.assert_has_calls([ + call("bridge create {}-0".format(cloud._id)), + call("bridge add_nio_udp {}-0 4242 127.0.0.1 4343".format(cloud._id)), + call("bridge add_nio_tap \"{}-0\" \"{}\"".format(cloud._id, tap)), + call("brctl addif \"bridge0\" \"{}\"".format(tap)), + call("bridge start {}-0".format(cloud._id)), + ]) diff --git a/tests/compute/builtin/nodes/test_nat.py b/tests/compute/builtin/nodes/test_nat.py index ade56a00..18a92d16 100644 --- a/tests/compute/builtin/nodes/test_nat.py +++ b/tests/compute/builtin/nodes/test_nat.py @@ -24,12 +24,6 @@ from gns3server.compute.builtin.nodes.nat import Nat from gns3server.compute.vpcs import VPCS -def test_init(on_gns3vm, project): - nat1 = Nat("nat1", str(uuid.uuid4()), project, MagicMock()) - nat2 = Nat("nat2", str(uuid.uuid4()), project, MagicMock()) - assert nat1.ports_mapping[0]["interface"] != nat2.ports_mapping[0]["interface"] - - def test_json(on_gns3vm, project): nat = Nat("nat1", str(uuid.uuid4()), project, MagicMock()) assert nat.__json__() == { @@ -39,20 +33,10 @@ def test_json(on_gns3vm, project): "status": "started", "ports_mapping": [ { - "interface": nat._interface, + "interface": "virbr0", "name": "nat0", "port_number": 0, - "type": "tap" + "type": "ethernet" } ] } - - -def test_add_nio(on_gns3vm, project, async_run): - nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) - nat = Nat("nat1", str(uuid.uuid4()), project, MagicMock()) - with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.add_nio") as cloud_add_nio_mock: - with asyncio_patch("gns3server.compute.base_node.BaseNode._ubridge_send") as nat_ubridge_send_mock: - async_run(nat.add_nio(0, nio)) - assert cloud_add_nio_mock.called - nat_ubridge_send_mock.assert_called_with("brctl addif virbr0 \"{}\"".format(nat._interface))