Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions back/src/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ def emulate(

# Validate job limit
MAX_JOBS_COUNT = 30
MAX_TIME_SLEEP = 60
if len(network.jobs) > MAX_JOBS_COUNT:
raise ValueError(
f"Превышен лимит! В сети максимальное количество команд ({MAX_JOBS_COUNT}). "
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)
if total_time > 60 or total_time < 0:
raise ValueError(
f"Превышен лимит! В сети максимальное количество команд sleep {MAX_TIME_SLEEP})."
)

if len(network.jobs) == 0:
return [], []
Expand Down
29 changes: 28 additions & 1 deletion back/src/jobs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
import shlex
import ipaddress

import time
from netaddr import EUI, AddrFormatError
from typing import Any, Callable, List, Dict
from network_schema import Job
Expand Down Expand Up @@ -194,6 +194,31 @@ def valid_iface(iface) -> bool:
return True


def valid_sleep(time) -> bool:
try:
_ = int(time)
except (ValueError, TypeError):
return False
if int(time) > 50 or int(time) <= 0:
return False

return True


def link_down_handler(job: Job, job_host: Any) -> None:
arg_interface = job.arg_1
if not net_dev_checker(arg_interface):
return
job_host.cmd(f"ip link set {arg_interface} down")


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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не очень понимаю, у нас весь эмулятор будет засыпать когда для какого-то хоста включается sleep? Это точно то, что требуется?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кажется что да, для демонстрации отказоустойчивости rstp нужно время на подумать после отключения линка.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кажется что да, для демонстрации отказоустойчивости rstp нужно время на подумать после отключения линка.

мне по описанию PR показалось, что должно в сон уходить конкретное устройство. Не очень понимаю как тогда эта задача должна работать. Добавляешь её и весь эмулятор спит, не запуская другие задачи, но устройства при этом продолжают работать (отправлять/принимать пакеты)?

Copy link
Collaborator Author

@ilq1 ilq1 Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

да, именно так и работает. при этом у rstp/stp появляется время на перерасчет маршрута. Я ориентировался на то, что подход с time.sleep также используется для расчета stp/rstp перед тем как команды начинают выполнятся

Copy link
Contributor

@d-zaytsev d-zaytsev Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

да, именно так и работает. при этом у rstp/stp появляется время на перерасчет маршрута. Я ориентировался на то, что подход с time.sleep также используется для расчета stp/rstp перед тем как команды начинают выполнятся

А имеет значение на каком устройстве запущена эта задача и какая она в очереди? Её нужно добавлять сразу после задачи с STP или это не обязательно?

Просто как будто делать отдельной задачей для устройства команду, которая стопарит весь эмулятор и при этом нужна только для STP странно. Может тогда сделать это как параметр STP. Ползунок какой-то добавить с временем ожидания и соответственно после запуска STP делать sleep. Тогда понятно будет и зачем он нужен и когда сработает.

Ну либо я просто не понимаю как оно должно использоваться на практике

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

думаю что на каком устройстве запущена значение не имеет, а вот в каком порядке имеет. ее нужно добавлять не после задачи stp (вообще в коде stp это даже не задача в как другие в файлике jobs, если stp активирован он настраивается раньше всех задач) а после задачи link_down

приведу пример использования
строим такую сеть: https://miminet.ru/web_network?guid=c26e21be-d2af-4394-8ad8-dc4b0035c32f

затем пишем в хост 1 задачу пинг до хоста 2
пишем link down на свиче l2sw1 (например отключим линк до l2sw3)
потом пишем sleep 7 (примерно столько нужно чтобы rstp перестроился)
затем отправляем еще один пинг из хоста 1 в хост 2 и смотрим результат
если нам повезло и первый пинг пошел по пути который затем мы отключим, то после того как произойдет Link down rstp начнет перестраиваться и в итоге второй пинг пойдет уже по другому маршруту
(у всех задач которые я использовал наименьший приоритет выполнения, поэтому они все будут выполнятся в порядке добавления)
в целом идея добавлении задачи sleep возникла тогда, когда я осознал что для перестройки rstp и stp нужно разное время. сначала я просто сделал так, чтобы после link down эмулятор засыпал, но столкнулся с проблемой выше + пользователю ничего не мешает сделать обычный link down без всяких stp/rstp, не придется же ему ждать несколько секунд дополнительно



def ping_handler(job: Job, job_host: Any) -> None:
"""Execute ping -c 1"""
arg_ip = job.arg_1
Expand Down Expand Up @@ -461,6 +486,8 @@ def __init__(self, job: Job, job_host: Any, **kwargs) -> None:
3: sending_udp_data_handler,
4: sending_tcp_data_handler,
5: traceroute_handler,
6: link_down_handler,
7: sleep_handler,
100: ip_addr_add_handler,
101: iptables_handler,
102: ip_route_add_handler,
Expand Down
110 changes: 110 additions & 0 deletions back/tests/test_json/link_down_answer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
[
[
{
"data": {
"id": "",
"label": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"type": "packet"
},
"config": {
"type": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"path": "edge_mjcztrism9ezamqqda",
"source": "host_1",
"target": "l2sw1",
"loss_percentage": 0.0,
"duplicate_percentage": 0.0
},
"timestamp": ""
}
],
[
{
"data": {
"id": "",
"label": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"type": "packet"
},
"config": {
"type": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"path": "edge_mjcztsiqna2lxz6nnwd",
"source": "l2sw1",
"target": "l2sw2",
"loss_percentage": 0.0,
"duplicate_percentage": 0.0
},
"timestamp": ""
}
],
[
{
"data": {
"id": "",
"label": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"type": "packet"
},
"config": {
"type": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"path": "edge_mjcztrism9ezamqqda",
"source": "host_1",
"target": "l2sw1",
"loss_percentage": 0.0,
"duplicate_percentage": 0.0
},
"timestamp": ""
}
],
[
{
"data": {
"id": "",
"label": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"type": "packet"
},
"config": {
"type": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"path": "edge_mjcztsiqna2lxz6nnwd",
"source": "l2sw1",
"target": "l2sw2",
"loss_percentage": 0.0,
"duplicate_percentage": 0.0
},
"timestamp": ""
}
],
[
{
"data": {
"id": "",
"label": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"type": "packet"
},
"config": {
"type": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"path": "edge_mjcztrism9ezamqqda",
"source": "host_1",
"target": "l2sw1",
"loss_percentage": 0.0,
"duplicate_percentage": 0.0
},
"timestamp": ""
}
],
[
{
"data": {
"id": "",
"label": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"type": "packet"
},
"config": {
"type": "ARP-request\nWho has 192.168.0.2? Tell 192.168.0.1",
"path": "edge_mjcztsiqna2lxz6nnwd",
"source": "l2sw1",
"target": "l2sw2",
"loss_percentage": 0.0,
"duplicate_percentage": 0.0
},
"timestamp": ""
}
]
]
181 changes: 181 additions & 0 deletions back/tests/test_json/link_down_network.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{
"nodes": [
{
"classes": [
"host"
],
"config": {
"default_gw": "",
"label": "host_1",
"type": "host"
},
"data": {
"id": "host_1",
"label": "host_1"
},
"interface": [
{
"connect": "edge_mjcztrism9ezamqqda",
"id": "iface_60266344",
"ip": "192.168.0.1",
"name": "iface_60266344",
"netmask": 24
}
],
"position": {
"x": 208.25,
"y": 192.5999984741211
}
},
{
"classes": [
"l2_switch"
],
"config": {
"label": "l2sw1",
"stp": 0,
"type": "l2_switch"
},
"data": {
"id": "l2sw1",
"label": "l2sw1"
},
"interface": [
{
"connect": "edge_mjcztrism9ezamqqda",
"id": "l2sw1_1",
"name": "l2sw1_1",
"type_connection": null,
"vlan": null
},
{
"connect": "edge_mjcztsiqna2lxz6nnwd",
"id": "l2sw1_2",
"name": "l2sw1_2",
"type_connection": null,
"vlan": null
}
],
"position": {
"x": 277.75,
"y": 184.8000030517578
}
},
{
"classes": [
"l2_switch"
],
"config": {
"label": "l2sw2",
"stp": 0,
"type": "l2_switch"
},
"data": {
"id": "l2sw2",
"label": "l2sw2"
},
"interface": [
{
"connect": "edge_mjcztsiqna2lxz6nnwd",
"id": "l2sw2_1",
"name": "l2sw2_1",
"type_connection": null,
"vlan": null
},
{
"connect": "edge_mjczttixwtd2jtzl29",
"id": "l2sw2_2",
"name": "l2sw2_2",
"type_connection": null,
"vlan": null
}
],
"position": {
"x": 345.75,
"y": 178.3000030517578
}
},
{
"classes": [
"host"
],
"config": {
"default_gw": "",
"label": "host_2",
"type": "host"
},
"data": {
"id": "host_2",
"label": "host_2"
},
"interface": [
{
"connect": "edge_mjczttixwtd2jtzl29",
"id": "iface_65503551",
"ip": "192.168.0.2",
"name": "iface_65503551",
"netmask": 24
}
],
"position": {
"x": 410.75,
"y": 182.5999984741211
}
}
],
"edges": [
{
"data": {
"duplicate_percentage": 0,
"id": "edge_mjcztrism9ezamqqda",
"loss_percentage": 0,
"source": "host_1",
"target": "l2sw1"
}
},
{
"data": {
"duplicate_percentage": 0,
"id": "edge_mjcztsiqna2lxz6nnwd",
"loss_percentage": 0,
"source": "l2sw1",
"target": "l2sw2"
}
},
{
"data": {
"duplicate_percentage": 0,
"id": "edge_mjczttixwtd2jtzl29",
"loss_percentage": 0,
"source": "l2sw2",
"target": "host_2"
}
}
],
"jobs": [
{
"arg_1": "l2sw2_2",
"arg_2": "host_2",
"host_id": "l2sw2",
"id": "179fdf3667d84b6ca28e127bf12387f6",
"job_id": 6,
"level": 0,
"print_cmd": "link down host_2"
},
{
"arg_1": "192.168.0.2",
"host_id": "host_1",
"id": "a4e92ee1d12b4f499821de2f03693be4",
"job_id": 1,
"level": 1,
"print_cmd": "ping -c 1 192.168.0.2"
}
],
"config": {
"zoom": 2,
"pan_x": -183.5437076535751,
"pan_y": 107.00501192502065
},
"pcap": [],
"packets": ""
}
2 changes: 1 addition & 1 deletion front/.env
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ POSTGRES_HOST=172.18.0.4
# YANDEX_POSTGRES_SSLMODE=verify-full

# Режим работы: dev (локальный PostgreSQL) или prod (Yandex Cloud PostgreSQL)
MODE=dev
MODE=prod
Loading