mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
feat(dynamips): anchor every Ethernet port on a persistent TAP
Dynamips links can now ride the kernel datapath like Docker, QEMU and IOU:
each Ethernet slot/port gets a persistent TAP anchor (gd{id}e{slot}p{port}),
created through uBridge's tap module, and the Dynamips hypervisor opens it
with nio create_tap — the hypervisor holds the fd, exactly QEMU's shape.
Links attach the anchor to their per-link kernel bridge; the legacy UDP
tunnel remains the relay datapath for every other port.
Anchor lifecycle: born at node start (tap module probed with one
create/delete cycle, sweep of stale devices, ownership handed to the server
user, born down); a kernel link opens the anchor in the hypervisor, binds
the slot/port and enslaves it; removing the link releases the fd again
(nio delete) and detaches. A link bound while the router is stopped is
wired by the next start (deferred wiring). Node close follows QEMU's stop
order — the hypervisor's TAP NIOs go before tap delete, since it is
Dynamips, not uBridge, that holds the fds.
Stop is not close: vm stop only halts the emulated router, so the
hypervisor, the TAP fds, the bindings and the per-link bridges all survive
a stop/start untouched (attach skips ports that still hold their TAP NIO).
Port selection follows the adapter registry: ETHERNET_ADAPTERS and
ETHERNET_WICS name the models whose ports carry Ethernet frames (WIC-1ENET
included, at its Dynamips port number); serial, ATM and POS ports never
anchor, and a kernel link there errors with the relay-datapath message
instead of half-wiring. Adapters hot-added to a running router (OIR) get
their anchors at once.
The compute NIO routes accept nio_bridge alongside nio_udp (the same
widening QEMU needed), carry the endpoint's suspend flag on update (it is
the anchor's admin state on a kernel link), skip the hypervisor delete for
a kernel NIO (its TAP NIO was already released by the slot removal), and
the close path leaves NIOBridge instances to _stop_ubridge.
This commit is contained in:
5 files changed
+1019
-13
No files matched your search
@@ -39,6 +39,7 @@ from gns3server.utils.asyncio import subprocess_check_output, wait_run_in_execut
|
||||
from gns3server.utils.interfaces import is_interface_up
|
||||
|
||||
from ..base_manager import BaseManager
|
||||
from ..nios.nio_bridge import NIOBridge
|
||||
from ..port_manager import PortManager
|
||||
|
||||
# Adapters
|
||||
@@ -364,7 +365,16 @@ class Dynamips(BaseManager):
|
||||
"""
|
||||
|
||||
nio = None
|
||||
if nio_settings["type"] == "nio_udp":
|
||||
if nio_settings["type"] == "nio_bridge":
|
||||
# Kernel-datapath NIO: the router binds the port's anchor TAP to
|
||||
# a hypervisor nio_tap and enslaves it into this per-link bridge
|
||||
# (Router._attach_kernel_nio). No hypervisor NIO is created here.
|
||||
nio = NIOBridge(nio_settings["bridge"])
|
||||
nio.filters = nio_settings.get("filters", {})
|
||||
nio.markers = nio_settings.get("markers", {})
|
||||
nio.suspend = nio_settings.get("suspend", False)
|
||||
return nio
|
||||
elif nio_settings["type"] == "nio_udp":
|
||||
lport = nio_settings["lport"]
|
||||
rhost = nio_settings["rhost"]
|
||||
rport = nio_settings["rport"]
|
||||
|
||||
@@ -15,6 +15,37 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# Adapter and WIC models whose ports carry Ethernet frames (by __str__
|
||||
# name, matching the controller's port matrix). Only these ports get a
|
||||
# kernel-datapath anchor TAP: a TAP cannot carry the serial/ATM/POS
|
||||
# encapsulations the other models speak.
|
||||
ETHERNET_ADAPTERS = frozenset(
|
||||
{
|
||||
"C1700-MB-1FE",
|
||||
"C2600-MB-1E",
|
||||
"C2600-MB-1FE",
|
||||
"C2600-MB-2E",
|
||||
"C2600-MB-2FE",
|
||||
"C7200-IO-2FE",
|
||||
"C7200-IO-FE",
|
||||
"C7200-IO-GE-E",
|
||||
"GT96100-FE",
|
||||
"Leopard-2FE",
|
||||
"NM-16ESW",
|
||||
"NM-1E",
|
||||
"NM-1FE-TX",
|
||||
"NM-4E",
|
||||
"PA-2FE-TX",
|
||||
"PA-4E",
|
||||
"PA-8E",
|
||||
"PA-FE-TX",
|
||||
"PA-GE",
|
||||
}
|
||||
)
|
||||
|
||||
ETHERNET_WICS = frozenset({"WIC-1ENET"})
|
||||
|
||||
|
||||
class Adapter:
|
||||
"""
|
||||
Base class for adapters.
|
||||
|
||||
@@ -22,6 +22,7 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L77
|
||||
import asyncio
|
||||
import base64
|
||||
import binascii
|
||||
import contextlib
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
@@ -37,10 +38,16 @@ from gns3server.utils.hostname import is_ios_hostname_valid
|
||||
from gns3server.utils.images import md5sum
|
||||
|
||||
from ...base_node import BaseNode
|
||||
from ...error import NodeError
|
||||
from ...kernel_datapath import KernelDatapathMixin
|
||||
from ...nios.nio_bridge import NIOBridge
|
||||
from ...nios.nio_tap import NIOTAP
|
||||
from ...ubridge.ubridge_error import UbridgeError
|
||||
from ..adapters.adapter import ETHERNET_ADAPTERS, ETHERNET_WICS
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
|
||||
class Router(BaseNode):
|
||||
class Router(KernelDatapathMixin, BaseNode):
|
||||
"""
|
||||
Dynamips router implementation.
|
||||
|
||||
@@ -123,6 +130,13 @@ class Router(BaseNode):
|
||||
self._slots = []
|
||||
self._ghost_flag = ghost_flag
|
||||
self._memory_watcher = None
|
||||
# Kernel datapath: the persistent anchor TAP per Ethernet slot/port
|
||||
# (in _kernel_taps) and the hypervisor TAP NIO holding each anchor
|
||||
# open while a kernel link binds the port (in _tap_nios).
|
||||
self._kernel_taps = {}
|
||||
self._tap_nios = {}
|
||||
self._tap_datapath = False
|
||||
self._ubridge_tc_caps = None
|
||||
|
||||
if not ghost_flag:
|
||||
if not dynamips_id:
|
||||
@@ -320,7 +334,16 @@ class Router(BaseNode):
|
||||
# an empty private-config can prevent a router to boot.
|
||||
private_config_path = ""
|
||||
|
||||
await self._hypervisor.send(f'vm set_config "{self._name}" "{startup_config_path}" "{private_config_path}"')
|
||||
# uBridge owns the kernel datapath's anchor TAPs; start it and
|
||||
# create them before the VM boots, so kernel links bound while
|
||||
# the node was stopped wire themselves as IOS comes up.
|
||||
await self._prepare_tap_datapath()
|
||||
|
||||
await self._hypervisor.send(
|
||||
'vm set_config "{name}" "{startup}" "{private}"'.format(
|
||||
name=self._name, startup=startup_config_path, private=private_config_path
|
||||
)
|
||||
)
|
||||
await self._hypervisor.send(f'vm start "{self._name}"')
|
||||
self.status = "started"
|
||||
log.debug(f'router "{self._name}" [{self._id}] has been started')
|
||||
@@ -421,7 +444,9 @@ class Router(BaseNode):
|
||||
for adapter in self._slots:
|
||||
if adapter is not None:
|
||||
for nio in adapter.ports.values():
|
||||
if nio:
|
||||
# NIOBridge has no hypervisor side to close here — its
|
||||
# release runs in _stop_ubridge below.
|
||||
if nio and not isinstance(nio, NIOBridge):
|
||||
await nio.close()
|
||||
|
||||
await self._stop_ubridge()
|
||||
@@ -1143,6 +1168,11 @@ class Router(BaseNode):
|
||||
|
||||
log.debug(f'Router "{self._name}" [{self._id}]: OIR start event sent to slot {slot_number}')
|
||||
|
||||
if self._tap_datapath:
|
||||
# The new adapter's Ethernet ports need their anchor TAPs now
|
||||
# (not at next start) for kernel links to attach at once.
|
||||
await self._create_taps()
|
||||
|
||||
async def slot_remove_binding(self, slot_number):
|
||||
"""
|
||||
Removes a slot binding (a module from a slot).
|
||||
@@ -1281,6 +1311,19 @@ class Router(BaseNode):
|
||||
if not adapter.port_exists(port_number):
|
||||
raise DynamipsError(f"Port {port_number} does not exist on adapter {adapter}")
|
||||
|
||||
if isinstance(nio, NIOBridge):
|
||||
# Kernel datapath: the port's anchor TAP is opened in the
|
||||
# hypervisor and enslaved into the NIO's per-link bridge (the
|
||||
# wiring is deferred to the node's start when no anchors exist).
|
||||
adapter.add_nio(port_number, nio)
|
||||
await self._attach_kernel_nio(slot_number, port_number, nio)
|
||||
log.debug(
|
||||
'Router "{name}" [{id}]: {nio} bound to port {slot_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, nio=nio, slot_number=slot_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
await self._hypervisor.send(f'vm slot_add_nio_binding "{self._name}" {slot_number} {port_number} {nio}')
|
||||
except DynamipsError:
|
||||
@@ -1302,6 +1345,15 @@ class Router(BaseNode):
|
||||
:param nio: NIO instance to add to the slot/port
|
||||
"""
|
||||
|
||||
if isinstance(nio, NIOBridge):
|
||||
# Filters, markers or suspend changed on a kernel link: re-apply
|
||||
# on the anchor, no re-binding and no re-enslaving. A NIO bound
|
||||
# while the node was stopped carries its state; start applies it.
|
||||
anchor = self._kernel_host_ifc(slot_number, port_number)
|
||||
if anchor is not None and self.ubridge:
|
||||
await self._kernel_update(anchor, nio)
|
||||
await self._set_adapter_carrier(slot_number, not nio.suspend, port_number)
|
||||
return
|
||||
await nio.update()
|
||||
|
||||
async def slot_remove_nio_binding(self, slot_number, port_number):
|
||||
@@ -1326,12 +1378,27 @@ class Router(BaseNode):
|
||||
raise DynamipsError(f"Port {port_number} does not exist on adapter {adapter}")
|
||||
|
||||
await self.stop_capture(slot_number, port_number)
|
||||
await self.slot_disable_nio(slot_number, port_number)
|
||||
await self._hypervisor.send(f'vm slot_remove_nio_binding "{self._name}" {slot_number} {port_number}')
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
if nio is None:
|
||||
return
|
||||
|
||||
if isinstance(nio, NIOBridge):
|
||||
await self._remove_kernel_nio_binding(slot_number, port_number, nio)
|
||||
adapter.remove_nio(port_number)
|
||||
log.debug(
|
||||
'Router "{name}" [{id}]: {nio} removed from port {slot_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, nio=nio, slot_number=slot_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
return nio
|
||||
|
||||
await self.slot_disable_nio(slot_number, port_number)
|
||||
await self._hypervisor.send(
|
||||
'vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format(
|
||||
name=self._name, slot_number=slot_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
await nio.close()
|
||||
adapter.remove_nio(port_number)
|
||||
|
||||
@@ -1421,6 +1488,21 @@ class Router(BaseNode):
|
||||
if not nio:
|
||||
raise DynamipsError(f"Port {slot_number}/{port_number} is not connected")
|
||||
|
||||
if isinstance(nio, NIOBridge):
|
||||
# Kernel link: capture on the port's TAP anchor (AF_PACKET),
|
||||
# not in the Dynamips NIO (this link has no Dynamips NIO).
|
||||
nio.start_packet_capture(output_file, data_link_type)
|
||||
if self.ubridge:
|
||||
anchor = self._kernel_host_ifc(slot_number, port_number)
|
||||
if anchor is not None:
|
||||
await self._ubridge_send(f'capture start_kernel {anchor} "{output_file}"')
|
||||
log.debug(
|
||||
'Router "{name}" [{id}]: starting packet capture on port {slot_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, slot_number=slot_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if nio.input_filter[0] is not None and nio.output_filter[0] is not None:
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied on {adapter}")
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
@@ -1448,10 +1530,265 @@ class Router(BaseNode):
|
||||
|
||||
if not nio.capturing:
|
||||
return
|
||||
|
||||
if isinstance(nio, NIOBridge):
|
||||
nio.stop_packet_capture()
|
||||
if self.ubridge and self._kernel_host_ifc(slot_number, port_number) is not None:
|
||||
await self._ubridge_send("capture stop_kernel")
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
|
||||
log.debug(f'Router "{self._name}" [{self._id}]: stopping packet capture on port {slot_number}/{port_number}')
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Kernel datapath: the port anchor is a persistent TAP held by the
|
||||
# Dynamips hypervisor (nio create_tap) — QEMU's shape, not IOU's
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _tap_name(self, slot_number, port_number):
|
||||
"""
|
||||
Deterministic anchor TAP name for a slot/port. The ``gd`` prefix
|
||||
keeps it out of the ``gns3`` bridge/TAP name space (and apart from
|
||||
Docker's ``gv``/``gc``, QEMU's ``gq`` and IOU's ``gi``); 8 hex chars
|
||||
of the node id plus slot/port keep it unique and within IFNAMSIZ
|
||||
(15), even for WIC port numbers (16, 32, 48).
|
||||
"""
|
||||
|
||||
return f"gd{self._id.replace('-', '')[:8]}e{slot_number}p{port_number}"
|
||||
|
||||
def _kernel_host_ifc(self, slot_number, port_number=0):
|
||||
"""
|
||||
The persistent TAP this Ethernet slot/port owns, or None for serial,
|
||||
ATM or POS ports (never anchored) and before the router started.
|
||||
"""
|
||||
|
||||
return self._kernel_taps.get((slot_number, port_number))
|
||||
|
||||
def _kernel_anchors(self):
|
||||
"""
|
||||
Every anchor TAP this router currently owns.
|
||||
"""
|
||||
|
||||
return set(self._kernel_taps.values())
|
||||
|
||||
def _kernel_error(self, message):
|
||||
return DynamipsError(message)
|
||||
|
||||
def _ethernet_slot_ports(self):
|
||||
"""
|
||||
Every (slot, port) that can carry Ethernet frames: slot adapters
|
||||
whose model is Ethernet, plus WIC-1ENET ports (numbered from 16 per
|
||||
WIC slot, the Dynamips convention) in any motherboard.
|
||||
"""
|
||||
|
||||
ports = []
|
||||
for slot_number, adapter in enumerate(self._slots):
|
||||
if adapter is None:
|
||||
continue
|
||||
if str(adapter) in ETHERNET_ADAPTERS:
|
||||
ports.extend((slot_number, port_number) for port_number in adapter.ports)
|
||||
for wic_slot_number, wic in enumerate(adapter.wics or []):
|
||||
if wic is not None and str(wic) in ETHERNET_WICS:
|
||||
ports.append((slot_number, 16 * (wic_slot_number + 1)))
|
||||
return ports
|
||||
|
||||
@property
|
||||
def _ethernet_adapters(self):
|
||||
"""
|
||||
Slot adapters owning at least one anchor — the mixin's per-link
|
||||
bridge sweep walks them. Dynamips ports live in slot adapters (WIC
|
||||
ports included), so this derives from the anchor map rather than
|
||||
the slot list.
|
||||
"""
|
||||
|
||||
adapters = []
|
||||
for slot_number, _port_number in self._kernel_taps:
|
||||
if slot_number < len(self._slots):
|
||||
adapter = self._slots[slot_number]
|
||||
if adapter is not None and adapter not in adapters:
|
||||
adapters.append(adapter)
|
||||
return adapters
|
||||
|
||||
async def _prepare_tap_datapath(self):
|
||||
"""
|
||||
Start uBridge and create the persistent TAP every Ethernet slot/port
|
||||
owns — the anchor role QEMU's TAPs and Docker's veth host ends play.
|
||||
The Dynamips hypervisor opens the device (ownership handed to this
|
||||
user by _create_taps), so it needs no privileges of its own to hold
|
||||
the fd. A uBridge without the tap module keeps this router on the
|
||||
relay datapath: every link rides the NIOUDP tunnel, exactly as
|
||||
before.
|
||||
|
||||
Nothing here is undone at node stop — the hypervisor, its NIO
|
||||
bindings and the per-link kernel bridges all outlive ``vm stop``,
|
||||
which is what makes a stopped router's links self-heal on the next
|
||||
start. The anchors are only retired when the node closes.
|
||||
"""
|
||||
|
||||
if self._ghost_flag:
|
||||
# Ghost IOS images share RAM with a real router and never link.
|
||||
return
|
||||
|
||||
try:
|
||||
await self._start_ubridge()
|
||||
except NodeError as e:
|
||||
# A Dynamips router without links never needed uBridge; keep the
|
||||
# node bootable and let the relay raise at link time (as before).
|
||||
log.warning("Router '%s': %s", self._name, e)
|
||||
return
|
||||
|
||||
# uBridge (re)started: the tc capabilities must be probed again.
|
||||
self._ubridge_tc_caps = None
|
||||
if not self._tap_datapath:
|
||||
probe = f"gd{self._id.replace('-', '')[:8]}prob"
|
||||
try:
|
||||
await self._ubridge_send(f'tap create "{probe}"')
|
||||
except UbridgeError as e:
|
||||
message = (
|
||||
f"Router '{self._name}': uBridge cannot create persistent TAPs ({e}); "
|
||||
"this node runs on the relay datapath and cannot carry kernel links"
|
||||
)
|
||||
log.warning(message)
|
||||
self.project.emit("log.warning", {"message": message})
|
||||
return
|
||||
with contextlib.suppress(UbridgeError):
|
||||
await self._ubridge_send(f'tap delete "{probe}"')
|
||||
self._tap_datapath = True
|
||||
|
||||
await self._create_taps()
|
||||
# Kernel links bound while the router was stopped could not wire (no
|
||||
# anchors existed); wire them now, before IOS comes up.
|
||||
for slot_number, adapter in enumerate(self._slots):
|
||||
if adapter is None:
|
||||
continue
|
||||
for port_number, nio in list(adapter.ports.items()):
|
||||
if isinstance(nio, NIOBridge):
|
||||
await self._attach_kernel_nio(slot_number, port_number, nio)
|
||||
|
||||
async def _create_taps(self):
|
||||
"""
|
||||
Create the persistent TAP every Ethernet slot/port owns. Each TAP
|
||||
starts DOWN (carrier off until a kernel link attaches); uBridge
|
||||
creates the device and hands ownership to this user, so the
|
||||
unprivileged Dynamips hypervisor can open it. A port whose anchor
|
||||
already exists (a restart, or an adapter hot-added to a running
|
||||
router) keeps it — an anchor is never recreated under a live link.
|
||||
A persistent TAP outlives its creator, so a leftover from a previous
|
||||
run (crash, kill) is swept first.
|
||||
"""
|
||||
|
||||
for slot_number, port_number in self._ethernet_slot_ports():
|
||||
if (slot_number, port_number) in self._kernel_taps:
|
||||
continue
|
||||
tap = self._tap_name(slot_number, port_number)
|
||||
with contextlib.suppress(UbridgeError):
|
||||
await self._ubridge_send(f'tap delete "{tap}"')
|
||||
await self._ubridge_send(f'tap create "{tap}"')
|
||||
try:
|
||||
await self._ubridge_send(f"tap set_owner {tap} {os.getuid()}")
|
||||
except UbridgeError as e:
|
||||
# Only root could open the TAP now: the hypervisor would fail
|
||||
# to bind its NIO, which is worth surfacing at that point.
|
||||
log.warning("Router '%s': could not hand TAP %s to uid %s: %s", self._name, tap, os.getuid(), e)
|
||||
await self._ubridge_send(f'link set "{tap}" down')
|
||||
self._kernel_taps[(slot_number, port_number)] = tap
|
||||
|
||||
async def _attach_kernel_nio(self, slot_number, port_number, nio):
|
||||
"""
|
||||
Wire a kernel link's NIO (NIOBridge) on a port: open the port's
|
||||
anchor TAP in the Dynamips hypervisor (``nio create_tap`` — the
|
||||
hypervisor, not uBridge, holds the fd, the same shape as QEMU), bind
|
||||
it to the slot/port, then enslave the anchor into the per-link
|
||||
kernel bridge — the shared mixin flow. On a router that is not
|
||||
running the wiring is deferred to its start (no anchors exist yet).
|
||||
A port whose wiring survived a stop (nothing is torn down there) is
|
||||
left alone: re-binding would collide on both hypervisor and bridge.
|
||||
"""
|
||||
|
||||
if (slot_number, port_number) in self._tap_nios:
|
||||
return
|
||||
anchor = self._kernel_host_ifc(slot_number, port_number)
|
||||
if anchor is None:
|
||||
if self.status != "started":
|
||||
return
|
||||
raise self._kernel_error(
|
||||
"Port {slot}/{port} of router '{name}' runs on the relay datapath "
|
||||
"(this uBridge cannot create persistent TAPs) and cannot carry a kernel link".format(
|
||||
slot=slot_number, port=port_number, name=self._name
|
||||
)
|
||||
)
|
||||
|
||||
tap_nio = NIOTAP(self._hypervisor, anchor)
|
||||
await tap_nio.create()
|
||||
try:
|
||||
await self._hypervisor.send(
|
||||
'vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(
|
||||
name=self._name, slot_number=slot_number, port_number=port_number, nio=tap_nio.name
|
||||
)
|
||||
)
|
||||
except DynamipsError:
|
||||
with contextlib.suppress(DynamipsError):
|
||||
await tap_nio.delete()
|
||||
raise
|
||||
await self.slot_enable_nio(slot_number, port_number)
|
||||
self._tap_nios[(slot_number, port_number)] = tap_nio
|
||||
await self._kernel_attach(anchor, nio)
|
||||
await self._set_adapter_carrier(slot_number, not nio.suspend, port_number)
|
||||
|
||||
async def _remove_kernel_nio_binding(self, slot_number, port_number, nio):
|
||||
"""
|
||||
Release a kernel link's NIO: tear the anchor out of the per-link
|
||||
kernel bridge (markers, tc, delif — the shared mixin flow), then
|
||||
unbind the port and delete the hypervisor's TAP NIO, releasing the
|
||||
anchor fd. The anchor device itself survives for the next link.
|
||||
"""
|
||||
|
||||
if self.ubridge and self._kernel_host_ifc(slot_number, port_number) is not None:
|
||||
await self._remove_kernel_nio(nio, slot_number, port_number)
|
||||
tap_nio = self._tap_nios.pop((slot_number, port_number), None)
|
||||
if tap_nio is not None:
|
||||
with contextlib.suppress(DynamipsError, OSError):
|
||||
await self.slot_disable_nio(slot_number, port_number)
|
||||
with contextlib.suppress(DynamipsError, OSError):
|
||||
await self._hypervisor.send(
|
||||
'vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format(
|
||||
name=self._name, slot_number=slot_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
with contextlib.suppress(DynamipsError, OSError):
|
||||
await tap_nio.delete()
|
||||
|
||||
async def _stop_ubridge(self):
|
||||
"""
|
||||
Stops uBridge, retiring the kernel datapath's host state first — in
|
||||
the QEMU order, since here it is the Dynamips hypervisor (not
|
||||
uBridge) that holds the anchor fds: the per-link kernel bridges need
|
||||
the control channel, and the anchor TAPs (persistent devices) must
|
||||
be deleted through it after the hypervisor's TAP NIOs are gone, or a
|
||||
device Dynamips still holds open would outlive its retirement. The
|
||||
next start spawns a fresh uBridge, so the tc capabilities must be
|
||||
probed again.
|
||||
"""
|
||||
|
||||
if self.ubridge:
|
||||
for (slot_number, port_number), tap_nio in list(self._tap_nios.items()):
|
||||
with contextlib.suppress(DynamipsError, OSError):
|
||||
await self._hypervisor.send(
|
||||
'vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format(
|
||||
name=self._name, slot_number=slot_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
with contextlib.suppress(DynamipsError, OSError):
|
||||
await tap_nio.delete()
|
||||
self._tap_nios.clear()
|
||||
for tap in self._kernel_taps.values():
|
||||
with contextlib.suppress(UbridgeError):
|
||||
await self._ubridge_send(f'tap delete "{tap}"')
|
||||
await self._remove_kernel_bridges()
|
||||
self._kernel_taps.clear()
|
||||
self._ubridge_tc_caps = None
|
||||
await super()._stop_ubridge()
|
||||
|
||||
def _create_slots(self, numslots):
|
||||
"""
|
||||
Creates the appropriate number of slots for this router.
|
||||
|
||||
Reference in new issue
Block a user