mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-13 14:05:36 +03:00
The Ethernet switch's kernel fast path: instead of a link crossing the
switch's per-port uBridge relay, the switch absorbs the peer's anchor into
its own kernel bridge. The peer's interface (Docker veth host end, QEMU
TAP, IOU port TAP, Dynamips port TAP) becomes the switch port, and frames
cross anchor -> switch bridge -> switch ports entirely in the kernel.
Wire format: the switch end receives nio_anchor ('join this interface to
my own bridge' — the mirror of nio_bridge), the peer end nio_bridge with a
null bridge ('your anchor is bridged elsewhere': carrier, markers and
impairments only — never bridge membership, and never a bridge delete, on
an interface this node does not own). Anchor names come from the shared
naming contract (utils.kernel_anchor), a pure function of node type, id,
adapter and port — the controller computes the peer's anchor while the
node may be stopped, which is what makes links to stopped nodes wireable.
Stopped peers defer the join; after any node of a link starts
(UDPLink.node_started) the controller re-pushes the switch NIO, and the
switch completes — or repairs, when a peer restart replaced its interface
under the same name — the join against the kernel's actual membership
instead of assuming it.
VLAN changes now edit membership in place: only the ports whose mode/VLAN
actually changed are touched, each losing exactly the previous mode's
entries the new mode does not want (the dot1q admits-all entry is one
range operation in both directions), and no port ever leaves the bridge to
change its VLANs. This replaces the old 'reset by re-enslaving' shortcut
(delif+addif on every wired port, added when the ports were the switch's
own TAPs): on absorbed anchors, re-enslaving an interface owned by another
node is where live testing watched links die — anchors occasionally lost
their bridge membership right after a ports_mapping update (later updates
then failed with delif EINVAL), reproducibly with qemu, IOU and Dynamips
peers and never with Docker's veths.
Eligibility: the switch side needs no capability of its own (its bridge
exists by construction); the peer must be able to anchor. Switch-to-switch
cascades stay on the relay — a kernel interface belongs to one bridge, and
joining two bridges needs a veth pair, deferred.
Verified live on a real server: qemu peers through four VLAN cycles with
both anchors enslaved throughout; a mixed switch (2 IOU + 2 Dynamips, four
kernel ports); Docker peers with real ICMP isolating and recovering (with
both anchors still enslaved); VPCS relay ports likewise in place.
177 lines
6.1 KiB
Python
177 lines
6.1 KiB
Python
#
|
|
# Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
from enum import Enum
|
|
from typing import List, Optional, Union
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class UDPNIOType(str, Enum):
|
|
udp = "nio_udp"
|
|
|
|
|
|
class UDPNIO(BaseModel):
|
|
"""
|
|
UDP Network Input/Output properties.
|
|
"""
|
|
|
|
type: UDPNIOType
|
|
lport: int = Field(..., gt=0, le=65535, description="Local port")
|
|
rhost: str = Field(..., description="Remote host")
|
|
rport: int = Field(..., gt=0, le=65535, description="Remote port")
|
|
suspend: Optional[bool] = Field(None, description="Suspend the NIO")
|
|
filters: Optional[dict] = Field(
|
|
None,
|
|
description="Packet filters (relay-supported types only: delay/packet_loss/corrupt/frequency_drop/bpf — "
|
|
"the netem extensions and the quota mode need a kernel-datapath NIO)",
|
|
)
|
|
markers: Optional[dict] = Field(None, description="Traffic-insight markers")
|
|
|
|
|
|
class EthernetNIOType(str, Enum):
|
|
ethernet = "nio_ethernet"
|
|
|
|
|
|
class EthernetNIO(BaseModel):
|
|
"""
|
|
Generic Ethernet Network Input/Output properties.
|
|
"""
|
|
|
|
type: EthernetNIOType
|
|
ethernet_device: str = Field(..., description="Ethernet device name e.g. eth0")
|
|
|
|
|
|
class TAPNIOType(str, Enum):
|
|
tap = "nio_tap"
|
|
|
|
|
|
class TAPNIO(BaseModel):
|
|
"""
|
|
TAP Network Input/Output properties.
|
|
"""
|
|
|
|
type: TAPNIOType
|
|
tap_device: str = Field(..., description="TAP device name e.g. tap0")
|
|
|
|
|
|
class BridgeNIOType(str, Enum):
|
|
bridge = "nio_bridge"
|
|
|
|
|
|
class BridgeNIO(BaseModel):
|
|
"""
|
|
Kernel-datapath bridge Network Input/Output properties. The NIO tells the
|
|
node to enslave its veth host end into the named kernel bridge instead of
|
|
wiring a uBridge UDP relay. Impairment filters run on the veth host end:
|
|
delay/packet_loss/corrupt and the netem extensions (rate, reorder,
|
|
gemodel, duplicate, seed, limit, jitter distributions, loss correlation)
|
|
as one tc netem qdisc, bpf as cls_bpf match-drop classifiers (needs a
|
|
uBridge reporting cbpf), frequency_drop and quota as the eBPF stateful
|
|
classifier (needs ebpf). Markers and packet capture are served by
|
|
uBridge's AF_PACKET modules on the veth host end.
|
|
|
|
``bridge`` may be None: the anchor is then bridged by an Ethernet switch
|
|
(its own kernel bridge absorbed it) and the node applies only carrier,
|
|
markers and impairments — never bridge membership.
|
|
"""
|
|
|
|
type: BridgeNIOType
|
|
bridge: Optional[str] = Field(
|
|
None,
|
|
description="Kernel bridge name e.g. gns3a1b2c3d4e5; None when an Ethernet switch "
|
|
"owns the anchor's bridge membership (absorbed link)",
|
|
)
|
|
suspend: Optional[bool] = Field(None, description="Suspend the NIO")
|
|
filters: Optional[dict] = Field(
|
|
None,
|
|
description="Packet filters served on the veth host end (netem surface + extensions via one tc netem qdisc, "
|
|
"bpf via cls_bpf, frequency_drop/quota via the eBPF classifier)",
|
|
)
|
|
markers: Optional[dict] = Field(None, description="Traffic-insight markers (attached to the veth host end)")
|
|
|
|
|
|
class AnchorNIOType(str, Enum):
|
|
anchor = "nio_anchor"
|
|
|
|
|
|
class AnchorNIO(BaseModel):
|
|
"""
|
|
Anchor-absorbing Network Input/Output properties, for a node that owns a
|
|
kernel bridge (the Ethernet switch): the named foreign interface — the
|
|
peer's kernel-datapath anchor — is joined to the node's own bridge with
|
|
the node's port settings applied to it. The mirror of BridgeNIO; filters,
|
|
markers and capture ride the NIO and attach to the named anchor.
|
|
"""
|
|
|
|
type: AnchorNIOType
|
|
anchor: str = Field(..., description="Host interface to absorb e.g. gv00010203e0p0")
|
|
suspend: Optional[bool] = Field(None, description="Suspend the NIO")
|
|
filters: Optional[dict] = Field(
|
|
None,
|
|
description="Packet filters served on the absorbed anchor (netem surface + extensions via one tc netem "
|
|
"qdisc, bpf via cls_bpf, frequency_drop/quota via the eBPF classifier)",
|
|
)
|
|
markers: Optional[dict] = Field(None, description="Traffic-insight markers (attached to the absorbed anchor)")
|
|
|
|
|
|
class MarkerToggle(BaseModel):
|
|
"""
|
|
Body for the per-marker enable/disable toggle endpoint: flips a running
|
|
uBridge marker filter with ``enable_packet_filter on|off`` (no NIO rebuild,
|
|
so the pcap identity and emitted counter are preserved).
|
|
"""
|
|
|
|
enabled: bool
|
|
|
|
|
|
class MarkerRebuild(BaseModel):
|
|
"""
|
|
Body for the per-marker rebuild endpoint: re-install a single uBridge marker
|
|
filter with new BPF/tag/direction via ``delete_packet_filter`` + add (NOT a
|
|
bridge-wide reset), so sibling markers keep their pcaps open. The marker's
|
|
own pcap is reopened by uBridge on re-add (new capture session for the new
|
|
BPF), which is expected.
|
|
"""
|
|
|
|
bpf: str
|
|
tag: Optional[int] = None
|
|
direction: Optional[str] = None
|
|
enabled: bool = True
|
|
link_id: str = ""
|
|
|
|
|
|
class BatchNIOEntry(BaseModel):
|
|
"""
|
|
A single NIO binding to create as part of a project-wide batch.
|
|
"""
|
|
|
|
node_id: str = Field(..., description="Node the NIO is attached to")
|
|
adapter_number: int = Field(0, ge=0, description="Adapter number")
|
|
port_number: int = Field(0, ge=0, description="Port number")
|
|
nio: Union[UDPNIO, BridgeNIO, AnchorNIO] = Field(..., description="NIO settings")
|
|
|
|
|
|
class BatchNIOCreate(BaseModel):
|
|
"""
|
|
Body for the project-wide batch NIO endpoint: create many NIO bindings in a
|
|
single request (used during project open) to avoid one HTTP round-trip per
|
|
NIO between controller and compute.
|
|
"""
|
|
|
|
nios: List[BatchNIOEntry] = Field(..., description="NIO bindings to create")
|