mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-18 00:50:09 +03:00
Merge pull request #2990 from yueguobin/fix/cloud-hide-kernel-datapath-interfaces
Hide kernel-datapath interfaces from the cloud node
This commit is contained in:
commit
149549e029
@ -21,6 +21,7 @@ import sys
|
||||
import gns3server.utils.asyncio
|
||||
import gns3server.utils.interfaces
|
||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||
from gns3server.utils.kernel_anchor import is_kernel_datapath_interface
|
||||
|
||||
from ...base_node import BaseNode
|
||||
from ...error import NodeError
|
||||
@ -52,7 +53,9 @@ class Cloud(BaseNode):
|
||||
if not ports or len(ports) == 0:
|
||||
self._ports_mapping = []
|
||||
for interface in self._interfaces():
|
||||
if not interface["special"]:
|
||||
# kernel-datapath interfaces (node anchors, cascade veths, VXLAN
|
||||
# devices) belong to their creating link or node, never the cloud
|
||||
if not interface["special"] and not is_kernel_datapath_interface(interface["name"]):
|
||||
self._ports_mapping.append(
|
||||
{
|
||||
"interface": interface["name"],
|
||||
@ -80,8 +83,10 @@ class Cloud(BaseNode):
|
||||
host_interfaces = []
|
||||
network_interfaces = gns3server.utils.interfaces.interfaces()
|
||||
for interface in network_interfaces:
|
||||
# Hide GNS3 internal bridges (e.g. EthernetSwitch kernel bridges)
|
||||
if interface["name"].lower().startswith("gns3"):
|
||||
# Hide GNS3 internal interfaces: kernel bridges ("gns3*" names) and
|
||||
# the kernel-datapath devices (node anchors, cascade veths, VXLAN
|
||||
# devices) a link or node of some project owns
|
||||
if interface["name"].lower().startswith("gns3") or is_kernel_datapath_interface(interface["name"]):
|
||||
continue
|
||||
host_interfaces.append(
|
||||
{
|
||||
|
||||
@ -36,6 +36,7 @@ is what makes links to stopped nodes wireable.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -113,3 +114,36 @@ def kernel_cascade_names(link_id):
|
||||
|
||||
stem = str(link_id).replace("-", "")[:10]
|
||||
return f"gs{stem}0", f"gs{stem}1"
|
||||
|
||||
|
||||
# Host-side interfaces the kernel datapath owns outright, recognised by
|
||||
# exact shape — the prefix plus its fixed-length id-derived tail — never by
|
||||
# bare prefix, so a user interface that merely starts with "gv" or "gs" is
|
||||
# not swallowed. Consumers keep these out of human-facing interface lists:
|
||||
# each belongs to the link or node that created it for as long as the
|
||||
# project exists (the names are deterministic, valid whether it runs or
|
||||
# not), so nothing else may bridge to it.
|
||||
_MANAGED_ANCHOR_PREFIXES = frozenset(ANCHOR_PREFIX.values())
|
||||
_MANAGED_ANCHOR_SUFFIX_SHAPE = re.compile(r"[0-9a-f]{8}e\d{1,2}p\d{1,2}")
|
||||
_MANAGED_CASCADE_SHAPE = re.compile(r"gs[0-9a-f]{10}[01]")
|
||||
# a cross-compute VXLAN device: "gvl" + 8 hex of the link id + 4 hex of the
|
||||
# endpoint node id (the cross-compute VXLAN naming keeps its devices out of
|
||||
# the anchor and "gns3" namespaces the same deterministic way)
|
||||
_MANAGED_VXLAN_SHAPE = re.compile(r"gvl[0-9a-f]{12}")
|
||||
|
||||
|
||||
def is_kernel_datapath_interface(name):
|
||||
"""
|
||||
Whether *name* is a host interface the kernel datapath manages by name —
|
||||
a node anchor, a switch cascade veth end, or a cross-compute VXLAN
|
||||
device. True for exactly the names the naming contract above can
|
||||
produce: the cloud node (and any other interface picker) uses this to
|
||||
refuse offering them, since bridging to one would race whatever link or
|
||||
node already owns it.
|
||||
"""
|
||||
|
||||
# "gvl" begins with the docker anchor prefix "gv", so an anchor miss must
|
||||
# still fall through to the VXLAN shape
|
||||
if name[:2] in _MANAGED_ANCHOR_PREFIXES and _MANAGED_ANCHOR_SUFFIX_SHAPE.fullmatch(name[2:]):
|
||||
return True
|
||||
return _MANAGED_CASCADE_SHAPE.fullmatch(name) is not None or _MANAGED_VXLAN_SHAPE.fullmatch(name) is not None
|
||||
|
||||
@ -156,6 +156,36 @@ async def test_json_without_ports(on_gns3vm, compute_project, manager):
|
||||
}
|
||||
|
||||
|
||||
async def test_kernel_datapath_interfaces_are_hidden(linux_platform, compute_project, manager):
|
||||
"""
|
||||
Kernel-datapath interfaces (node anchors, cascade veths, VXLAN devices)
|
||||
are neither pre-filled as cloud ports nor listed as host interfaces,
|
||||
while look-alike user interfaces survive both.
|
||||
"""
|
||||
|
||||
host_interfaces = [
|
||||
{"name": "eth0", "special": False, "type": "ethernet"},
|
||||
{"name": "gv00010203e0p0", "special": False, "type": "ethernet"}, # docker anchor
|
||||
{"name": "gq00010203e0p0", "special": False, "type": "ethernet"}, # qemu anchor
|
||||
{"name": "gi00010203e0p0", "special": False, "type": "ethernet"}, # iou anchor
|
||||
{"name": "gd00010203e0p0", "special": False, "type": "ethernet"}, # dynamips anchor
|
||||
{"name": "gx00010203e0p0", "special": False, "type": "ethernet"}, # iol runner anchor
|
||||
{"name": "gp00010203e0p0", "special": False, "type": "ethernet"}, # vpcs anchor
|
||||
{"name": "gs1a2b3c4d5e0", "special": False, "type": "ethernet"}, # cascade veth end
|
||||
{"name": "gvl1a2b3c4d0001", "special": False, "type": "ethernet"}, # VXLAN device
|
||||
{"name": "gv0", "special": False, "type": "ethernet"}, # look-alike user interface
|
||||
{"name": "gs1a2b3c4d5e", "special": False, "type": "ethernet"}, # look-alike (no side digit)
|
||||
{"name": "gvl1a2b3c4d5e", "special": False, "type": "ethernet"}, # look-alike (short tail)
|
||||
]
|
||||
|
||||
with patch("gns3server.utils.interfaces.interfaces", return_value=host_interfaces):
|
||||
cloud = Cloud("cloud1", str(uuid.uuid4()), compute_project, manager, ports=None)
|
||||
interfaces = [interface["name"] for interface in cloud.asdict()["interfaces"]]
|
||||
|
||||
assert [port["interface"] for port in cloud.ports_mapping] == ["eth0", "gv0", "gs1a2b3c4d5e", "gvl1a2b3c4d5e"]
|
||||
assert interfaces == ["eth0", "gv0", "gs1a2b3c4d5e", "gvl1a2b3c4d5e"]
|
||||
|
||||
|
||||
async def test_update_port_mappings(on_gns3vm, compute_project):
|
||||
"""
|
||||
We don't allow an empty interface in the middle of port list
|
||||
|
||||
@ -21,7 +21,13 @@ Ethernet-switch link), plus the cascade veth naming for switch-to-switch
|
||||
links.
|
||||
"""
|
||||
|
||||
from gns3server.utils.kernel_anchor import anchor_suffix, kernel_anchor_name, kernel_anchor_type, kernel_cascade_names
|
||||
from gns3server.utils.kernel_anchor import (
|
||||
anchor_suffix,
|
||||
is_kernel_datapath_interface,
|
||||
kernel_anchor_name,
|
||||
kernel_anchor_type,
|
||||
kernel_cascade_names,
|
||||
)
|
||||
|
||||
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
||||
LINK_ID = "1a2b3c4d-5e6f-4a5b-8c9d-0e1f2a3b4c5d"
|
||||
@ -86,3 +92,32 @@ def test_cascade_names_stay_out_of_the_node_anchor_namespace():
|
||||
# cascade end is never mistaken for another node's anchor
|
||||
for end in kernel_cascade_names(LINK_ID):
|
||||
assert not any(end.startswith(prefix) for prefix in ("gv", "gq", "gi", "gd", "gx"))
|
||||
|
||||
|
||||
def test_every_name_the_contract_produces_is_a_managed_interface():
|
||||
for node_type in ("docker", "qemu", "iou", "dynamips", "iol_docker", "vpcs"):
|
||||
assert is_kernel_datapath_interface(kernel_anchor_name(node_type, NODE_ID, 0, 0))
|
||||
for end in kernel_cascade_names(LINK_ID):
|
||||
assert is_kernel_datapath_interface(end)
|
||||
# a cross-compute VXLAN device: "gvl" + 8 hex of the link id + 4 hex of the node id
|
||||
assert is_kernel_datapath_interface("gvl" + LINK_ID.replace("-", "")[:8] + NODE_ID.replace("-", "")[:4])
|
||||
|
||||
|
||||
def test_lookalike_interfaces_are_not_managed():
|
||||
# recognition is by exact shape, not bare prefix: user interfaces that
|
||||
# merely start with a managed prefix stay visible to the cloud node
|
||||
for name in (
|
||||
"eth0",
|
||||
"g",
|
||||
"gs",
|
||||
"gvl",
|
||||
"gv0",
|
||||
"gp1",
|
||||
"gv00010203e0p", # anchor without the port
|
||||
"gv00010203Xe0p0", # non-hex node id
|
||||
"gs1a2b3c4d5e", # cascade without the side digit
|
||||
"gs1a2b3c4d5e2", # side digit other than 0/1
|
||||
"gvl1a2b3c4d5e", # VXLAN with a short tail
|
||||
"GV00010203E0P0", # the contract only ever produces lowercase
|
||||
):
|
||||
assert not is_kernel_datapath_interface(name)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user