mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-13 05:55:35 +03:00
fix(esw): harden the cascade end the create race leaves unhardened
docker create_veth hardens only its FIRST end (the Docker shape: host anchor plus a container leg that keeps its L3 life). A cascade end is that second, unhardened end whenever the peer switch's create won the race, and the kernel gives it an IPv6 link-local — a silent L3 identity on the switch fabric (ubridge-l2-anchor-spec §E). Each switch now hardens its OWN end with 'link l2only <end> on' once the pair exists, whatever branch brought it there (fresh create, lost race, reuse); idempotent, and best-effort with a warning on a uBridge without the command.
This commit is contained in:
parent
6e36b58a5d
commit
cb7f74c77b
@ -476,6 +476,22 @@ class EthernetSwitch(KernelDatapathMixin, BaseNode):
|
||||
else:
|
||||
with contextlib.suppress(UbridgeError):
|
||||
await self._ubridge_send(f'tc reset "{anchor}"')
|
||||
# ``docker create_veth`` hardens only its FIRST end (the Docker
|
||||
# shape: host anchor plus a container leg that keeps its L3
|
||||
# life). Here BOTH ends are anchors, and whichever switch loses
|
||||
# the create race owns the second, unhardened end — its kernel
|
||||
# IPv6 link-local would give the switch fabric a silent L3
|
||||
# identity (ubridge-l2-anchor-spec §E). Each switch hardens its
|
||||
# OWN end once the pair exists; idempotent, and it covers a
|
||||
# reused pre-existing end as well.
|
||||
try:
|
||||
await self._ubridge_send(f'link l2only "{anchor}" on')
|
||||
except UbridgeError as e:
|
||||
log.warning(
|
||||
'Ethernet switch "{name}" [{id}]: could not harden cascade end "{anchor}" L2-only: {error}'.format(
|
||||
name=self._name, id=self._id, anchor=anchor, error=e
|
||||
)
|
||||
)
|
||||
# Register before _kernel_attach: the marker reconcile recognises
|
||||
# kernel anchors through _kernel_anchors().
|
||||
self._kernel_ports[port_number] = anchor
|
||||
|
||||
@ -956,6 +956,37 @@ class TestEthernetSwitchNodesRoutes:
|
||||
node._ubridge_send.assert_any_call(f'brctl addif "{br}" "{anchor}"')
|
||||
assert node._kernel_ports[0] == anchor
|
||||
|
||||
async def test_ethernet_switch_create_cascade_nio_hardens_own_end_l2only(
|
||||
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
|
||||
) -> None:
|
||||
"""
|
||||
``docker create_veth`` hardens only its FIRST end (the Docker shape:
|
||||
host anchor plus a container leg that keeps its L3 life) — but a
|
||||
cascade end the peer's winning create minted is that second,
|
||||
unhardened end, and the kernel gives it an IPv6 link-local (a silent
|
||||
L3 identity on the switch fabric, ubridge-l2-anchor-spec §E). Each
|
||||
switch therefore hardens its OWN end once the pair exists, whatever
|
||||
branch brought it there (fresh create, lost race, reuse).
|
||||
"""
|
||||
|
||||
anchor, peer = "gs1a2b3c4d5e0", "gs1a2b3c4d5e1"
|
||||
url = app.url_path_for(
|
||||
"compute:create_ethernet_switch_nio",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"],
|
||||
adapter_number="0",
|
||||
port_number="0",
|
||||
)
|
||||
# pre-existing end: the branch whose end create_veth did NOT harden
|
||||
with self._cascade_sysfs(anchor, {"value": True}):
|
||||
response = await compute_client.post(url, json=self._cascade_params(anchor, peer))
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
node = compute_project.get_node(ethernet_switch["node_id"])
|
||||
node._ubridge_send.assert_any_call(f'link l2only "{anchor}" on')
|
||||
# only its own end — the peer's end is the peer switch's to harden
|
||||
assert not any(f'"{peer}"' in str(c) and "l2only" in str(c) for c in node._ubridge_send.call_args_list)
|
||||
|
||||
async def test_ethernet_switch_delete_cascade_nio_destroys_the_pair(
|
||||
self, app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict
|
||||
) -> None:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user