Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3dcaa9d
Add MSTP implementation and update network/frontend files
RamProsad2 Dec 13, 2025
8720b2d
improve for multi bridge
RamProsad2 Dec 13, 2025
d6dadbf
delete a=extra line
RamProsad2 Dec 13, 2025
05ae781
Apply black formatting
RamProsad2 Dec 14, 2025
196b829
fix flake8
RamProsad2 Dec 15, 2025
0f302a5
fix test
RamProsad2 Dec 15, 2025
97b2895
fix test
RamProsad2 Dec 15, 2025
78e7bcf
fix black
RamProsad2 Dec 15, 2025
c6aaff4
fix module
RamProsad2 Dec 15, 2025
125c89b
Delete front/tests/test_mstp.py
RamProsad2 Dec 16, 2025
040db82
Add MSTP test
RamProsad2 Dec 16, 2025
6b8cc92
Add test
RamProsad2 Dec 16, 2025
5a41306
balck problem
RamProsad2 Dec 16, 2025
a71b8a7
Remove unnecessary test_mstp.py file
RamProsad2 Dec 16, 2025
ab73795
Delete all tests folder and its contents
RamProsad2 Dec 17, 2025
a518e27
test
RamProsad2 Dec 17, 2025
d8e5a43
Update MSTP logic
RamProsad2 Dec 22, 2025
309b530
Update logic
RamProsad2 Dec 22, 2025
c83598a
Update logic
RamProsad2 Dec 22, 2025
9a0af7c
Update logic
RamProsad2 Dec 22, 2025
f641283
Update network py
RamProsad2 Dec 24, 2025
1db1d52
add mstp file
RamProsad2 Dec 25, 2025
ac920e0
add mstp file
RamProsad2 Dec 25, 2025
95cc771
add mstp file
RamProsad2 Dec 25, 2025
bb5ec5b
Update MSTP functionality, VLAN, VXLAN, and related tests
RamProsad2 Feb 19, 2026
41bf98f
change selenium-hub to localhost
RamProsad2 Feb 20, 2026
1875a8d
fix return animation, pcaps
RamProsad2 Feb 20, 2026
0c76301
change important_packets
RamProsad2 Feb 20, 2026
c872403
fix tasks
RamProsad2 Feb 20, 2026
c811315
fix tasks
RamProsad2 Feb 20, 2026
e2518f0
fix linter
RamProsad2 Feb 20, 2026
879d8ef
fix linter
RamProsad2 Feb 20, 2026
d69354e
fix blank
RamProsad2 Feb 21, 2026
3c15f1d
format with black
RamProsad2 Feb 21, 2026
bb85dc7
style: fix formatting with black
RamProsad2 Feb 23, 2026
ab8686d
style: fix formatting with fleke8
RamProsad2 Feb 23, 2026
feaa84a
add front/src/static/shark_script.js
RamProsad2 Feb 23, 2026
8bfc9b5
Resolve merge conflict
RamProsad2 Feb 23, 2026
87515d8
Resolve front/src/static/shark_script.js
RamProsad2 Feb 23, 2026
b8d1600
Resolve lint jobs and emulator
RamProsad2 Feb 23, 2026
4556359
Resolve lint jobs
RamProsad2 Feb 23, 2026
5280d90
Resolve lint jobs
RamProsad2 Feb 23, 2026
71f83be
Merge branch 'main' into mstp-update
RamProsad2 Mar 10, 2026
56655b1
Resolve back/mstp
RamProsad2 Mar 12, 2026
d0fb632
Resolve back/mstp
RamProsad2 Mar 13, 2026
2e546ae
Resolve back/mstp
RamProsad2 Mar 13, 2026
255adcb
Resolve black back/mstp
RamProsad2 Mar 13, 2026
8d5b990
Merge branch 'main' into mstp-update
i1ya Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions back/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,27 @@ RUN apt-get update \
libpcap-dev \
libbsd-dev \
openvswitch-switch \
autoconf \
automake \
libtool \
&& pip3 install --upgrade pip \
&& rm -rf /var/lib/apt/lists/* \
&& touch /etc/network/interfaces \
&& mkdir /opt/mininet_dependencies

# Install mimidump
RUN git clone https://github.com/mimi-net/mimidump.git \
&& cd mimidump && git checkout 32e6e5e && make prefix=/usr install && cd .. && rm -rf mimidump

# Install mstpd for MSTP support
RUN git clone https://github.com/mstpd/mstpd.git /tmp/mstpd \
&& cd /tmp/mstpd \
&& ./autogen.sh \
&& ./configure \
&& make \
&& make install \
&& rm -rf /tmp/mstpd

WORKDIR /app
ADD ./requirements.txt /app/requirements.txt
RUN pip3 install -r requirements.txt
Expand Down
24 changes: 13 additions & 11 deletions back/src/emulator.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import os
import os.path
import subprocess
import json

from ipmininet.ipnet import IPNet
from jobs import Jobs
from network_schema import Job, Network
from pkt_parser import create_pkt_animation
from src.jobs import Jobs
from src.network_schema import Job, Network
from src.pkt_parser import create_pkt_animation
from mininet.log import setLogLevel, error
from network_topology import MiminetTopology
from network import MiminetNetwork
from src.network_topology import MiminetTopology
from src.network import MininetNetwork
from typing import Union, Tuple, List


def emulate(
network: Network,
) -> tuple[list[list], list[tuple[bytes, str]]]:
) -> tuple[str, list[tuple[bytes, str]]]:
"""Run mininet emulation.

Args:
Expand All @@ -34,18 +36,18 @@ def emulate(
f"Текущее количество: {len(network.jobs)}"
)
sleep_jobs = [j for j in network.jobs if j.job_id == 7]
total_time = sum(int(j.arg_1) for j in sleep_jobs)
total_time = sum(int(j.arg_1 or 0) for j in sleep_jobs)
if total_time > 60 or total_time < 0:
raise ValueError(
f"Превышен лимит! В сети максимальное количество команд sleep {MAX_TIME_SLEEP})."
)

if len(network.jobs) == 0:
return [], []
return "[]", []

try:
topo = MiminetTopology(network)
net = MiminetNetwork(topo, network)
net = MininetNetwork(topo, network)

net.start()

Expand All @@ -68,12 +70,12 @@ def emulate(
animation, pcaps = create_animation(topo.interfaces)
animation = group_packets_by_time(animation)

return animation, pcaps
return json.dumps(animation), pcaps


def create_animation(
interfaces_info,
) -> tuple[list[list] | list, list | list[tuple[bytes, str]]]:
) -> Tuple[Union[List[list], list], Union[list, List[Tuple[bytes, str]]]]:
"""Creates an animation using saved pcap files.

Args:
Expand Down
20 changes: 11 additions & 9 deletions back/src/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import ipaddress
import time
from netaddr import EUI, AddrFormatError
from typing import Any, Callable, List, Dict
from network_schema import Job
from typing import Any, Callable, List, Dict, Tuple
from src.network_schema import Job
from mininet.log import info
from ipmininet.host.config.dnsmasq import Dnsmasq

Expand Down Expand Up @@ -59,7 +59,7 @@ def traceroute_options_filter(arg: str) -> str:
return filter_arg_for_options(arg, flags_without_args, flags_with_args)


def udp_tcp_args_checker(ip, size, port) -> bool:
def udp_tcp_args_checker(ip: str, size: str, port: str) -> bool:
"""Check all args in tcp and udp data handler on correct"""
if not valid_ip(ip):
return False
Expand Down Expand Up @@ -227,7 +227,7 @@ def sleep_handler(job: Job, job_host: Any) -> None:
arg_time = job.arg_1
if not valid_sleep(arg_time):
return
time.sleep(int(arg_time))
time.sleep(int(arg_time or 0))


def ping_handler(job: Job, job_host: Any) -> None:
Expand All @@ -243,7 +243,7 @@ def ping_handler(job: Job, job_host: Any) -> None:
def ping_with_options_handler(job: Job, job_host: Any) -> None:
"""Execute ping with options"""

arg_opt = job.arg_1
arg_opt = job.arg_1 or ""
arg_ip = job.arg_2

if not valid_ip(arg_ip):
Expand All @@ -255,14 +255,16 @@ def ping_with_options_handler(job: Job, job_host: Any) -> None:
job_host.cmd(f"ping -c 1 {arg_opt} {arg_ip}")


def get_sending_data_argument(job: Job) -> tuple[str | int, str | int, str | int]:
def get_sending_data_argument(
job: Job,
) -> Tuple[str, str, str]:
"""Method for get arguments for sending udp and tcp data"""

arg_size = job.arg_1
arg_ip = job.arg_2
arg_port = job.arg_3

return arg_size, arg_ip, arg_port
return str(arg_size or ""), str(arg_ip or ""), str(arg_port or "")


def sending_udp_data_handler(job: Job, job_host: Any) -> None:
Expand Down Expand Up @@ -294,7 +296,7 @@ def sending_tcp_data_handler(job: Job, job_host: Any) -> None:
def traceroute_handler(job: Job, job_host: Any) -> None:
"""Method for executing traceroute"""

arg_opt = job.arg_1
arg_opt = job.arg_1 or ""
arg_ip = job.arg_2

if not valid_ip(arg_ip):
Expand Down Expand Up @@ -424,7 +426,7 @@ def arp_handler(job: Job, job_host: Any) -> None:

def subinterface_with_vlan(job: Job, job_host: Any) -> None:
"""Method for adding subinterface with vlan"""
arg_intf = job.arg_1
arg_intf = job.arg_1 or ""
arg_ip = job.arg_2
arg_mask = job.arg_3
arg_vlan = job.arg_4
Expand Down
161 changes: 161 additions & 0 deletions back/src/net_utils/mstp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""MSTP (Multiple Spanning Tree Protocol) configuration utilities."""

import shlex
from ipmininet.ipnet import IPNet # type: ignore
from ipmininet.ipswitch import IPSwitch # type: ignore

from src.net_utils.mstp_schema import MstInstance
from src.network_schema import Node, NodeInterface


def _validate_mstp_priority(priority: int, label: str) -> None:
if not 0 <= priority <= 61440 or priority % 4096 != 0:
raise ValueError(
f"{label} priority must be between 0 and 61440 in steps of 4096 (got {priority})."
)


def setup_mstp(net: IPNet, nodes: list[Node]) -> None:
"""Configure MSTP bridges for all L2 switches with MSTP enabled."""
for node in nodes:
if node.config.type == "l2_switch" and node.config.stp == 3:
switch = net.get(node.data.id)
configure_mstp_bridge(switch, node)


def configure_mstp_bridge(switch: IPSwitch, node: Node) -> None:
"""Configure MSTP on a switch using Linux bridge + mstpd."""
config = node.config
bridge_name = f"br-{switch.name}"
bridge_name_q = shlex.quote(bridge_name)

if config.priority is not None:
_validate_mstp_priority(config.priority, "CIST")

if config.mst_instances:
for mst_instance in config.mst_instances:
if mst_instance.priority is not None:
_validate_mstp_priority(
mst_instance.priority, f"MST instance {mst_instance.instance_id}"
)

switch.cmd(f"ip link add name {bridge_name_q} type bridge")
switch.cmd(f"ip link set dev {bridge_name_q} type bridge vlan_filtering 1")
switch.cmd(f"ip link set dev {bridge_name_q} up")

for iface in node.interface:
switch.cmd(f"ip link set {shlex.quote(iface.name)} master {bridge_name_q}")

result = switch.cmd("which mstpctl 2>/dev/null || echo 'not found'")

if "not found" in result:
print(f"Warning: mstpctl not found, using STP fallback for {switch.name}")
switch.cmd(f"ip link set {bridge_name_q} type bridge stp_state 1")
if config.priority is not None:
switch.cmd(
f"ip link set {bridge_name_q} type bridge priority {config.priority}"
)
return

switch.cmd(f"mstpctl addbridge {bridge_name_q}")
switch.cmd(f"mstpctl setforcevers {bridge_name_q} mstp")

if config.mst_region:
switch.cmd(
f"mstpctl setmstconfid {bridge_name_q} 0 {shlex.quote(config.mst_region)}"
)

if config.mst_revision is not None:
switch.cmd(f"mstpctl setmstconfid {bridge_name_q} 1 {config.mst_revision}")

if config.priority is not None:
switch.cmd(f"mstpctl settreeprio {bridge_name_q} 0 {config.priority}")

if config.mst_instances:
for mst_instance in config.mst_instances:
configure_mst_instance(switch, bridge_name, mst_instance)

for iface in node.interface:
configure_mstp_interface_vlan(switch, bridge_name, iface)


def configure_mst_instance(
switch: IPSwitch, bridge_name: str, mst_instance: MstInstance
) -> None:
"""Configure a single MST instance with VLAN mappings."""
bridge_name_q = shlex.quote(bridge_name)
instance_id = mst_instance.instance_id
vlans = mst_instance.vlans
priority = mst_instance.priority

switch.cmd(f"mstpctl createtree {bridge_name_q} {instance_id}")

if priority is not None:
_validate_mstp_priority(priority, f"MST instance {instance_id}")
switch.cmd(f"mstpctl settreeprio {bridge_name_q} {instance_id} {priority}")

fid = instance_id

for vlan in vlans:
switch.cmd(f"mstpctl setvid2fid {bridge_name_q} {vlan}:{fid}")

switch.cmd(f"mstpctl setfid2mstid {bridge_name_q} {fid}:{instance_id}")


def configure_mstp_interface_vlan(
switch: IPSwitch, bridge_name: str, iface: NodeInterface
) -> None:
"""Configure VLAN settings on an interface for MSTP."""
if iface.vlan is None:
return

iface_name_q = shlex.quote(iface.name)
switch.cmd(f"bridge vlan del dev {iface_name_q} vid 1 2>/dev/null || true")

if iface.type_connection == 0:
vlan = iface.vlan
switch.cmd(f"bridge vlan add dev {iface_name_q} vid {vlan} pvid untagged")
elif iface.type_connection == 1:
vlans = iface.vlan if isinstance(iface.vlan, list) else [iface.vlan]
for vlan in vlans:
switch.cmd(f"bridge vlan add dev {iface_name_q} vid {vlan}")
else:
vlans = iface.vlan if isinstance(iface.vlan, list) else [iface.vlan]
for vlan in vlans:
switch.cmd(f"bridge vlan add dev {iface_name_q} vid {vlan}")


def clean_mstp_bridges(net: IPNet, nodes: list[Node]) -> None:
"""Clean up MSTP bridges.

Args:
net (IPNet): The network instance.
nodes (list[Node]): List of network nodes.
"""
for node in nodes:
if node.config.type == "l2_switch" and node.config.stp == 3:
switch = net.get(node.data.id)
bridge_name = f"br-{switch.name}"
bridge_name_q = shlex.quote(bridge_name)

switch.cmd(f"mstpctl delbridge {bridge_name_q} 2>/dev/null || true")

switch.cmd(f"ip link set {bridge_name_q} down 2>/dev/null || true")
switch.cmd(f"ip link del {bridge_name_q} 2>/dev/null || true")


def get_mst_instance_for_vlan(node: Node, vlan_id: int) -> int:
"""Get the MST instance ID for a given VLAN.

Args:
node (Node): The node configuration.
vlan_id (int): The VLAN ID to look up.

Returns:
int: MST instance ID (0 = CIST if not mapped).
"""
if node.config.mst_instances:
for mst_instance in node.config.mst_instances:
if vlan_id in mst_instance.vlans:
return mst_instance.instance_id
return 0 # Default to CIST (Common and Internal Spanning Tree)
9 changes: 9 additions & 0 deletions back/src/net_utils/mstp_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from dataclasses import dataclass
from typing import List, Optional


@dataclass
class MstInstance:
instance_id: int
vlans: List[int]
priority: Optional[int] = None
6 changes: 3 additions & 3 deletions back/src/net_utils/vlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ipmininet.ipswitch import IPSwitch
from ipmininet.ipovs_switch import IPOVSSwitch

from network_schema import Node, NodeInterface
from src.network_schema import Node, NodeInterface


def setup_vlans(net: IPNet, nodes: list[Node]) -> None:
Expand All @@ -23,9 +23,9 @@ def setup_vlans(net: IPNet, nodes: list[Node]) -> None:
vlan = iface.vlan
type_connection = iface.type_connection
if vlan is not None:
if type_connection == 0: # Access link
if type_connection == 0 and isinstance(vlan, int): # Access link
configure_access(switch, iface.name, vlan)
elif type_connection == 1: # Trunk link
elif type_connection == 1 and isinstance(vlan, list): # Trunk link
configure_trunk(switch, iface.name, sorted(vlan))


Expand Down
4 changes: 3 additions & 1 deletion back/src/net_utils/vxlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from ipmininet.ipnet import IPNet

from network_schema import Node
from typing import Any

Node = Any


def setup_vtep_interfaces(net: IPNet, nodes: list[Node]) -> None:
Expand Down
Loading
Loading