ratatoskr
- title
- ratatoskr
- type
- toolbox
- summary
- Встраивает узел сети Yggdrasil в процесс на Golang со стеком gVisor в пространстве пользователя - без TUN и root
- tags
- go, networking, mesh, p2p
- language
- Go
- license
- LGPL-2.1
- created
- 2026-07-23
- updated
- 2026-07-23
- lang
- ru
- translation_of
- ratatoskr
- source_updated
- 2026-07-23
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Ratatoskr запускает узел Yggdrasil прямо внутри вашего процесса и предоставляет результат в виде net.Conn, net.Listener и net.PacketConn. Здесь нет TUN-устройства, прав root и отдельного демона yggdrasil, за которым нужно следить: передача данных идёт через netstack из gvisor, управляемый ядром Yggdrasil и Ironwood. ratatoskr.New объединяет в едином жизненном цикле ядро, стек TCP/UDP на базе gVisor, запросы NodeInfo и опциональный селектор управляемых пиров, а node.DialContext напрямую передаётся в http.Transport. У модуля всего четыре прямые внешние зависимости: ядро Yggdrasil, gVisor, golang.org/x/net и go-socks5. Требуется Golang 1.25 или новее.
Он решает ту же задачу, что и ygglib + VTun из asciimoth-ygg (рассмотренные в yggdrasil-embedded-go), но с другой стороны: вместо форка апстрима Yggdrasil ради библиотечного интерфейса Ratatoskr оставляет апстрим зависимостью и оборачивает его, а сопутствующие службы разносит по подпакетам, которые можно импортировать отдельно от корневого фасада.
Структура
Корневой пакет служит фасадом. Каждый подпакет в mod/ завязан на узкий локальный интерфейс, а не на ratatoskr.Obj, так что можно использовать mod/socks со своим dialer'ом или mod/forward с любой реализацией сети из пяти методов, вообще не создавая узел.
mod/core- ядро Yggdrasil, сетевой интерфейс и сокеты gVisor, идентификаторы узла, пиры, multicastmod/peermgr- проверяет кандидатов в пиры, опрашивает их ограниченными пачками и оставляет пир с наименьшей задержкой для каждого протокола (MaxPerProto)mod/socks- SOCKS5 поверх TCP или Unix-сокета с лимитами на TCP и UDP ASSOCIATEmod/resolver- резолвит<64-hex>.pk.ygg, IP-литералы и при необходимости обычный DNS через переданный dialermod/forward- неизменяемые сопоставления TCP/UDP между локальной сетью и Yggdrasilmod/ninfo- удалённый поиск NodeInfo с объединением параллельных запросов к одной целиmod/probe- ограниченный поиск топологии в ширину, пути остовного дерева, трассировка маршрутовmod/sigils- типизированные фрагменты NodeInfo
Сигилы (sigils) - более интересная архитектурная идея. NodeInfo в Yggdrasil представляет собой открытый map[string]any: для программ со строгой схемой он бесполезен, а если его закрыть, его нельзя использовать как схему. Сигил берёт на себя группу ключей NodeInfo, проверяет локальные данные, публикует эти ключи и распознаёт такую же структуру в ответе от удалённого узла. Встроены четыре сигила: info (имя, роль, локация, контакты), services (именованные порты), public (точки пиринга по транспортам) и inet (публичные IP-адреса в интернете). Конфликт ключей между сигилами либо между сигилом и Config.NodeInfo прерывает вызов New с ошибкой ErrInvalidSigils, не допуская публикации неполных метаданных. Ask и AskAddr разбирают известные им сигилы, а неизвестные, но валидные оставляют без изменений.
Использование
node, err := ratatoskr.New(ratatoskr.ConfigObj{
Ctx: ctx,
Peers: &peermgr.ConfigObj{
Peers: []string{"tls://peer.example:17117", "quic://peer.example:17117"},
MaxPerProto: 1,
},
})
if err != nil {
panic(err)
}
defer node.Close()
client := &http.Client{Transport: &http.Transport{DialContext: node.DialContext}}
Если передать nil в Config, генерируются случайные ключи и отключается админский listener; для постоянной идентичности перед вызовом New загружают сохранённый config.NodeConfig (HJSON по пути PrivateKeyPath). Одновременная настройка пиров в Config.Peers и ConfigObj.Peers завершается с ErrPeersConflict, так как управлять одним набором пиров пытались бы два владельца. Слушатели создаются через node.Listen("tcp", ":8080") и работают внутри userspace-стека. Close можно вызывать повторно: метод параллельно завершает зависимые службы, затем ядро, укладываясь в общий бюджет CloseTimeout (по умолчанию 10 секунд); при превышении лимита возвращается ErrCloseTimedOut, а очистка ресурсов продолжается в фоне, не блокируя вызывающий код.
Распространение через Yggdrasil
Ratatoskr распространяется тремя путями, и это три разных идентификатора модуля Golang, которые нельзя смешивать в рамках одного модуля: канонический github.com/voluminor/ratatoskr, HTTPS-зеркало ratatoskr.space/pkg/ratatoskr и то же зеркало, доступное внутри самой mesh-сети по адресу 14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg. Для маршрута через mesh-сеть нужно направить GOPROXY на этот хост, выставить GOSUMDB=off (публичная база контрольных сумм индексирует путь на GitHub, а не переписанный) и задать GOINSECURE для этого хоста, поскольку трафик внутри зашифрованной оверлейной сети идёт по обычному HTTP:
YGG_HOST="14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg"
GOPROXY="http://${YGG_HOST}" GOSUMDB=off GOINSECURE="${YGG_HOST}/*" \
go get "${YGG_HOST}/pkg/ratatoskr@latest"
Это протокол module proxy, используемый как обычный транспорт поверх оверлейной сети - то же самое свойство, которое cursed-bundler-go-get-ruby-gems эксплуатирует в противоположном направлении.
Ограничения
Пропускная способность - плата за сетевой стек в пространстве пользователя. Собственный бенчмарк проекта в сравнении с прямым подключением в Docker на одном хосте показывает медианную скорость TCP в 198 МиБ/с против 4809 МиБ/с (4,1%) и UDP в 44,5 МиБ/с против 440 МиБ/с (10,1%, причём это полезная пропускная способность приёмника при потере 24% пакетов, а не ёмкость без потерь). Профилирование показывает, что основные затраты приходятся на криптографию Yggdrasil и Ironwood, обработку пакетов в gVisor, системные вызовы и копирование данных, а не на управляющий код самого Ratatoskr. С одной стороны, это хороший показатель для реализации самого проекта, но с другой означает, что на этом уровне узкое место не устранить.
Метод Core().EnableAdmin намеренно оставлен незащищённым, о чём прямо сказано в документации: он напрямую вызывает реализацию админки из апстрима Yggdrasil. Она может выполнить os.Exit(1) при некорректном адресе или ошибке очистки сокета, не имеет аутентификации, ограничений на размер запроса и таймаутов на соединение, а также способна оставлять keepalive-соединения активными после вызова DisableAdmin. Доступ к нему равносилен полному контролю над процессом.
В mod/forward значения MaxTCPConnections == 0 и MaxUDPSessions == 0 означают отсутствие ограничений, поэтому сопоставление, доступное недоверенным узлам, будет создавать goroutine на каждое принятое соединение и на каждый источник UDP. На любом публичном контуре оба параметра нужно задавать явно.
CI работает на Golang 1.26.5 и тестирует релизный исходный код на Linux, macOS и Windows, а также проверяет компиляцию для 25 комбинаций GOOS/GOARCH, включая FreeBSD, OpenBSD и NetBSD. Unix-сокеты и часть транспортов Yggdrasil платформозависимы. В ветке разработки сгенерированные файлы не отслеживаются: при сборке из репозитория потребуется выполнить шаги инициализации из CONTRIBUTING.md, тогда как в тегированных релизах уже есть сгенерированный пакет target и полный go.mod.
Репозиторий
github.com/voluminor/ratatoskr - Go, LGPL-2.1. Проект одного автора, первые релизы в 2026 году.