# # 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 . # """ Interface for anchor-absorbing NIOs (the Ethernet switch's kernel datapath). A NIO of this type instructs the node to join a *foreign* host interface — another node's kernel-datapath anchor — into a bridge the node itself owns, with the node's port settings applied to it. The Ethernet switch is the consumer: its per-node kernel bridge absorbs the peer's anchor directly, so frames cross peer anchor -> switch bridge -> switch ports entirely in the kernel instead of riding the switch's per-port uBridge relay. This is the mirror of NIOBridge: NIOBridge says "enslave *my* anchor into the bridge named here", NIOAnchor says "enslave *this* interface into my own bridge". Impairment filters, markers and capture ride the NIO and attach to the named anchor (tc on it, marker add_kernel / capture start_kernel on it) — exactly one link end owns them. """ from .nio import NIO class NIOAnchor(NIO): """ Anchor-absorbing NIO. :param anchor: host interface to join to the node's own bridge :param peer: for a switch-to-switch cascade link, the *other* end of the veth pair this port is one end of. Both sides carry it: each creates the pair when its end is missing (``docker create_veth ``, the race between the two uBridge processes lost benignly by whoever's create answers "exists"), and either side's teardown may destroy it — deleting one veth end takes the whole pair, so the deletions converge. Absent on absorbed-anchor links, where the interface belongs to the peer's node and is never this switch's to create or destroy. """ def __init__(self, anchor, peer=None): super().__init__() self._anchor = anchor self._peer = peer @property def anchor(self): """ Returns the interface name to absorb. :returns: interface name """ return self._anchor @property def peer(self): """ Returns the other end of the cascade veth pair, or None when the anchor is a foreign interface the switch merely absorbs. :returns: interface name or None """ return self._peer @property def bridge(self): """ Always None: this NIO owns no per-link bridge — the absorbed anchor joins the node's *own* bridge. The None makes the shared KernelDatapathMixin attach/remove flows skip the per-link bridge membership work (their external-bridge case), which is exactly what the switch needs on top of its own addif. """ return None def __str__(self): return "NIO anchor" def asdict(self): data = { "type": "nio_anchor", "anchor": self._anchor, "suspend": self._suspended, "filters": self._filters, "markers": self._markers, } if self._peer is not None: data["peer"] = self._peer return data