Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Сусанин: как я перестал кормить MikroTik списками доменов и научил искать рабочий маршрут самостоятельно

Дата публикации: 31-08-2026 09:13:30

Мне надоело вручную кормить MikroTik списками доменов и IP для выборочной маршрутизации через VPN. CDN меняются, адреса переезжают, TCP и QUIC могут вести себя по-разному — а список приходится постоянно догонять руками.Так появился «Сусанин»: экспериментальная система, которая не знает заранее, что нужно отправлять в VPN. RouterOS наблюдает за connection tracking, замечает подозрительный сбой DIRECT-соединения, проверяет тот же destination через уже существующий туннель и временно запоминает рабочий путь. TCP и UDP обучаются отдельно, а при падении VPN система делает fail-open обратно в DIRECT.В статье — архитектура FAST / SOFT / JUDGE / HEALTH, C11 control plane, credentialless bootstrap, транзакционная установка, реальные грабли RouterOS API (!empty / !done), clean install 0/16 → 16/16, reboot-тест, логи и полная инструкция по установке.Проект пока очень пилотный: ARM64, основная тестовая версия RouterOS 7.23.3. Перед установкой — обязательно backup. Читать далее

Основное содержимое страницы с новостью.

TL;DR. Я сделал пилотную систему адаптивной маршрутизации для MikroTik RouterOS. Она не хранит список «какие сайты надо отправлять в VPN». Вместо этого RouterOS смотрит на собственный connection tracking, замечает характерные неудачные соединения, проверяет тот же destination через уже существующий route-based VPN/tunnel и временно запоминает, помог ли альтернативный путь. TCP и UDP обучаются отдельно, а при падении VPN система уходит в fail-open DIRECT. Для установки нужны susanin.tar и install.rsc, после чего пользователь выбирает только VPN-интерфейс.

Проект пока очень пилотный. Публичная цель — ARM64, основная тестовая версия — RouterOS 7.23.3. На реальном MikroTik проверены clean install 0/16 → 16/16, reboot, fail-open/recovery, credentialless bootstrap и повторная structural reconciliation. Перед установкой обязательно делайте backup.

Сусанин

Сусанин

Сразу две честные оговорки. Я не профессиональный программист, а сетевой инженер и специалист по информационной безопасности. В маршрутизации, firewall, conntrack, VPN и сетевой диагностике я чувствую себя заметно увереннее, чем в production-C. При разработке Susanin я активно использовал ChatGPT: для C-кода, ревью, разбора RouterOS API, проверки гипотез и документации. Не хочу делать вид, что всё было написано в одиночку и исключительно по памяти.

При этом схема разработки была не «сгенерировал код и выложил». Почти каждая заметная версия проходила через реальный MikroTik: сборка, установка, ошибка, анализ логов и RouterOS state, исправление, иногда восстановление старого backup и повторный clean install. Некоторые ошибки были обычными моими багами, другие оказались неожиданными особенностями RouterOS. Именно эта граница между C-контейнером, RouterOS scripting, API и container runtime в итоге оказалась интереснее самой исходной идеи.

Вторая оговорка — проект появился не в вакууме. Очень сильным толчком стал timbrs/amneziawg-mikrotik-c и статья его автора «Наконец-то: AmneziaWG в Mikrotik». Мне понравилась сама инженерная философия: не переписывать то, что MikroTik уже делает хорошо, а добавить минимальный недостающий слой. В amneziawg-mikrotik-c это позволило оставить основную WireGuard-логику в RouterOS. В моём случае проблема была другой — мне надоело вручную вести selective routing через постоянно растущие доменные и IP-списки.

Почему вообще появился «Сусанин»

Название почти буквальное. Для selective routing интернет довольно быстро превращается в лес из CDN, динамических DNS-ответов, меняющихся IP, TCP, UDP, QUIC, временных отказов и разных сетевых путей. Классическая схема со списками пытается заранее нарисовать карту этого леса: этот домен отправляем в VPN, этот IP заносим в address-list, эту подсеть обновляем скриптом.

Susanin делает наоборот. У него нет готовой карты. Сначала клиент идёт обычным DIRECT-маршрутом. Если поведение соединения выглядит подозрительно, роутер формирует короткоживущую гипотезу и даёт следующей попытке пройти через VPN. Если альтернативный путь реально помог, это решение на некоторое время запоминается. Если не помог — destination остаётся DIRECT.

Именно поэтому название мне понравилось: проект сначала может пойти не туда, но пытается выбраться из леса по фактическим следам соединений, а не по заранее составленному атласу.

Почему меня перестали устраивать доменные списки

Моя исходная схема была вполне обычной: DNS static с address-list=to_awg, затем mangle и отдельная routing table через AmneziaWG. Она работала, пока список был небольшим. Потом список начал жить своей жизнью.

На одном старом backup оказалось 1185 DNS static записей, плюс десятки IP в to_awg, часть из которых успевала динамически накопиться через DNS. Само число 1185 для MikroTik не выглядит катастрофой. Проблема в другом: я вручную поддерживал модель внешнего интернета, которая постоянно устаревала.

Сегодня сервис отвечает с одного CDN-узла, завтра с другого. Один hostname ведёт на несколько сетей, один IP обслуживает несколько сервисов, а QUIC/UDP 443 и TCP/443 на одном destination могут вести себя по-разному. Иногда ломается не весь сервис, а конкретный протокол или конкретный путь. Иногда проблема временная, а IP ещё месяцами остаётся в списке только потому, что никто его не удалил.

В какой-то момент я сформулировал проблему так: я пытаюсь заранее угадать результат сетевого соединения вместо того, чтобы посмотреть на результат самого соединения.

Статические списки и поведенческий подход

Статические списки и поведенческий подход

Отсюда появилась базовая модель Susanin:

DIRECT работает
    → ничего не делать

DIRECT выглядит сломанным
    → проверить тот же destination через VPN

VPN помог
    → временно запомнить VPN для этого протокола

VPN не помог
    → оставить DIRECT

Здесь принципиально нет попытки определить «какой это сайт». Susanin не читает TLS SNI, не расшифровывает HTTPS, не анализирует payload и не скачивает внешнюю базу адресов. Ему важнее состояние конкретного соединения: был ли reply, как меняется TCP state, есть ли нормальный обратный поток и помог ли другой маршрут.

Это одновременно сильная и слабая сторона подхода. Сильная — не нужно заранее классифицировать интернет. Слабая — любое решение эвристическое: зависший TCP может быть проблемой сервера, а не маршрута. Поэтому одного детектора недостаточно; подозрение обязательно должно подтверждаться повторной попыткой.

Что можно увидеть в RouterOS connection tracking

Для первого пилота я сознательно не хотел строить сложный DPI или «AI по пакетам». В RouterOS уже есть /ip firewall connection, где доступны source/destination, protocol, TCP state, packet/byte counters, reply counters, seen-reply, rates и connection marks. Этого достаточно, чтобы искать довольно понятные симптомы.

Например, клиент несколько раз отправляет TCP SYN, но нормального ответа нет. Или TCP формально перешёл в established, однако клиент продолжает отправлять данные, а обратный поток почти пуст. Для QUIC полезен простой признак UDP/443 без reply. Есть и более осторожные late-stall сценарии, когда состояние должно повториться несколько циклов подряд, прежде чем destination вообще попадёт в тест.

Важно, что эти признаки только создают гипотезу. Сам факт SYN без reply не означает «навсегда отправить IP в VPN».

Четыре worker-а: FAST, SOFT, JUDGE и HEALTH

В итоге data plane разделился на четыре RouterOS script, каждый со своей ролью.

Логика Susanin

Логика Susanin

FAST запускается раз в секунду и ловит быстрые симптомы. Если, например, TCP SYN повторяется без нормального reply, destination временно попадает в auto_awg_test_tcp, текущая conntrack-запись удаляется, а следующая попытка клиента уже может получить routing mark в VPN. Для UDP/QUIC используется отдельное test-состояние. В логах это выглядит примерно так:

AUTO-AWG: FAST TCP-SYN 203.0.113.10:443
AUTO-AWG: FAST TCP-CLOSE 203.0.113.11:443
AUTO-AWG: FAST QUIC 203.0.113.12:443

SOFT нужен потому, что далеко не все проблемы выглядят как отсутствие SYN-ACK. Соединение может быть established, но фактически зависнуть. Поэтому есть более осторожные признаки TCP stall и late stall с debounce через временный watch. Логика специально не реагирует на один случайный snapshot conntrack:

AUTO-AWG: SOFT TCP-STALL 203.0.113.20:443
AUTO-AWG: SOFT TCP-LATE-STALL 203.0.113.21:443

JUDGE — ключевая часть архитектуры. FAST и SOFT говорят только: «DIRECT выглядит подозрительно». После этого новая попытка клиента направляется через VPN, и JUDGE уже оценивает её результат. Если нормальный reply появился, destination попадает в подтверждённый cache примерно на шесть часов:

AUTO-AWG: CONFIRMED 203.0.113.30 via tcp:443
AUTO-AWG: CONFIRMED 203.0.113.30 via udp:443

Если через VPN тоже ничего не изменилось, destination получает cooldown и остаётся DIRECT. Таким образом Susanin не пытается доказать, что IP «заблокирован»; он отвечает только на более узкий практический вопрос: оказался ли альтернативный путь лучше для этого протокола прямо сейчас.

TCP и UDP специально обучаются отдельно. Один и тот же IP может нормально работать по TCP/443 напрямую, но требовать VPN для QUIC/UDP 443, или наоборот. Поэтому существуют отдельные watch/test/ok/cooldown списки для TCP и UDP. Подтверждённые записи имеют TTL, то есть со временем исчезают и переучиваются. Это важный момент: я не хотел случайно построить новую вечную IP-базу вместо старой.

HEALTH решает другую задачу: что делать, если умер сам VPN. Если продолжать отправлять learned destinations в мёртвую routing table, Susanin сам создаст outage. Поэтому после двух health miss managed mangle отключается:

AUTO-AWG: tunnel DOWN after 2 health misses, fallback to DIRECT

Это fail-open: потеря VPN не должна означать потерю интернета. Когда tunnel возвращается, HEALTH автоматически включает adaptive routing обратно:

AUTO-AWG: tunnel UP, recovery TCP=... UDP=...

Эта логика отдельно проверялась после reboot. RouterOS успевал подняться раньше VPN-контейнера, HEALTH сначала видел tunnel DOWN и оставлял трафик DIRECT, а через несколько секунд после появления VPN сам выполнял recovery. Мне этот вариант нравится больше искусственной boot-задержки: система реагирует на фактическое состояние туннеля, а не на таймер.

Почему data plane остался внутри RouterOS

Первоначально казалось естественным вынести всё в контейнер: каждую секунду читать conntrack через API, принимать решение в C и снова записывать state в RouterOS. На практике архитектура быстро начала выглядеть хуже.

Получался постоянный путь RouterOS conntrack → API → container → decision → API → RouterOS firewall. Это лишние round-trip, сериализация, задержка реакции и зависимость forwarding logic от controller.

При этом RouterOS уже умеет connection tracking, scripts, schedulers, address-list timeout, mangle, routing-mark и FIB. Поэтому итоговая схема стала двухуровневой.

Архитектура Susanin

Архитектура Susanin

Data plane живёт непосредственно в RouterOS и выполняется каждую секунду без API. C11-контейнер — это control plane: он делает discovery, setup, генерирует scripts, валидирует их, выполняет fresh install, показывает status и умеет structural reconciliation и stage/promote/rollback для обновлений.

Пользовательский трафик через Susanin container не проходит. Если controller остановить после установки, RouterOS data plane продолжит работать. Во время разработки это оказалось особенно удобно: controller я заменял много раз, а текущая маршрутизация могла продолжать жить.

Почему C11

C здесь не является обязательной частью самой идеи. То же самое можно было бы написать на Go, Rust или Python. Я выбрал C11 из-за небольшого runtime footprint, минимального набора зависимостей и желания держать контейнер максимально простым. Плюс это был повод самому глубже разобраться с C.

Самая неприятная часть кода оказалась не в эвристиках, а на границе RouterOS scripting, RouterOS API и container runtime. В проекте есть собственный небольшой RouterOS API client на C, который используется для discovery, inventory, read-back, temporary validation objects, установки и status. И именно там нашёлся один из самых интересных багов проекта.

Credentialless bootstrap

Ранние версии Susanin были обычным API client с конфигурацией вроде:

ROUTER_HOST=...
ROUTER_USER=...
ROUTER_PASSWORD=...

Технически это работало, но пользовательский сценарий был плохим. Если цель — «загрузить два файла, выбрать VPN и готово», странно заставлять человека вручную создавать API user, придумывать пароль, правильно ограничивать права и затем класть credentials в env. Кроме того, на одной из ранних схем старый credential однажды оказался в RouterOS log, после чего я решил полностью убрать пользовательские credentials из процесса установки.

Credentialless bootstrap

Credentialless bootstrap

install.rsc создаёт отдельную изолированную /30 сеть: RouterOS получает 172.31.254.1/30, а container — 172.31.254.2/30. Затем временный admin-owned worker генерирует случайный secret длиной 48 символов, пишет его в RouterOS file, читает обратно и проверяет размер, длину и точное совпадение. Только после успешной проверки создаётся или обновляется внутренний susanin-agent, а secret монтируется в container как /run/secrets/routeros_password.

Долгоживущий агент имеет read,write,test,api и ограничен адресом 172.31.254.2/32. Secret не передаётся через container env или argv. Сейчас controller использует обычный RouterOS API TCP/8728 внутри этой изолированной /30 сети с узким firewall rule; API-SSL остаётся будущей задачей hardening.

Четыре грабли, которые стоили больше времени, чем сама идеяSecret-файл внутри /import

Обычный тест из terminal или /system script стабильно создавал 48-байтный файл и позволял прочитать его обратно:

generated-len=48
file-size=48
read-len=48
equal=true

Но та же логика внутри длинного /import иногда оставляла 0-byte file. Вместо бесконечной борьбы с import runtime bootstrap был перестроен: /import теперь только создаёт temporary worker, а сам worker выполняет secret generation, write и read-back verification уже как обычный RouterOS script. Если verification не проходит, пароль machine account не меняется.

Асинхронная распаковка container

/container add file=... не означает, что образ уже готов. Во время extraction у container могут быть пустые tag, os и arch, хотя root-dir уже назначен. Одна промежуточная версия worker воспринимала такой объект как старый container, удаляла его и начинала всё заново. Получался цикл add → extracting → tag empty → remove → add.

Фикс оказался простым: in-progress target определяется по versioned root-dir, а реальные tag и arch проверяются только после завершения extraction. Для v0.11.3 ожидаются root-dir=/susanin-controller-v0113, tag=0.11.3, arch=arm64.

Выключенный RouterOS API

На одном clean backup сервис API был выключен. Bootstrap пытался прочитать disabled в переменную и условно включить сервис, но в import context эта проверка сработала не так, как ожидалось. Сеть container↔RouterOS была полностью жива, а Susanin закономерно писал Cannot connect to RouterOS API at 172.31.254.1:8728.

Вместо сложной проверки bootstrap теперь просто выполняет idempotent:

/ip service enable [find where name="api"]

Если сервис уже включён, ничего плохого не происходит.

!empty — не конец ответа RouterOS API

А это был уже мой настоящий C-баг. На чистом роутере inventory-запросы часто ничего не находят, и RouterOS API может вернуть:

!empty
!done

Клиент ошибочно считал !empty окончанием команды. Финальный !done оставался в TCP stream и затем принимался за конец следующего запроса. После нескольких пустых lookup API session рассинхронизировалась.

Из-за этого standalone susanin render работал на свежей session, а install --dry-run после проверки 0/16 внезапно сообщал, что в LAN нет usable interfaces, хотя discover прекрасно видел bridge-LAN и 192.168.1.1/24.

RouterOS API framing

RouterOS API framing

После фикса смысл стал правильным: !empty означает пустой результат, но command stream всегда читается до !done. Только после этого clean dry-run стабильно начал показывать READY FOR FRESH INSTALL.

Fresh install как транзакция

Installer я сознательно не хотел делать в стиле «успели создать половину конфигурации, на следующем объекте получили ошибку — разбирайтесь сами».

На чистом роутере Susanin сначала должен увидеть 0/16 managed objects. Затем он рендерит desired source, создаёт временные RouterOS script objects и даёт самому RouterOS проверить syntax. Validators удаляются, после чего создаются production scripts, их source читается обратно и сравнивается по fingerprints. Mangle и schedulers создаются выключенными, inventory проверяется, и только после этого выполняется commit — включение data plane.

Если ошибка произошла до commit, Susanin удаляет объекты, которые создал в этой транзакции.

Есть три состояния. 0/16 — разрешён fresh install. 16/16 — полная существующая установка, дубликаты не создаются. Частичное состояние вроде 7/16 считается blocker: controller не пытается угадать, какие объекты администратор хочет сохранить.

Перед production commit generated scripts отдельно валидируются самим RouterOS parser. Для протестированной конфигурации v0.11.3 fingerprints были следующими:

auto-awg-health   3356 bytes  24fc884bbe4473dc
auto-awg-fast     4780 bytes  17f96c5f8c6b94de
auto-awg-detect   7387 bytes  af897abaf3841a18
auto-awg-judge    5965 bytes  737624f7c52ad08a
Самый важный тест: вернуться в старый backup

Когда v0.11.3 уже стабильно работала поверх моей текущей конфигурации, оставался неприятный вопрос: действительно ли fresh install работает, или я всё время проверяю обновление уже существующего data plane?

Для этого я вернулся к старому backup из эпохи доменных списков. В нём было 1185 DNS static записей с address-list=to_awg, 74 записи в самом to_awg и семь старых AWG mangle rules. Susanin при этом отсутствовал полностью: scripts, schedulers и AUTO-AWG mangle были равны нулю.

Я удалил только старый selective-routing слой, но оставил рабочий VPN, его routing table и default route. После очистки состояние было честным:

scripts=0
schedulers=0
managed-mangle=0
susanin-safety=0

Затем на MikroTik были загружены только install.rsc и susanin.tar. install --dry-run увидел 0/16 и перечислил то, что собирается создать: четыре generated scripts, четыре schedulers, восемь managed mangle rules и три safety bypass rules. После единственного выбора VPN в setup прошли validation, создание выключенных объектов и commit.

Финал выглядел так:

Fresh install result: SUCCESS
  scripts=4 schedulers=4 mangle=8 safety=3
  tunnel NAT=EXISTING
  data-plane started
Реальный setup

Реальный setup

После установки status показал scripts=4/4 schedulers=4/4 mangle=8, а apply --dry-run дал:

KEEP=16 CREATE=0 UPDATE=0 BLOCKERS=0
Result: IN SYNC structurally.
Реальный status

Реальный status

После этого я перезагрузил MikroTik. Controller снова автоматически поднялся, schedulers продолжили увеличивать RUN-COUNT, а dynamic cache начал обучаться заново. Именно после этого теста я решил, что проект уже можно хотя бы показывать другим людям.

Как поставить Susanin

Ниже — полная пользовательская часть, но она гораздо проще внутренней архитектуры.

Текущий pilot release находится здесь: https://github.com/Fiark/susanin/releases/tag/v0.11.3. Сейчас публичная цель — ARM64 MikroTik и RouterOS 7.23.x; основной реальный тест выполнен на 7.23.3. На роутере должен быть установлен package container, разрешён container device-mode, существовать interface-list LAN с IPv4-сетью и уже работать route-based VPN/tunnel с IPv4 на интерфейсе. Susanin сам VPN не создаёт.

Перед установкой сделайте backup:

/system backup save name=before-susanin
/export file=before-susanin

Проверьте архитектуру, package/container mode, LAN и уже существующий tunnel:

/system resource print
/system package print where name="container"
/system device-mode print
/interface list member print detail where list="LAN"
/ip address print detail
/interface print detail
/routing table print
/ip route print detail

Из Assets release нужны два файла:

susanin.tar
install.rsc

Загрузите их в MikroTik Files и сначала прогоните parser dry-run:

/import file-name=install.rsc verbose=yes dry-run

В конце ожидается:

No syntax errors found in the import file

После этого запускается bootstrap:

/import file-name=install.rsc verbose=yes

Во время extraction container может иметь флаг E. После завершения нужен R:

/container print detail where name="susanin-controller"

Для v0.11.3 ожидаются tag="0.11.3" и arch="arm64".

Когда controller запущен, выполняется setup:

/container/shell susanin-controller \
    cmd="/usr/local/bin/susanin setup" \
    no-sh \
    timeout=300

Susanin покажет найденные route-based tunnel interfaces и попросит выбрать один. Если отдельная routing table для этого egress уже существует, controller попытается определить её автоматически; если однозначной таблицы нет, fresh installer умеет подготовить собственную FIB table/default route.

После установки полезно сразу выполнить:

/container/shell susanin-controller \
    cmd="/usr/local/bin/susanin status" \
    no-sh \
    timeout=60

и затем:

/container/shell susanin-controller \
    cmd="/usr/local/bin/susanin apply --dry-run" \
    no-sh \
    timeout=60

Нормальное состояние:

Summary: scripts=4/4 schedulers=4/4 mangle=8
Installation state: detected

KEEP=16 CREATE=0 UPDATE=0 BLOCKERS=0
Result: IN SYNC structurally.
Как смотреть, что он делает

Black box routing мне не нужен, поэтому основные решения видны прямо в RouterOS log:

/log print follow-only where message~"AUTO-AWG:"

Там появляются FAST/SOFT-события, подтверждения CONFIRMED и переходы tunnel DOWN / tunnel UP. Learned cache можно посмотреть через:

/ip firewall address-list print where list~"auto_awg_"

Состояние schedulers и mangle:

/system scheduler print where name~"auto-awg-"
/ip firewall mangle print where comment~"^AUTO-AWG:|^SUSANIN:"

Для control plane полезны:

/container print detail where name="susanin-controller"
/ip service print detail where name="api"
/log print where message~"SUSANIN:"

Сам controller также умеет discover, render, status и install --dry-run. Например, если setup не видит LAN или tunnel, сначала удобно посмотреть:

/container/shell susanin-controller \
    cmd="/usr/local/bin/susanin discover" \
    no-sh \
    timeout=60

Если проблема похожа на генерацию scripts:

/container/shell susanin-controller \
    cmd="/usr/local/bin/susanin render" \
    no-sh \
    timeout=60
Что создаётся на роутере

Control plane использует bridge-susanin, veth-susanin, внутреннюю сеть 172.31.254.0/30, restricted account susanin-agent, каталоги susanin-secrets и susanin-data, а также сам susanin-controller.

Data plane состоит из четырёх scripts (auto-awg-health, auto-awg-fast, auto-awg-detect, auto-awg-judge), четырёх schedulers, восьми основных managed mangle rules, трёх safety bypass rules и временных auto_awg_* address lists.

Исторический namespace AUTO-AWG остался намеренно. Проект вырос из моей старой локальной схемы, и для первой публичной версии я не хотел одновременно менять installer, namespace и migration. Позже планируется переход к чистому SUSANIN, но сначала хочется получить реальные тесты текущего data plane.

Если для выбранного tunnel уже существует подходящий активный masquerade, installer старается не создавать дубликат. На моём clean install он корректно определил tunnel NAT=EXISTING.

Обновление и удаление

Пока pilot-обновление controller выглядит так же просто: сделать backup, заменить susanin.tar и install.rsc, выполнить dry-run импорта, затем обычный import, дождаться R и проверить status/apply --dry-run. Data plane живёт в RouterOS и не обязан останавливаться во время замены controller.

В release также лежат два uninstall script. uninstall.rsc предназначен для полного удаления Susanin controller и managed data plane. uninstall-controller.rsc удаляет только control plane и оставляет RouterOS data plane работать самостоятельно:

/import file-name=uninstall.rsc

или:

/import file-name=uninstall-controller.rsc
Security model

Для pilot-проекта я постарался не делать очевидно плохих вещей с credentials. Пользователь не вводит RouterOS API password. Long-lived susanin-agent ограничен правами и внутренним source IP, а secret хранится в susanin-secrets/routeros_password и монтируется как /run/secrets/routeros_password.

После нормального bootstrap временные elevated helpers должны исчезнуть. Это можно проверить:

/system scheduler print where name~"susanin-bootstrap-"
/system script print where name~"susanin-bootstrap-"

Оба вывода должны быть пустыми.

В GitHub есть отдельный SECURITY.md, включены private vulnerability reporting и secret scanning. При создании issue не стоит прикладывать RouterOS .backup, show-sensitive, private/preshared keys, VPN credentials или содержимое secret-файла.

Что уже проверено и что ещё нет

На реальном ARM64 MikroTik с RouterOS 7.23.3 я проверил credentialless bootstrap, запуск при изначально выключенном API, exact secret read-back verification, container extraction/start, cleanup temporary helpers, LAN и tunnel discovery, routing table auto-detection, RouterOS-side source validation, clean install 0/16 → 16/16, TCP/UDP learning, fail-open DIRECT, VPN recovery, reboot и повторную structural reconciliation.

Но это всё ещё не production-ready продукт. Практически не проверены другие ARM64-модели, разные RouterOS 7.x, multi-WAN, multi-VRF, сложный existing policy routing, несколько одновременно используемых VPN, большие LAN, длительный uptime месяцами и IPv6. Не набрана и нормальная статистика false-positive/false-negative на разных провайдерах.

Сами heuristics тоже экспериментальные. TCP stall может быть проблемой удалённого сервера, а не маршрута. TEST + JUDGE сильно уменьшают риск неправильного решения, но не превращают эвристику в математическое доказательство.

Что дальше

Сейчас мне интереснее не добавлять ещё двадцать функций, а посмотреть, живёт ли минимальная схема за пределами одного моего роутера. Особенно полезны тесты на других ARM64 MikroTik и других RouterOS 7.23.x.

После этого уже имеет смысл думать о configurable thresholds, API-SSL, более аккуратном multi-LAN, IPv6, локальной telemetry-free статистике решений и migration с исторического AUTO-AWG namespace.

GitHub проекта: https://github.com/Fiark/susanin
Issues: https://github.com/Fiark/susanin/issues

Итог

Всё началось с очень простой бытовой проблемы: мне надоело дописывать очередной домен или IP в очередной список.

Из этого вырос эксперимент: пусть роутер сам замечает, что конкретный сетевой путь выглядит сломанным, и проверяет альтернативу.

RouterOS оказался хорошим местом для data plane, C-контейнер — удобным control plane, а большая часть сложности оказалась не в самой идее adaptive routing, а в том, чтобы безопасно установить её на реальный MikroTik, пережить reboot и падение VPN, не оставить половину конфигурации и не рассинхронизировать RouterOS API stream.

Проект называется Сусанин, потому что у него нет заранее готовой карты леса. Он пытается выбраться по фактическим следам соединений и в конце концов найти рабочую дорогу.

Отдельное спасибо timbrs/amneziawg-mikrotik-c и его автору. Без этого проекта я, скорее всего, ещё долго продолжал бы дописывать очередной домен в очередной список.

И да: ChatGPT в разработке использовался много. Я этого не скрываю. Для меня здесь интереснее не доказать, что я могу один написать идеальный C, а сделать работающий, объяснимый и проверяемый сетевой инструмент — и честно показать процесс, включая ошибки.


Disclaimer. Материал посвящён сетевой маршрутизации и администрированию собственной инфраструктуры. Используйте описанные подходы в рамках применимого законодательства и правил ваших сетей/провайдеров. Проект экспериментальный; перед установкой делайте backup.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Yet another Rust VPN (TUN-QUIC-TUN)010.9811-07-2026
2Свой VPN на Rust: как я спорил с сетью, TLS и самим собой08.7827-06-2026
3Своя self-hosted двухсерверная VPN-архитектура. Подробный путь разработки и ошибки, с которыми вы можете столкнуться7813-07-2026
4И еще немного извращений из мира прокси и VPN08.7530-07-2026
5Вышла новая версия 1.0.5 программного комплекса Рутокен Логон для Linux: поддержка новых устройств, ОС и OTP-аутентификация011.2622-04-2026
6Специальные/зарезервированные IP-адреса?0602-07-2026
7Зачем нужен ещё один инструмент обхода блокировок, если уже есть VPN04.114-08-2026
8Тимлид и subnet-полукровка: как одна строчка в YAML стоила $50 000 и двенадцати часов прода08.4410-08-2026
9Купил cudy самый мощный с OpenWRT 24, решил клиента l2tp/IKEV1 поднять... Да какой же это ад!-8630-06-2026
10Решено закрыть репозиторий Packman, если не найдутся новые сопровождающие011.5131-08-2026

Классификация: . Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 8.77. Источник: habr.com.