mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
feat: cross-compute VXLAN kernel datapath
Cross-compute links can ride kernel VXLAN tunnels instead of the uBridge
UDP relay: each endpoint creates one VXLAN device (uBridge's rtnetlink
vxlan module) enslaved into the same per-link Linux bridge the same-host
datapath uses, so filters, markers, capture and suspend keep running on
the anchor unchanged. Opt-in (Server.enable_vxlan_cross_compute, default
off) because the tunnel caps the usable MTU at the underlay minus the
outer headers; the relay stays the default and the fallback.
The controller derives what the pair must agree on: one VNI per link
(upper VNI space, per-link random salt) and deterministic device names
(gvl{link}{node}) that make crashed-run leftovers claimable
(show -> delete -> create). Same-host compute pairs, switch endpoints
and serial/unix-socket containers stay on the relay.
A VNI the kernel refuses self-heals on every path it can surface:
interactive create retries a fresh salt then degrades to the relay;
project open re-dispatches refused batch entries the same way; node
start recognises the refused device, stops the half-started node,
re-wires both ends on a fresh VNI (or degrades the link) and retries
the start. The device MTU is derived from the lower device (dev=) and
the per-link bridge inherits it - no configuration. The capability
probe (ubridge_vxlan in /capabilities) runs one scratch create/delete
on a scratch port that never equals the configured dstport.
The e2e scenario runs two computes on one machine (a network namespace
joined by a veth pair as the fabric) and needs root.
This commit is contained in:
24 files changed
+2107
-59
No files matched your search
@@ -30,6 +30,7 @@ from gns3server.compute.ubridge.tc_probe import (
|
||||
probe_iol_tap_support,
|
||||
probe_tap_support,
|
||||
probe_tc_capabilities,
|
||||
probe_vxlan_support,
|
||||
)
|
||||
from gns3server.compute.vpcs.vpcs_probe import probe_vpcs_tap
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
@@ -75,6 +76,11 @@ async def get_capabilities(request: Request) -> dict:
|
||||
# kernel link. None = unknown (probe failed or binary missing).
|
||||
vpcs_tap = await probe_vpcs_tap()
|
||||
|
||||
# VXLAN device creation (the vxlan module): the tunnel a cross-compute
|
||||
# kernel link rides. None = unknown (probe failed, uBridge too old, or
|
||||
# the kernel refuses vxlan devices here).
|
||||
ubridge_vxlan = await probe_vxlan_support()
|
||||
|
||||
# record the controller hostname or IP address
|
||||
if request.client:
|
||||
request.app.state.controller_host = request.client.host
|
||||
@@ -91,4 +97,5 @@ async def get_capabilities(request: Request) -> dict:
|
||||
"ubridge_iol_tap": ubridge_iol_tap,
|
||||
"ubridge_bridge_tap": ubridge_bridge_tap,
|
||||
"vpcs_tap": vpcs_tap,
|
||||
"ubridge_vxlan": ubridge_vxlan,
|
||||
}
|
||||
@@ -35,6 +35,7 @@ from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.error import NodeError
|
||||
from gns3server.compute.project import Project
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.utils.path import is_safe_path
|
||||
@@ -271,16 +272,35 @@ async def create_batch_nios(
|
||||
# count (standard == 1, Dynamips == 2) and await the async variant.
|
||||
sig = inspect.signature(node.manager.create_nio)
|
||||
dynamips_style = len(sig.parameters) >= 2
|
||||
failed = []
|
||||
for entry in entries:
|
||||
nio_settings = jsonable_encoder(entry.nio, exclude_unset=True)
|
||||
if dynamips_style:
|
||||
nio = await node.manager.create_nio(node, nio_settings)
|
||||
else:
|
||||
nio = node.manager.create_nio(nio_settings)
|
||||
await _add_nio_binding(node, entry.adapter_number, entry.port_number, nio)
|
||||
try:
|
||||
if dynamips_style:
|
||||
nio = await node.manager.create_nio(node, nio_settings)
|
||||
else:
|
||||
nio = node.manager.create_nio(nio_settings)
|
||||
await _add_nio_binding(node, entry.adapter_number, entry.port_number, nio)
|
||||
except NodeError:
|
||||
# A VXLAN entry that cannot be wired (its VNI already
|
||||
# registered on the host's shared socket, no route to the
|
||||
# peer) must not fail the whole project open: report it so
|
||||
# the controller can degrade that link to the uBridge relay.
|
||||
# Every other NIO type keeps the fail-the-batch semantics.
|
||||
if not nio_settings.get("vxlan"):
|
||||
raise
|
||||
failed.append(
|
||||
{
|
||||
"node_id": entry.node_id,
|
||||
"adapter_number": entry.adapter_number,
|
||||
"port_number": entry.port_number,
|
||||
}
|
||||
)
|
||||
return failed
|
||||
|
||||
await asyncio.gather(*[_create_one_node(nid, ents) for nid, ents in per_node.items()])
|
||||
return {"added": len(batch.nios)}
|
||||
results = await asyncio.gather(*[_create_one_node(nid, ents) for nid, ents in per_node.items()])
|
||||
failed = [item for node_failed in results for item in node_failed]
|
||||
return {"added": len(batch.nios) - len(failed), "failed": failed}
|
||||
|
||||
|
||||
@router.put(
|
||||
|
||||
Reference in new issue
Block a user