Files

24 KiB
Raw Permalink Blame History

Карта проекта sing-box-rr-edition

Назначение

Репозиторий упаковывает RR Edition — урезанный для OpenWrt форк sing-box-extended. Основной сценарий — network-only/tproxy-ориентированный sing-box для роутеров с пакетами IPK (OpenWrt 24.10) и APK (OpenWrt 25.12).

Это не маленькая самостоятельная реализация: каталог sing-box-rr/ содержит почти полный upstream-код github.com/sagernet/sing-box, а профиль RR Edition задаётся прежде всего registry, build tags и упаковкой. Наличие исходника в дереве не означает, что функция доступна в пакетном бинарнике.

Текущий снимок при составлении карты:

  • ветка: extended;
  • версия пакета: 1.14.1-rr-edition-2.7.2-r3;
  • Go-модуль: github.com/sagernet/sing-box;
  • требование go.mod: Go 1.26.4;
  • пакетные build tags: with_quic,with_utls,with_clash_api.

Верхний уровень

  • VERSION — единственный источник версии и OpenWrt r-релиза.
  • SOURCE_DATE_EPOCH — timestamp для воспроизводимой упаковки.
  • build-ipk.sh — локальная сборка arm64 IPK для OpenWrt 24.10.
  • build-apk.sh — локальная сборка arm64/x86_64 APK для OpenWrt 25.12.
  • packages/ — закоммиченные готовые пакеты по версии OpenWrt и архитектуре.
  • sing-box-rr/ — Go-код, OpenWrt feed Makefile, release-конфигурация и унаследованные upstream workflows.
  • README.md — руководство по урезанию и упаковке; отдельные детали в нём устарели, см. раздел «Оговорки» ниже.

Главные точки изменения

Задача Источник истины
Изменить версию/rN VERSION
Изменить возможности OpenWrt-пакета sing-box-rr/release/DEFAULT_BUILD_TAGS_OTHERS
Добавить/убрать runtime-тип sing-box-rr/include/registry.go и соответствующий include/*_stub.go
Изменить JSON surface и валидацию sing-box-rr/option/
Изменить сборку runtime-графа/порядок запуска sing-box-rr/box.go
Изменить CLI sing-box-rr/cmd/sing-box/
Изменить маршрутизацию sing-box-rr/route/
Изменить DNS sing-box-rr/dns/
Изменить протокол sing-box-rr/protocol/ и регистрация в include/
Изменить OpenWrt service/config sing-box-rr/release/config/
Изменить feed package metadata sing-box-rr/Makefile
Изменить локальную упаковку build-ipk.sh и build-apk.sh

Держать поведение в одном месте: сначала менять build tag/registry, затем config surface и только после стабилизации удалять физические каталоги.

Карта Go-кода

  • cmd/sing-box/ — Cobra CLI: run, check, format, merge, generate, rule-set, tools, api, version и вспомогательные команды.
  • box.go — composition root. Создаёт и связывает все менеджеры, а также владеет фазами PreStart, Start и Close.
  • include/ — связывает интерфейсы с конкретными реализациями. Здесь registry и build-tag пары «реализация/stub».
  • adapter/ — интерфейсы и менеджеры endpoint/inbound/outbound/provider/ service/certificate; граница между ядром и реализациями.
  • option/ — строгий JSON-конфиг, типы опций и первичная валидация. Неизвестные JSON-поля запрещены.
  • route/ — router, connection/network managers, route rules и rule sets.
  • dns/ — DNS router, правила, кэш и transports.
  • protocol/ — inbound/outbound реализации протоколов.
  • transport/ — сетевые транспорты, включая V2Ray transports.
  • common/ — общие сетевые, TLS, HTTP, netns, database и platform helpers.
  • experimental/ — Clash/V2Ray API, cache file и прочие расширения.
  • service/ — отдельные runtime services.
  • test/ — интеграционные протокольные тесты, поднимающие настоящий Box.

Production-путь запуска

OpenWrt procd
  -> /usr/bin/sing-box run -c <config> -D <workdir>
  -> cmd/sing-box/main.go: mainCommand.Execute
  -> cmd/sing-box/cmd.go: preRun
       рабочий каталог + include.Context(registries)
  -> cmd/sing-box/cmd_run.go: run
       readConfig -> сортировка путей -> mergeOptionsList
  -> create
       box.New -> Box.Start
  -> box.go
       managers -> Initialize -> Start -> PostStart -> Started
  -> ожидание сигнала
       SIGHUP: check -> Close -> перечитать конфиг -> создать новый Box
       SIGINT/SIGTERM: Close -> выход

box.New строит граф в следующем центре: endpoint, inbound, outbound, provider, DNS transport/router, network, connection, HTTP client, router, service и certificate-provider managers. Box.Start запускает их фазами; переставлять порядок без трассировки зависимостей нельзя. Box.Close закрывает компоненты явно заданным порядком и агрегирует ошибки.

Конфиг может приходить из нескольких -c и каталогов -C. Все найденные JSON-файлы сортируются по полному пути, затем сливаются. Без флагов CLI ищет config.json в рабочем каталоге.

Фактический пакетный профиль

OpenWrt package builds читают только sing-box-rr/release/DEFAULT_BUILD_TAGS_OTHERS:

with_quic,with_utls,with_clash_api

Из include/registry.go пакетный runtime получает:

  • inbounds: TUN, redirect, TProxy, direct, SOCKS, HTTP, mixed, Shadowsocks, VMess, Trojan, ShadowTLS, VLESS, AnyTLS и QUIC-семейство Hysteria/TUIC/Hysteria2;
  • outbounds: direct, block, selector/urltest/fallback, SOCKS, HTTP, Shadowsocks, VMess, Trojan, ShadowTLS, VLESS, Mieru, AnyTLS и то же QUIC-семейство;
  • DNS transports: TCP, UDP, TLS, HTTPS, DNSCrypt/SDNS, hosts, local, fake-IP, fallback, QUIC и HTTP/3;
  • services: OOM killer; profiler остаётся распознаваемым stub, потому что в package profile нет with_profiler;
  • Clash API и uTLS включены build tags.

Некоторые config types намеренно остаются распознаваемыми, но ведут в stub с явной ошибкой: Naive outbound без with_naive_outbound, удалённые failover/SSR/старый WireGuard, VPN endpoints, resolved/SSMAPI и функции без QUIC при выключенном with_quic. Provider manager и поле providers ещё есть в общем графе, но package registry провайдеров пуст.

Полные DEFAULT_BUILD_TAGS, DEFAULT_BUILD_TAGS_WINDOWS и Docker profile — другие, значительно более широкие профили. Не использовать их как описание возможностей опубликованного OpenWrt-пакета.

URLTest и NetworkQuality

URLTest реализован в sing-box-rr/protocol/group/urltest.go, а измерение и политика выбора по пропускной способности — в соседнем protocol/group/networkquality.go. Сам измеритель не дублируется: группа использует существующий common/networkquality.Run через dialer конкретного outbound.

Конфигурация URLTest поддерживает:

  • strategy: "latency" — поведение по умолчанию и прежний выбор по задержке;
  • strategy: "networkquality" — среди доступных по URLTest участников выбирается максимальный комбинированный score download capacity (bit/s) / URLTest delay (ms); при равном score побеждает меньшая задержка, а нулевая считается равной 1ms;
  • networkquality_ttl — минимальный интервал до повторного сетевого замера, период автоматического теста и TTL кэша; по умолчанию и минимум 30m;
  • networkquality_target — apple по умолчанию, встроенный cloudflare или custom;
  • networkquality_config_url — Config v1 URL для custom; если target не указан, наличие URL автоматически выбирает custom;
  • networkquality_download_url и networkquality_upload_url — прямые endpoint-ы для custom вместо Config v1;
  • networkquality_probe_url — необязательный маленький download endpoint для latency/responsiveness probes; по умолчанию берётся networkquality_download_url.

Apple использует прежний публичный Mensura Config v1. Cloudflare задан встроенным Config v1 с официальными endpoint-ами h3.speed.cloudflare.com/__down и h3.speed.cloudflare.com/__up, поэтому отдельный внешний config-файл ему не нужен. Собственный network-quality/goserver подключается как custom: голый host или корневой URL автоматически дополняется /.well-known/nq, полный URL с путём остаётся без изменений. Config URL и прямые URL взаимоисключаются; в прямом режиме download, upload и probe должны использовать один host.

Успешные NetworkQuality-замеры дедуплицируются между всеми URLTest-группами одного Box. Идентичность участника намеренно упрощена до нормализованного server:port; ключ также включает target, max runtime и режим HTTP/3. Поэтому разные credentials/transports на одном server:port используют общий замер. Общий common/urltest.HistoryStorage хранит последний успешный результат и объединяет одновременно запущенные проверки, а локальная карта группы по-прежнему публикует результат под tag каждого своего участника. Если владелец общей проверки отменён, независимый ожидающий запрос с живым контекстом повторно захватывает ключ и запускает измерение сам, а не наследует чужой context.Canceled. Один общий слот в HistoryStorage разрешает не более одного реального NetworkQuality-теста на весь Box; разные ключи из разных групп ждут в общей очереди, а ожидание прерывается контекстом запроса. Standalone daemon RPC StartNetworkQualityTest использует тот же слот, поэтому тоже не пересекается с групповыми замерами. При strategy: "networkquality" первый автоматический замер запускается после начального latency URLTest, затем повторяется через TTL после завершения предыдущего автоматического прохода. Переключение с latency на networkquality также запускает замер сразу и заново отсчитывает TTL после его завершения; в режиме latency автоматический таймер остановлен. До истечения TTL ручной API-вызов переиспользует кэш. Сам TTL не исключает старый download из сортировки. Обычная ошибка повторного замера публикуется в API, но не удаляет последние успешные capacity и исходный timestamp: они остаются в выборе до нового успешного результата.

HTTP 429 распознаётся как отдельный rate-limit: API возвращает участнику rateLimited: true и текст ошибки, но сохраняет capacity и исходный timestamp последнего успешного замера. Такой download продолжает участвовать в выборе, независимо от его возраста; без сохранённого результата алгоритм возвращается к latency. Сам ответ 429 запускает новый TTL-интервал: повторные API-вызовы внутри него возвращают сохранённый rate-limit без нового сетевого теста.

Upload capacity измеряется, хранится и отдаётся API, но в сортировке не участвует. Если успешных download-результатов нет, выбранный режим networkquality автоматически возвращается к latency-алгоритму. Участники с известной URLTest-задержкой тестируются строго последовательно; участник без delay не занимает общий слот и получает в API ошибку URLTest delay unavailable. Предыдущие успешные capacity и timestamp такого участника сохраняются, но без delay он не участвует в выборе; после появления нового delay сохранённый download снова становится доступен сортировке. Результат каждого участника, включая пропущенного, публикуется API сразу после обработки. Неполная первая карта в выборе не участвует: до полного опроса всей текущей группы URLTest выбирает по задержке. NetworkQuality только публикует полный snapshot и сам не меняет outbound; комбинированный download/latency-выбор применяет следующий полный URLTest-проход. Отдельные delay-ответы Clash API обновляют history, но не переключают NetworkQuality-группу по частично обновлённой карте задержек. При повторных замерах предыдущий полный snapshot действует до готовности нового; отмена оставляет частичные API-результаты видимыми, но не заменяет завершённый snapshot. Живые delay-замеры всех участников сохраняются, но в history самой NetworkQuality-группы публикуется committed delay выбранного now, обновляемый только после полного URLTest-прохода. Публикуемая API-история живёт в памяти конкретной URLTest-группы, общий кэш — в памяти Box; ни то ни другое не сохраняется между перезапусками.

Runtime-управление находится в Clash meta API experimental/clashapi/api_meta_group.go:

  • GET|PUT /group/{name}/strategy — прочитать или изменить стратегию;
  • GET /group/{name}/networkquality — получить последний замер;
  • POST /group/{name}/networkquality — последовательно обработать всех участников и измерить только имеющих URLTest delay; тело принимает maxRuntimeSeconds и http3.

Ответ NetworkQuality содержит progress.current и progress.total: во время прохода это 1-based номер обрабатываемого участника и фиксированный размер снимка группы на старте; вне активного прохода оба значения равны нулю.

Контракт между API и группой находится в adapter/experimental.go: прежний URLTestGroup сохранён для source compatibility, а новые методы собраны в расширяющем NetworkQualityURLTestGroup. JSON-поля конфига находятся в option/group.go, а регрессионные тесты выбора и конкурентности — в protocol/group/networkquality_test.go. Встроенный Cloudflare Config и передача inline-конфига в общий измеритель проверяются в common/networkquality/networkquality_test.go.

OpenWrt-интеграция и упаковка

UCI-конфиг release/config/openwrt.conf по умолчанию включает сервис и задаёт:

  • конфиг: /etc/sing-box/config.json;
  • workdir: /usr/share/sing-box;
  • stderr logging: включён.

Init script release/config/openwrt.init использует procd, следит за файлом конфига, включает respawn и reload trigger, выставляет высокий nofile.

Оба локальных скрипта:

  1. читают версию и SOURCE_DATE_EPOCH из корня;
  2. читают package tags из DEFAULT_BUILD_TAGS_OTHERS;
  3. собирают ./cmd/sing-box внешним Go с -trimpath, version ldflag и опциональным CGO/musl toolchain;
  4. укладывают бинарник, JSON/UCI/init/keep-файлы;
  5. формируют package metadata и сохраняют артефакт в packages/.

Пакет называется sing-box-rr, предоставляет sing-box и sing-box-tiny, конфликтует с sing-box, зависит от ca-bundle, kmod-inet-diag, kmod-tun и создаёт пользователя sing-box с UID/GID 5566. Совместимость с sing-box-tiny нужна downstream-пакетам вроде ZeroBlock.

Локальные пути SDK и /usr/local/go/bin/go жёстко заданы в скриптах. Их можно переопределять только предусмотренными переменными; переносимость не предполагать. IPK helper поддерживает arm64, APK helper — arm64 и x86_64. Feed sing-box-rr/Makefile поддерживает OpenWrt aarch64 и x86_64 и также использует внешний Go вместо SDK golang/host.

Базовые команды из корня:

./build-ipk.sh arm64
./build-apk.sh arm64
./build-apk.sh x86_64

--force удаляет выделенные Go build/module caches в /tmp перед сборкой.

Тесты и проверки

В дереве 110 файлов *_test.go. Основные группы:

  • common/ — низкоуровневые helpers;
  • test/ — интеграционные тесты протоколов, создающие настоящий box.Box;
  • route/ — запуск соединений, rule-set lifecycle и семантика правил;
  • dns/ — race/fallback/order и transport multiplexer;
  • option/ — строгий JSON и валидация;
  • transport/, service/, experimental/, adapter/ — точечные тесты.

Интеграционный harness в test/box_test.go вызывает production-путь include.Context -> box.New -> Box.Start -> Box.Close. Часть тестов требует сетевых возможностей/root; upstream workflow на Unix запускает весь suite через sudo.

Рекомендуемая локальная проверка из sing-box-rr/:

BUILD_TAGS="$(cat release/DEFAULT_BUILD_TAGS_OTHERS)"
/usr/local/go/bin/go test -tags "$BUILD_TAGS" -ldflags "$(cat release/LDFLAGS)" ./...

Для полного соответствия CI на Unix может понадобиться -exec sudo. Go toolchain должен удовлетворять go.mod; build scripts выставляют GOTOOLCHAIN=go<version>+auto, если переменная не задана явно.

Workflow-файлы находятся в sing-box-rr/.github/workflows/, а не в корневом .github/workflows/ этого wrapper-репозитория. Поэтому не считать их автоматически действующим CI корневого репозитория без отдельной проверки хостинга; использовать их как upstream-рецепты test/lint/build.

Оговорки и известные несоответствия

  • go.mod требует Go 1.26.4, хотя README.md ещё упоминает 1.26.1. Источник истины — go.mod.
  • README.md показывает ./build-ipk.sh x86_64, но текущий IPK helper принимает только arm64|all; x86_64 реализован только в APK helper/feed.
  • README.md говорит, что Naive outbound исключён из default builds. Это верно для package profile DEFAULT_BUILD_TAGS_OTHERS, но полный DEFAULT_BUILD_TAGS всё ещё содержит with_naive_outbound.
  • Дефолтный release/config/config.json слушает :::8080 и содержит опубликованный статический Shadowsocks-пароль. Это пример, а не безопасная production-конфигурация; пароль и bind policy нужно менять до установки на доступный извне роутер.
  • В репозитории закоммичены бинарные IPK/APK. После изменения версии, tags, registry или release config нужно пересобрать нужные архитектуры и проверить, что metadata и содержимое пакетов синхронны с исходниками.

Правило работы с проектом

Перед изменением сначала проследить существующий production call chain и найти владельца поведения. Для урезания использовать один путь:

  1. package build tag;
  2. registry или build-tag stub;
  3. проверка валидных конфигов и production-тестов;
  4. только затем чистка option/constant/source surface.

Не дублировать feature policy одновременно в новых скриптах и Go-коде. Версия живёт в VERSION, package tags — в DEFAULT_BUILD_TAGS_OTHERS, runtime registration — в include/registry.go.