EnglishРусский Map

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, идентификаторы узла, пиры, multicast
  • mod/peermgr - проверяет кандидатов в пиры, опрашивает их ограниченными пачками и оставляет пир с наименьшей задержкой для каждого протокола (MaxPerProto)
  • mod/socks - SOCKS5 поверх TCP или Unix-сокета с лимитами на TCP и UDP ASSOCIATE
  • mod/resolver - резолвит <64-hex>.pk.ygg, IP-литералы и при необходимости обычный DNS через переданный dialer
  • mod/forward - неизменяемые сопоставления TCP/UDP между локальной сетью и Yggdrasil
  • mod/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 году.