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:
YueGuobin committed 2026-10-06 00:21:10 +08:00
1 parent 6e36b58a5d
commit cb7f74c77b
2 files changed
+47

No files matched your search

@@ -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