Repository navigation
Releases: EuZireael/SCADA_rust
Release list
v0.4.1
Настройки тегов из channel.tag-settings, быстрый возврат связи по OPC UA, защита от лавины полной переотправки в Kafka,
опрос контроллера сразу после записи, сравнение старой SCADA и шлюза на одном эмуляторе. Формат сообщений Kafka, ключи и
топики телеметрии и команд не менялись; новый входной топик channel.tag-settings необязателен (нет топика — работа по YAML).
Что может задеть при обновлении
GATEWAY_SUPERVISE_INTERVAL_MSпо умолчанию 200 вместо 10000. Пауза между попытками подключения к контроллеру: первые 300
неудачных попыток (≈ минута) — 0,2 с, дальше не реже раза в 2 с; после отказа сервера (пароль, защита, лимит сессий) — не короче 2 с.
Если в окружении значение задано явно, оно сохраняется (и никогда не сокращается). Чтобы вернуть прежнее поведение —
GATEWAY_SUPERVISE_INTERVAL_MS=10000.
Добавлено
- Настройки тегов из топика
channel.tag-settings(контракт бэкенда от 19.10.2026).channelпубликует в сжатый топик период опроса и зону
нечувствительности канала (ключ — имя тега,null— настройки сняты); шлюз читает топик целиком с начала (без группы, каждому экземпляру нужна полная
копия) и применяет записи на лету, без перезапуска. Период: тег сpoll.mode = "period"читается не чащеperiodMs: у OPC UA читаются только
«созревшие» узлы, у PAC и Modbus снимок/блоки читаются целиком, а в обработку и Kafka идут созревшие теги; цикл контроллера по-прежнему определяют
pollingRateиз YAML, чаще цикла тег не читается. Зона:deadbandподменяетhistory.deadbandтега в локальной истории; фильтр публикации в Kafka
не меняется. Тег без записи и tombstone — значения из YAML. Нет топика — шлюз пишет предупреждение, повторяет раз в 10 с и работает по YAML.
Настройки:CHANNEL_TAG_SETTINGS_TOPIC(channel.tag-settings),GATEWAY_TAG_SETTINGS_ENABLED(true); метрикаscada_tag_settings_tags.
Тесты: разбор записи, расписание (каждый N-й цикл, дрожание тикера, пропущенное не догоняется), зона истории, сквознойtests/tag_settings.rs.
Исправлено
- Возврат связи по OPC UA после обрыва контроллера занимал 5–10 с и больше. Две причины. (1) Закрытие уже оборвавшейся сессии
ждало ответа от мёртвого соединения до конца таймаута операции (5 с по умолчанию), и только потом шлюз пробовал подключиться заново;
теперь, если цикл событий сессии завершён, закрывать нечего. (2) Между неудачными попытками подключения стояла фиксированная пауза
GATEWAY_SUPERVISE_INTERVAL_MS(10 с), а после обрыва ещё 1 с: контроллер, вернувшийся через пару секунд, ждал шлюз до 10 с.
Теперь по умолчанию пауза 0,2 с (первые 300 неудачных попыток, ≈ минута; дальше не реже раза в 2 с, чтобы журнал не рос при
многодневной недоступности контроллера). Быстро повторяются только попытки после потери связи (порт закрыт, обрыв, таймаут); если
сервер ответил отказом (неверный пароль, защита, лимит сессий), пауза не короче 2 с: иначе каждая попытка оставляла бы на сервере
незавершённую сессию (на симуляторе тест неверного пароля за секунды исчерпал лимит сессий и уронил следующие тесты).
Изменение поведения: значение по умолчаниюGATEWAY_SUPERVISE_INTERVAL_MS10000 → 200.
На симуляторе возврат после обрыва в 5 с / 30 с: было 5,4 с и до 10 с, стало ≈ 0,15 с (по PAC — 0,65 с: ждёт цикла опроса). Цена на недоступный контроллер:
0,017 ядра и 1,7 КБ/с журнала, нагрузка на Kafka не меняется. Тестыpoller::tests::reconnect_is_fast_at_first_and_slows_down_for_a_long_outage,
a_longer_configured_pause_is_never_shortened. - PLC-симулятор не принимался драйвером старой SCADA (EasyDriver). Ответы PAC шли без завершающего нуля (драйвер исполняет их как
C-строки и читал хвост прошлого ответа), на GET_DEVICES симулятор отвечал другим форматом, чем прошивка (снимокt={…}), а
рецептурный менеджер был набором приборовRECMAN[n], а не массивомt.RECMAN[n]. Теперь реальный драйвер находит все 2 741
тегов станции «Танки нормализации» (раньше 222–253 не находил). Симулятор также понимает формат записи драйвера EasyDriver
(__OBJECT2:set_cmd( "S_PAR_F", 1, 10 )— двойные кавычки, пробелы,__RECMAN[n]) и раз в 5 с пишет в журнал число запросов состояния. Нужно для
замеров «старая SCADA против шлюза» (docs/OLD_VS_NEW.md). - Полная переотправка всех тегов при «мигающем» брокере Kafka. Каждое восстановление доставки после сбоя запускало отправку всех тегов всех
контроллеров заново (при 54 800 тегах — залп из 54 800 сообщений), и при частых сбоях и восстановлениях шлюз почти не публиковал «по
исключению». Теперь флаг сбоя снимается как раньше, но новая полная отправка идёт не чаще раза в 30 с; недоставленное в паузе уходит
с обычной плановой полной отправкой (GATEWAY_PUBLISH_FULL_RESEND_MS). Тестkafka::tests::flapping_broker_causes_one_resync_per_interval.
(Основная причина шторма — переполнение очереди продюсера принималось за сбой доставки — исправлена раньше, см. ниже.) - OPC UA-фасад
ptusa-opcuaзависал целиком после нескольких записей. Обработчик записи (синхронный, под блокировкой адресного
пространства библиотеки OPC UA) ждал ответа прошивки через драйвер ввода-вывода основного рантайма, а этот драйвер в тот же момент
держал рабочий поток, остановленный параллельным запросом чтения на той же блокировке. Ответ прошивки некому было принять, сервер
переставал отвечать на всё, в том числе на новые подключения (у шлюза — таймауты записи и чтения, «handshake завис», потеря связи).
Чаще всего проявлялось при быстром опросе фасада (период 100 мс и меньше). Теперь команды идут по отдельному соединению в отдельном
рантайме со своим драйвером; после обрыва соединение открывается заново, команда повторяется один раз. Тест
writes_during_fast_polling_do_not_freeze_the_server(300 записей при опросе 5 мс и параллельном чтении) на прежнем коде виснет. - Недоступная Kafka останавливала опрос ПЛК: ложная «потеря связи» и кадры BAD. Приём команд пересоздаёт консьюмера в цикле, пока
брокера нет; создание (fetch_metadata, чтение позиций — до 10 с каждое) шло синхронно прямо в асинхронной задаче и держало
рабочий поток рантайма, а вместе с ним задачи, лежащие в его очереди, в том числе ответ на чтение из ПЛК. Чтение упиралось в таймаут
5 с, шлюз писал «Потеряна связь» и слал монитору BAD, хотя ПЛК был жив. Нашёл дымовой тест стенда (сбой Kafka на 45 с): шлюз
теперь создаёт консьюмера в отдельном блокирующем потоке (spawn_blocking), опрос от Kafka не зависит. - Переполнение очереди продюсера Kafka запускало лавину повторной отправки. Сообщение, которое очередь librdkafka
(по умолчанию 100 000) не приняла, считалось сбоем доставки; следующая удачная отправка запускала повторную отправку
всех тегов, она снова переполняла очередь, и так по кругу. На стенде в 750 тысяч тегов шлюз публиковал каждое
снятое значение (785 тысяч сообщений в секунду вместо ожидаемых 250 тысяч) и накопил 14,8 млн ошибок за минуту.
Теперь отказ очереди — не сбой доставки: тег забывает, что он опубликован, и уходит следующим же циклом опроса,
остальные теги это не затрагивает. Новая метрикаscada_kafka_queue_full_total; предупреждение в журнале — не чаще
раза в 30 с. На реальной станции (2–3 тысячи тегов) очередь не переполняется, поведение не меняется.
Изменено
- Симулятор по умолчанию отдаёт ВСЕ каналы базы каналов станции только по OPC UA. Новая конфигурация
simulator/config/station_opcua.yaml— 2647 каналовconfig/stations/BN1_MCA1.yaml(включено в базе 1834) с теми же
адресами узловns=2;s=<прибор>.<поле>, что у OPC UA-фасада эмулятора мойки: шлюзу нужен один и тот же файл станции.
Прежний симулятор описывал 2517 тегов, из них 489 не относились к базе каналов, а 619 каналов базы в нём
отсутствовали. Modbus и PAC симулятор умеет по-прежнему, но поднимает их, только если в конфигурации есть их теги
(config/replay_config.yaml— три протокола, для интеграционных тестов);healthcheckпроверяет порт OPC UA и порты
настроенных протоколов. Подкомандаsimulator station-configпересобирает конфигурацию из базы каналов; тест следит, что
файл в репозитории не устарел. docker-compose.ymlиscripts/stack_smoke.sh: стенд — симулятор станции по OPC UA и шлюз с
config/stations/BN1_MCA1.yaml(PLC_HOSTвместоSIM_HOST); порты 5020 и 10000 на хост не пробрасываются.
Добавлено
- Два готовых режима чтения одной станции: «EasyDriver» (PAC, driver-master) и OPC UA.
config/stations/BN1_MCA1_easydriver.yaml
(получается из станции для OPC UA скриптомtools/station_to_easydriver.sh; тест следит, что каналы, имена, типы и доступ не
разошлись),simulator/config/station_pac.yaml(simulator station-config pac: все 2647 каналов только по PAC, серверы OPC UA и
Modbus не поднимаются),scripts/stand.sh opcua|easydriver,up-moika.sh opcua|easydriver(профильopcua— фасад). У PAC-режима
каналы с пробелами в индексе (ST_CH[ 1 ]) читаются какST_CH[1]. - Проверка эффекта записи теперь и для PAC (
GATEWAY_COMMANDS_VERIFY_MS): код 0 PAC означает только «команду приняли», а прошивка
принимает почти любую запись. Шлюз читает значение до записи, отпускает соединение (опрос не ждёт), через заданное время запрашивает
свежий снимок и при неизменном значении отвечаетFAILED_NOT_APPLIED. Проверено на настоящей прошивке и тестом
write_effect::pac_write_to_a_program_owned_valve_is_reported_as_not_applied_until_manual_mode. scripts/compare_protocols.py,loadsim --protocol pac,PROTOCOL=pac scripts/stress_test.sh,scripts/pac_probe_noauth.pyи
docs/PROTOCOL_COMPARISON.md: сравнение EasyDriver и OPC UA на настоящей прошивке и симуляторе (охват, нагрузка, нагрузка на ПЛК при
нескольких шлюзах, запись, обрыв связи, масштаб до миллиона значений в секунду, безопасность), выводы и список того, чего нет в PAC-режиме по
сравнению с драйвером savushkin-r-d/driver.scripts/stack_smoke.shсMODE=easydriverи вторая стр...
v0.4.0
Аудит безопасности и надёжности всего модуля, исправление записи PAC в поля-массивы, полная документация. Формат
сообщений Kafka, ключи и топики не менялись.
Что может задеть при обновлении
- Конфигурация проверяется строже. Станция, в которой тег молча не работал бы, теперь останавливает запуск с понятным
текстом (список ниже, раздел «Исправлено»). Конфигурации из репозитория проходят проверку; свою прогоните на тестовом
запуске до выкладки. - Порты БД и Kafka в compose-стендах привязаны к
127.0.0.1: с других хостов они недоступны, пока привязку не заменят. - Подключение к БД по умолчанию —
sslmode=prefer: TLS, если сервер его предлагает (раньше TLS не было вовсе). Чтобы
требовать шифрование —sslmode=requireили строже.
Безопасность
- TLS к PostgreSQL. Раньше драйвер БД был собран без TLS, а
sslmodeв адресе молча отбрасывался: трафик шёл
открытым текстом, даже если оператор написалsslmode=require. Теперь поддерживаютсяsslmode,sslrootcert,
ssl=true|falseиDB_SSLMODE/DB_SSLROOTCERT; неверное значение останавливает запуск. Проверено на настоящем
PostgreSQL с УЦ (tests/db_tls.rs,scripts/pg_tls.sh, шаг в CI). - Команда со значением
"NaN"/"inf"больше не доходит до ПЛК (REJECTED_TYPE_MISMATCH); значение дляset_cmdPAC —
только конечное число или bool, строки и NaN/inf отклоняются на месте подстановки в Lua-текст. - Адреса PAC (
deviceName,fieldName) проверяются при запуске: кавычки, пробелы и управляющие символы запрещены. - Пароль БД и токен REST — тип
Secret: вDebugи журнал не попадают. - Ошибки БД в REST не раскрывают адрес и SQL (подробности — в журнале шлюза); токен короче 16 символов — предупреждение.
- Образ и CI: права задач по умолчанию — только чтение;
cargo auditпроверяет и симулятор, и ptusa-opcua; обоснование
исключенияRUSTSEC-2023-0071(rsa) исправлено: оно ссылалось на «политику None», хотя защищённый канал поддерживается
с v0.3.0. Порты БД и Kafka в compose-стендах привязаны к127.0.0.1. - Чек-лист безопасности перед пуском на объекте —
docs/OPERATIONS.md.
Исправлено
- Запись PAC в поле-массив. Шлюз слал
set_cmd('RT_PAR_F[62]', 1, v): настоящий ptusa принимает такую команду с кодом 0 и
ничего не меняет, то есть запись выглядела выполненной (APPLIED). Теперь индекс идёт отдельным аргументом:
set_cmd('RT_PAR_F', 62, v)(как у фасадаptusa-opcua). - Чтение
RECMAN[n]. Менеджер рецептов лежит в снимке PAC массивомt.RECMAN[n]; тег сdeviceName: "RECMAN[1]"читался
какt["RECMAN[1]"]и всегда былBAD. POST /api/events/{id}/acknowledgeотвечал «ACKNOWLEDGED» и для несуществующего аларма — теперь404; пустой или
слишком длинныйuserId—400.- Конфигурация, при которой тег молча не работал, останавливает запуск с понятным текстом: протокол тега не совпадает с
контроллером (тег никто не опрашивал), два тега с однимnodeId(общий id в БД и общая история), Modbus-адрес вне
40001…105536 (обрезался бы до чужого регистра), PAC-тег безdeviceName/fieldName, разныеmodbusUnitIdу тегов
одного контроллера. - Тип контроллера определяется по схеме адреса, а не по вхождению:
pac://modbus-gw:502больше не принимается за Modbus. - Неверный пароль БД и непроверяемый сертификат показываются сразу, а не после 60 с повторов.
- Зависший Lua-скрипт на большом числе каналов останавливал обработку контроллера (по
timeoutна канал за цикл): после
3 превышений времени подряд скрипт отключается на 30 с, его каналы идут как BAD, остальные работают. - Мусор в топике команд усекается в журнале (300 символов) и экранируется.
Добавлено
- Запись в менеджер рецептов
RECMAN[n]в формате, который разбирает прошивка:__RECMAN[1]:set_cmd( "CMD", 1, 1001 )
(двойные кавычки, пробелы, строка в кавычках, число без знака). Значение, которое прошивка молча проигнорировала бы
(отрицательное число, кавычка в строке), отклоняется какREJECTED_TYPE_MISMATCH. Формат взят из описания прошивки
(регулярное выражениеptusa_main), на настоящем контроллере с рецептами пока не проверен. SIM_PAC_PORT: порт PAC вcontrollers.yamlи на хосте вdocker-compose.ymlнастраивается, если 10000 занят настоящим
ptusa_main.
Документация
- README переписан: быстрый старт, схемы (mermaid), возможности, контракт с монитором, таблица документов.
- Новые документы:
docs/ARCHITECTURE.md(потоки данных, жизненный цикл, надзор, резервирование, песочницы Lua, принципы),
docs/CONFIGURATION.md(все переменные окружения,controllers.yaml,scripts.yaml, проверки запуска),
docs/DEVELOPMENT.md(сборка, тесты, CI, соглашения, как расширять, релиз),docs/TROUBLESHOOTING.md
(симптом → причина → что делать),simulator/README.md. - Комментарии: каждый публичный элемент шлюза описан (
#![warn(missing_docs)]проверяется в CI), описаны приватные
функции, типы и поля во всех пакетах, у интеграционных тестов — сценарий, в неочевидных местах — пояснения «почему». - Примеры
controllers.yamlв документации проверяются тестом, чтобы не устаревать.
Образ: docker pull ghcr.io/euzireael/scada_rust:v0.4.0
v0.3.2
Внутренние изменения; формат Kafka, переменные окружения и поведение шлюза не менялись.
Изменено
- Симулятор и фасад ptusa-opcua: длинные функции
mainиbuildразделены на шаги (поведение то же). В CI для всех крейтов добавленcargo doc -D warnings, исправлены битые ссылки в документации.
Образ: docker pull ghcr.io/euzireael/scada_rust:v0.3.2
v0.3.1
Изменено
- Тело телеметрии формируется без промежуточных выделений (в 2 раза быстрее: 198 → 94 нс на сообщение), байты на проводе те же (тест на совпадение с serde). Код разделён на модули:
startup,config/,script/; поведение не менялось.
Добавлено
GATEWAY_COMMANDS_VERIFY_MS(по умолчанию 0 — выключено): проверка эффекта записи OPC UA. Если ПЛК принял
команду, но значение осталось прежним (программа не разрешает действие, например клапан не в ручном режиме),
результат —FAILED_NOT_APPLIED,success=false. Тестtests/write_effect.rs.docs/FIRMWARE_UNMAPPED_FIELDS.md: 138 полей настоящей прошивки без канала в базе станции (для разработчика монитора).
Образ: docker pull ghcr.io/euzireael/scada_rust:v0.3.1
v0.3.0
Шлюз под сбои и под масштаб, безопасность, симулятор и фасад на Rust (Python в репозитории больше нет), условная
логика прошивки в симуляторе. Формат сообщений Kafka, ключи и топики не менялись; новое для монитора —
docs/MONITOR_INTEGRATION.md («Что нового для монитора в шлюзе v0.3»), эксплуатация — docs/OPERATIONS.md.
Исправлено
- Недоставленная телеметрия не теряется молча. После обрыва брокера значения, которые фильтр
«по исключению» уже считал отправленными, не доходили до монитора до полной отправки. Теперь, когда
доставка восстановилась, все теги отправляются заново (scada_kafka_resyncs_total); предупреждения о
недоставленных пишутся раз в 30 с (раньше — по строке на сообщение, тысячи при обрыве). - Полная отправка без «залпа». Первая полная отправка каждого тега смещена на свою долю периода
(filter::phase): на 54 800 тегах вместо одной секунды с десятками тысяч сообщений раз в 30 с —
ровный поток. - Падение задачи не остаётся незамеченным. Опрос контроллеров, приём команд и фоновые задачи
перезапускаются надзирателем (scada_task_restarts_total, событие SYSTEM/ERROR); отравленный
мьютекс больше не каскадирует паники. - События в Kafka не зависят от БД. Запись в БД вынесена в отдельную задачу: при недоступной БД
смена роли пары и обрывы связи уходят в Kafka сразу, а не после таймаута пула. Потерянные из-за БД
строки считаются (scada_db_write_errors_total,scada_db_rows_lost_total). /actuator/healthпоказывает компонентkafka.- OPC UA-клиент шлюза пробует все адреса имени. Клиент async-opcua берёт первый адрес из DNS и
не пробует остальные:localhost→::1первым, а сервер на0.0.0.0слушает только IPv4 —
шлюз на хосте не подключался (Could not connect to host [::1]:4840), хотя127.0.0.1отвечал.
Теперь имя с несколькими адресами проверяется TCP-подключением, и если первый недоступен,
используется тот, что ответил (в журнале — строка «недоступен — подключаюсь к …»).
Проверка конфигурации
- Конфигурация контроллеров проверяется целиком при старте: повторяющееся имя тега (ключ Kafka и адрес команды),
повторяющееся имя контроллера, пустое имя тега и неизвестный протокол вendpointостанавливают запуск с
перечнем проблем (раньше контроллер с неизвестным протоколом молча не опрашивался, а два тега с одним именем
делали телеметрию и команды неоднозначными). Выключенные контроллеры и теги в расчёт не идут.
Безопасность OPC UA
- Защищённое подключение к ПЛК.
security:(Basic256Sha256,Aes128_Sha256_RsaOaep,Aes256_Sha256_RsaPss,
Basic256,Basic128Rsa15; режим_Sign/_SignAndEncrypt) иusername:/password:вcontrollers.yaml
теперь работают — раньше разбирались и молча игнорировались (канал оставался открытым и анонимным). Сертификат сервера
проверяется поtrusted/(GATEWAY_OPCUA_PKI_DIR, том/app/pkiв образе);
GATEWAY_OPCUA_TRUST_SERVER_CERTS— только для стенда. Ошибка вsecurity, логин без пароля и защита у
Modbus/PAC останавливают запуск с понятным сообщением; пароль не попадает в журнал иDebug. - Симулятор:
SIM_OPCUA_USER/SIM_OPCUA_PASSWORDвключают защищённые конечные точки (Basic256Sha256) — на них
проверяются политика, режимы, неверный пароль, анонимный вход, цепочка доверия к сертификату сервера.
Эксплуатация
docs/OPERATIONS.md— настройки, здоровье, метрики и оповещения, что шлюз делает при сбоях.scripts/stack_smoke.sh— дымовой тест стенда целиком с отказами Kafka, БД и ПЛК.scripts/load_check.sh(jobloadв CI) — нагрузочная проверка на 27 400 тегах: опрос не отстаёт, нет ошибок доставки и перезапусков задач, память не растёт.
Симулятор повторяет условную логику прошивки
- Снято с прошивки мойки (все 1816 записываемых каналов станции) и воспроизведено в симуляторе: часть полей прошивка
принимает (код 0), но не меняет (обратная связь, датчики, регистры команд, состояния техобъектов — 107), часть
действует только в ручном режиме (LINE#V0— главные клапаны линий, виртуальные выходы, сторожевые таймеры — 18; при
возврате в автоматику значение возвращается к программе), часть хранится целой (16). Вreplay_config.yaml— ключи
write: ignore,write_requires: {field: M, equals: 1},write_int,write_state. Протокол PAC отвечает как
прошивка: неизвестное поле известного прибора — код 0, не число — код 1. simulator conformance [host] [port] [config]проверяет соответствие по driver-master у любого PAC (CI — на симуляторе;
на прошивке мойки: 81 тег соответствует, 45 пропущено, расхождений 0). Описание —docs/FIRMWARE_WRITE_BEHAVIOR.md.
Изменено
-
PLC-симулятор переписан с Python на Rust (
simulator/): OPC UA, Modbus TCP и PAC
driver-master в одном процессе, тот жеreplay_config.yaml, тот же реплей архива BN1_MCA1
(формат архива —data/archive_replay.bin.gzвместо pickle). Образ — бинарник без интерпретатора,
узлы OPC UA получают только изменившиеся значения, запись оператора применяется сразу.
Структура снимка PAC и раскладка регистров Modbus совпадают с прежним симулятором
(сверено на 63 приборах PAC и 500 регистрах). Новое: поле-массив PAC пишется командой
set_cmd('RT_PAR_F', 12, v)как у настоящего ptusa;simulator probe-pacвместоpac_probe.py.
Убраны: синтетические генераторы сигнала (в конфигурации только replay), режим «сырых записей
приборов» (sim_records.py, нужен был Java-шлюзу),tools/*.py(build_archive.py
строил архив из SQL-дампа, которого в репозитории нет). -
OPC UA-фасад
ptusa-opcua/переписан с Python на Rust (async-opcua + mlua): те же узлы
ns=2;s=<прибор>.<поле>, те же настройки окружения и порт, запись —set_cmdс исходом прошивки
в статусе записи. Проверен на настоящей прошивке ptusa: 1834 канала GOOD, командаAPPLIEDза
27 мс, переключение резерва шлюза. Песочница Lua — как в шлюзе (безos/io/load/байткода,
память 64 МБ, 1 с на снимок), потолок распаковки zlib 8 МБ; вместе с этим закрыт эффект алерта
GHSA-69v7-xpr6-6gjm (lupa в репозитории больше нет). Python в репозитории не осталось.
Ограничение: конечные точки GetEndpoints строятся изOPCUA_BIND(async-opcua не разделяет
адрес привязки и анонса); сторонним клиентам OPC UA, которые не пробуют второй адресlocalhost, вне Docker нуженOPCUA_BIND=[::]:4840(шлюз с этим справляется сам, см. «Исправлено»).
Добавлено
- Безопасность Kafka: TLS и SASL (PLAIN, SCRAM) —
KAFKA_SECURITY_PROTOCOL,
KAFKA_SASL_*,KAFKA_SSL_*, любое свойство librdkafka черезKAFKA_CLIENT_*, Spring-имена
SPRING_KAFKA_PROPERTIES_*(включая JAAS); OpenSSL вшит в бинарник. Секреты не попадают в журнал.
Проверено на брокере с SASL_PLAINTEXT: неверный пароль — отказ в аутентификации, верный — данные идут. - Безопасность REST:
GATEWAY_API_TOKEN(илиGATEWAY_API_TOKEN_FILE) закрывает/api/*
токеном Bearer;/actuator/healthи/actuator/prometheusоткрыты.GATEWAY_HTTP_BIND. - Резервирование по здоровью: активный экземпляр без связи со всеми контроллерами
(GATEWAY_HA_YIELD_AFTER_MS, 30 с) передаёт лидерство партнёру, который в группе, и не отбирает
его обратно; без партнёра остаётся активным.
Образ: docker pull ghcr.io/euzireael/scada_rust:v0.3.0
v0.2.0
Перенос наработок Java-шлюза: телеметрия «по исключению» (контракт монитора), горячее
резервирование, пользовательские Lua-скрипты и станция мойки целиком по OPC UA.
Добавлено
- Телеметрия «по исключению» (
GATEWAY_PUBLISH_*): тег уходит в Kafka при первом значении,
смене качества, изменении за зону нечувствительности и раз вfull-resend-ms(полная
отправка). Фильтр локальной истории (GATEWAY_HISTORY_*, блокhistory:тега, колонки
tags.history_*). Контракт —docs/TELEMETRY_BY_EXCEPTION.md. На стенде 54 800 тегов
(20 × 2740, 18 280 изменений/с): ≈25 % одного ядра, 125 МБ, без потерь. - Горячее резервирование (
GATEWAY_HA_*): выборы через группу потребителей Kafka, резерв
опрашивает, но молчит; переключение при отказе ≈1,9 с (штатная остановка ≈0,6 с), новый
активный сразу шлёт все теги;GET /api/ha, метрикаscada_ha_active, событияHotStandby.
Команды: ручное назначение партиций, коммит позиции до исполнения, возраст команды
(GATEWAY_COMMANDS_MAX_AGE_MS,REJECTED_EXPIRED). - Пользовательские скрипты Lua 5.1 (
config/scripts/,GATEWAY_SCRIPTS_*):processдля
входящих значений,writeдля команд, состояние на канал, цепочки, горячая перезагрузка;
песочница с лимитами памяти и времени;GET /api/scripts. - Станция мойки по OPC UA:
config/stations/BN1_MCA1.yaml(1834 канала), фасад
ptusa-opcua/(прошивка ptusa → OPC UA, запись черезset_cmd),docker-compose.moika.yml,
up-moika.sh,tools/station-config/,docs/MONITOR_INTEGRATION.md. - Нагрузочный стенд
loadtest/loadsim; метрикиscada_telemetry_suppressed_total,
scada_process_seconds,scada_poll_seconds,scada_poll_overruns_total,
scada_script_errors_total,scada_scripts_bound_tags. CONTROLLERS_CONFIG(формаfile:/путь),KAFKA_TOPICS_REPLICATION.
Изменено
- Все узлы OPC UA вернули BAD ⇒ связь с контроллером считается потерянной.
${NAME}вcontrollers.yamlбез значения и без умолчания — ошибка старта (${NAME:}— пусто).- Миграция
0002: колонкиhistory_*вtags(совместима с Flyway V2 Java-шлюза).
Безопасность
- Песочница Lua: убраны
coroutine(в сопрограмме не работает хук времени),getfenv/setfenv,
newproxy,collectgarbage; лимит распаковки ответа PAC — 8 МБ (zlib-бомба).
Образ: docker pull ghcr.io/euzireael/scada_rust:v0.2.0
v0.1.0
Первый релиз: шлюз АСУ ТП на Rust — замена Java-шлюза savushkin-dev/scada-gateway с теми же
внешними контрактами (controllers.yaml, топики и формат Kafka, статусы команд, env,
/actuator/health, метрики, схема БД), поэтому встаёт на его место без правок у монитора.
Добавлено
- Опрос контроллеров, по задаче на контроллер с периодом по
pollingRate:- OPC UA — подключение прямо к адресу из конфига (без discovery), чтение пачками по 500 узлов,
пересоздание сессии при зависании; - Modbus TCP — FC03 блоками до 120 регистров; при исключении IllegalDataAddress блок делится
пополам, пока не прочитается (план подстраивается под карту регистров ПЛК); - PAC driver-master в формате ptusa (баннер
PAC accept, zlib, снимокtисполняется
в Lua 5.1), запись черезset_cmdс кодом результата.
- OPC UA — подключение прямо к адресу из конфига (без discovery), чтение пачками по 500 узлов,
- Kafka: телеметрия
scada.tags(ключ — путь канала, тело{value, quality, timestamp},
timestamp — epoch-секунды с наносекундами), события, алармы, команды записи и результаты со
статусами Java-шлюза; идемпотентный продюсер, который не блокирует опрос; команды читаются
с конца топика, повторы отбрасываются. - PostgreSQL: схема, совместимая с Flyway-схемой Java-шлюза, и индексы для выборок;
синхронизация тегов сcontrollers.yaml; журнал событий и (по флагу) история телеметрии
пачками через очереди — база не на горячем пути. - HTTP:
/actuator/health,/actuator/prometheus, REST журнала/api/*(всегда сLIMIT). - Кадры BAD при обрыве, события связи (одно сводное событие смены качества на цикл),
heartbeat, остановка по SIGTERM, подкомандаhealthcheckдля контейнера. - Стенд в репозитории: PLC-симулятор (
simulator/: OPC UA, Modbus, PAC, реплей архива
BN1_MCA1), конфигурация станции (2517 каналов на трёх контроллерах),docker-compose.yml,
эмулятор настоящего PAC (scripts/ptusa_emulator.sh). - Тесты: юнит (протоколы на фейковых серверах PAC и Modbus), сверка конфигов шлюза и
симулятора, интеграционные против симулятора (каждый тег своим протоколом, запись),
сквозной через Kafka и БД, обрыв и восстановление связи через TCP-прокси. - CI/CD: fmt, clippy, тесты, pytest симулятора, интеграционные (Kafka и Postgres —
service-контейнеры), образ в GHCR, GitHub Release по тегу;cargo auditна каждый push и
раз в неделю; Dependabot.
Безопасность
- Lua-скрипт в ответе PAC исполняется в песочнице: без io/os/package и файлов, без загрузки
байткода (load,loadstring,string.dump) иpcall; не больше 64 МБ памяти и 1 с на
скрипт. - RUSTSEC-2023-0071 (
rsaиз async-opcua, исправленной версии нет) — в исключениях
cargo audit: при политике OPC UA None RSA-расшифровки у клиента нет.
Отличия от Java-шлюза
- Учитывается
pollingRate(Java опрашивала раз в секунду при 2000 мс). - История
telemetryпо умолчанию выключена (было ~15 ГБ/сут), кадры BAD — включены. - Обрыв OPC UA обнаруживается за ~1 с (было 30 с); память 14–27 МБ (было 823 МБ),
образ 137 МБ (было 646 МБ), старт до связи с контроллерами < 1 с (было 17,5 с). - Не перенесено: режим
recordDevice/fields, JVM-метрики/actuator/metrics/*,
политики безопасности OPC UA кроме None.
Образ: docker pull ghcr.io/euzireael/scada_rust:v0.1.0