gns3-server/tests/utils/test_kernel_anchor.py
YueGuobin c0568dc25f
fix: hide kernel-datapath interfaces from the cloud node
The cloud node offered the kernel datapath's host-side interfaces —
node anchors (gv/gq/gi/gd/gx/gp), switch cascade veth ends (gs) and
cross-compute VXLAN devices (gvl) — in its interface list and even
pre-filled them as ports on a freshly created cloud: none of them is
flagged special and only "gns3*" names were filtered. These interfaces
belong to the link or node that created them for the project's whole
lifetime, so bridging a cloud to one would race its owner.

Add is_kernel_datapath_interface() next to the naming contract in
utils/kernel_anchor.py: exact-shape matching (prefix plus its
fixed-length id-derived tail), so a user interface that merely starts
with "gv" is never swallowed. The cloud keeps managed names out of its
host-interface list and default ports mapping, while saved mappings
keep working (the existence checks still see the full interface list).
2026-10-14 10:11:49 +08:00

124 lines
4.9 KiB
Python

#
# Copyright (C) 2026 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The kernel-datapath anchor naming contract (shared by the compute classes
that create anchors and the controller that names a peer's anchor for an
Ethernet-switch link), plus the cascade veth naming for switch-to-switch
links.
"""
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"
def test_every_node_type_has_its_own_prefix():
assert kernel_anchor_name("docker", NODE_ID, 0, 0) == "gv00010203e0p0"
assert kernel_anchor_name("qemu", NODE_ID, 1, 2) == "gq00010203e1p2"
assert kernel_anchor_name("iou", NODE_ID, 0, 3) == "gi00010203e0p3"
assert kernel_anchor_name("dynamips", NODE_ID, 6, 48) == "gd00010203e6p48"
assert kernel_anchor_name("iol_docker", NODE_ID, 0, 0) == "gx00010203e0p0"
assert kernel_anchor_name("vpcs", NODE_ID, 0, 0) == "gp00010203e0p0"
def test_anchor_type_keys_iol_runner_containers_apart():
"""
IOL runner containers are Docker nodes whose anchors are persistent TAPs,
not veth host ends — their own key keeps the gv namespace free of TAPs.
The marker check must ignore a mere mention of the knob and survive a
multi-line environment.
"""
assert kernel_anchor_type("docker", "GNS3_IOL_RUNNER=1") == "iol_docker"
assert kernel_anchor_type("docker", "GNS3_IOL_STARTUP_CONFIG=cfg.txt\nGNS3_IOL_RUNNER=1") == "iol_docker"
assert kernel_anchor_type("docker", "GNS3_IOL_RUNNER=1,") == "iol_docker"
assert kernel_anchor_type("docker") == "docker"
assert kernel_anchor_type("docker", "GNS3_UNIX_SOCKET_NIO=1") == "docker"
# a mid-line mention is not the marker (mirrors _select_node_class)
assert kernel_anchor_type("docker", "NOTE=GNS3_IOL_RUNNER=1") == "docker"
assert kernel_anchor_type("qemu") == "qemu"
def test_unknown_node_types_have_no_anchor():
assert kernel_anchor_name("ethernet_switch", NODE_ID, 0, 0) is None
assert kernel_anchor_name("cloud", NODE_ID, 0, 0) is None
def test_names_fit_ifnamsiz():
# worst case: WIC port numbers reach 48 with the longest prefix
assert len(kernel_anchor_name("dynamips", NODE_ID, 6, 48)) <= 15
def test_suffix_is_stable_across_the_node_type():
assert anchor_suffix(NODE_ID, 1, 2) == "00010203e1p2"
assert kernel_anchor_name("docker", NODE_ID, 1, 2) == "gv" + anchor_suffix(NODE_ID, 1, 2)
def test_cascade_names_are_a_pair_of_distinct_sides():
end0, end1 = kernel_cascade_names(LINK_ID)
assert end0 == "gs1a2b3c4d5e0"
assert end1 == "gs1a2b3c4d5e1"
assert end0 != end1
def test_cascade_names_fit_ifnamsiz():
for end in kernel_cascade_names(LINK_ID):
assert len(end) <= 15
def test_cascade_names_stay_out_of_the_node_anchor_namespace():
# "gs" cannot collide with any node anchor prefix (gv/gq/gi/gd/gx), so a
# 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)