Cjdns - технология и соответствующее ПО для построения mesh-сети. Cjdns сейчас на слуху у многих, но все же представить ее в начале статьи стоит, и считаю, что точнее всего опишет заголовок на гитхаб:

Cjdns — это зашифрованная IPv6 сеть, в которой используются публичные ключи шифрования для присвоения публичного адреса и распределённой таблицы маршрутизации (DHT). Это позволяет создавать сети с очень простой настройкой, которые будут защищены от потенциальных проблем ныне существующих IPv4 и IPv6 сетей

Теперь немного отсебятины. Протокол и ПО cjdns в целом позволяют создать свою сеть внушительных масштабов, при этом не прикладывая человеческой руки к настройке адресного пространства или протоколов маршрутизации. В целом, чтобы попасть в отдельно взятую сеть требуется только подключиться к одному из узлов сети, и этого будет достаточно. Вам сразу будет видна вся сеть, как и Вы сети - для каждой ноды генерируется уникальный IPv6-адрес, из частного диапазона fc00::/8, который доступен из любой точки, что дает возможность работать p2p-приложениям без преодоления NAT или прокси. Работает cjdns как поверх UDP, для обеспечения функционирования в привычных нам сетях, такие как Интернет, так и напрямую поверх Ethernet, что дает возможность установить связь между узлами в пределах L2-сегмента. Работа последнего упрощена с перспективы пользователя до неприличия. После запуска Cjdns начинает посылать броадкастовые кадры в сеть с полем type равным 0xfc00, и соответственно сам их слушает. Узлы находят друг друга и устанавливают связь, после чего идет обмен данными.

cjdns1 Обмен данными между двумя cjdns-узлами в одном L2-сегменте. Обратите внимание на поле type = 0xfc00 - это значит, что в кадр упанованы данные cjdns. Даже IP-адреса на интерфейсы вешать не нужно!

Чем же это хорошо? Конечно же в первую очередь подразумевают построение сети по WiFi-линкам между узлами. Тут используется либо протокол 802.11s “WiFi Mesh”, как низкоуровневый транспорт, либо просто через соединение AP-client (“точка доступа - клиент”) или Ad-hoc (“точка-точка”). Кабельное же соединение можно, например, использовать для организации линка через локальную сеть провайдера, если он все еще подключает абонентов через широкие L2-сегменты, хотя бы в пределах района.

Ну и, конечно, шифрование! Шифруется как “линк” между узлами, так и передача пакетов “end-to-end”. Транзитная нода не может прочитать пакеты, которые протекают через нее между двумя абонентами.

Самой большой публичной сетью, построенной на cjdns, на данный момент является Hyperboria. Визуально масштабы можно оценить на карте fc00.org. Карта на момент написания статьи показывала 648 активных узлов. К сети можно подключиться как подцепившись к публичным нодам, так и договорившись с товарищем о пиринге. Далее я опишу процесс установки и настройки, но сперва рассмотрим принципы работы cjdns.

О “кишках” cjdns

Функционирование cjdns держится на следующем принципе:

cjdns is made of three major components which are woven together. There is a switch, a router, and a CryptoAuth module. With total disregard for the OSI layers, each module is inherently dependent on both of the others. The router cannot function without routing in a small world which is made possible by the switch, the switch is blind and dumb without the router to command it, and without the router and switch, the CryptoAuth has nothing to protect.

cjdns состоит из трех главных компонентов, которые связаны между собой. Это модули switch (коммутатор), router (маршрутизатор) и CryptoAuth. С полным пренебрежением к уровням модели OSI, каждый из модулей по своему существу зависим от двух остальных. Маршрутизатор не может функционировать без маршрутизации в маленьком мире, которая возможно благодаря коммутатору. Коммутатор глуп и слеп без команд маршрутизатора. А без коммутатора и маршрутизатора модулю CryptoAuth нечего защищать.

jdns-trianle

Весьма расплывчато? Постараемся разобраться чуть детальнее, посмотрев в whitepaper.

Cjdns-коммутатор выполняет непосредственную маршрутизацию пакетов от точки А до точки B. Маршрутизация ведется на основе т.н. Interface, Self Interface, Director и Route Label. Interface - в данном контексте это линк “точка-точка” между двумя cjdns-коммутаторами, который как раз эмулируется UDP-тоннелем или подключением по Ethernet. Self Interface - специальный интерфейс, который нужен для приема пакетов, предназначенных для самой ноды, а не для пересылки дальше по цепочке. Director - это двоичное число произвольной длины, на основе которого принимается решение о маршрутизации в заданный интерфейс. А Route Label - это набор Director’ов, которые описывают полный путь от точки A к точке B. Внутри каждого пакета содержится Route Label. При прохождении пакета по нодам, начиная от самой ноды-источника, каждая из них “смотрит” в конец Route Label и ищет там свой Director. Если находит - то нода знает Interface, в который следует послать пакет дальше и готовит пакет к отправке. Нода сместит строку Route Label вправо на длину найденного Director’а, тем самым стерев его из Route Label, а в начало Route Label запишет Director интерфейса откуда пришел пакет. Итого, получается, что в начале пути пакета в нем записан маршрут от точки А к точке B, а в конце его пути уже будет записан полный обратный маршрут от B к А. И сразу стоит заметить, что в начале пути в конце Route Label стоит Director описывающий Self Interface точки B, а к концу пути в начале Route Label уже записан Self Interface точки А. Чтобы разобраться в принципе, следует посмотреть пример такой маршрутизации пакета.

cjdns-route-label-transmit
Пример прохождения пакета. Route label меняется на каждом узле, начиная от узла-источника. Иллюстрация не отражает реального положения дел, но показывает принцип. В реальности выполняются некоторые битовые операции и есть ограничения на идентификатор Director’а

Вы могли могли заметить, что Director’ы не связаны с глобальными оконечными адресами узлов, и как это называется, не globally-unique, т.е. не уникальны глобально, и имеют только локальное значение на cjdns-коммутаторе. Да и весь Route Label - это всего лишь 64-битная строка. И поэтому для определения пути до оконечной точки нужен cjdns-маршрутизатор.

Cjdns-маршрутизатор выполняет 3 функции. Он периодически ищет маршруты до нод в сети, отвечает на запросы поиска, и пересылает пакеты из стандартного стека в cjdns. Маршрутизатор отвечает на запросы поиска координатами ноды, которую считает ближайшей к точке назначения. А ближайшей будет та нода, которая ближе всего по т.н. расстоянию адресного пространства (address space distance). Считается по так называемому “XOR metric”, впервые представленным в Kademlia DHT. XOR metric по сути это складывание по модулю 2 двух адресов, и представление результата в виде целого числа. Чем более “разные” адреса, тем больше эта метрика. Если адреса одинаковые, то метрика равна 0. Маршрутизатор, при поиске пути до запрашиваемого IPv6-адреса начинает рассылать сообщения поиска узла своим известным нодам. Ноды отвечают списком своих известных нод, которые они считают ближайшими до искомой (по address space distance, с некоторой фильтрацией ответов с целью исключения петель поиска), включая в ответ Route Label до них со своей перспективы. Маршрутизатор заносит в свою таблицу маршрутизации новые разведанные ноды. Путь до новых разведанных нод получается сцепкой присланной Route Label, которую прислала нода, с Route Label до ноды которая прислала ответ.

cjdns-route-label-splicing
Сцепка Route Lable’ов для с целью построить полный путь до ноды.

Такой поиск повторяется рекурсивно, пока искомая нода не будет найдена. Причем разведанные в ходе поиска ноды продолжают храниться в базе. Если потребуется к этим нодам обратиться снова, можно взять сохраненные результаты разведки без дополнительного поиска! Все же следует сказать, что сейчас используется также и централизованный подход получения маршрутов - по запросу от суперноды. Супернода - это нода в сети cjdns, которая периодически сканирует сеть на предмет новых пиров и заносит в базу. По запросу к ней можно получить маршрут между любыми двумя нодами.

Кстати говоря, искомый адрес, как можно было догадаться, это привычный IPv6-адрес узла. И сразу стоит заметить, что изменить его на красивый и блатной не выйдет, так как получается IPv6-адрес путем подбора нужного публичного ключа шифрования (выполняется двойной подсчет sha-512, пока не получится строка, начинающаяся с байта fc), и функционально с ним связан. Поэтому плавно перейдем к модулю CryptoAuth.

CryptoAuth требуется для аутентификации и шифрования данных. Первый уровень шифрования это “end-to-end”, или “router-to-router”, который упаковывается в шифрование “node-to-node”, или “switch-to-switch”. Таким образом с перспективы транзитных нод не видно, какие данные пересылаются между двумя абонентами сети cjdns, а с перспективы канала связи между транзитными нодами не видно, между какими абонентами сети cjdns пересылаются данные. Шифрование обеспечивается сессиями, которые устанавливаются путем пересылки специальных пакетов.

На этом моменте стоит перейти к практической настройке cjdns.

Подключение к Hyperboria

Инструкция по установки cjdns на официальном git очень подробно описывает весь процесс и для разных дистрибутивов. Кратко рассмотрим процесс установки на Ubuntu.

Устанавливаем зависимости:

sudo apt install nodejs git build-essential python2.7

Клонируем репо и собираем cjdns:

git clone https://github.com/cjdelisle/cjdns.git cjdns
cd cjdns
./do

Генерируем новый конфиг-файл:

./cjdroute --genconf >> cjdroute.conf

Не забываем выставить на него нужные права - принадлежать должен он только пользователю, из под кого вы собираетесь запускать cjdns и запрещаем его читать кому-либо другому. Нужно это, так как файл содержит закрытый ключ.

Открываем конфиг. И как раз первым встречаем закрытый ключ, а еще публичный ключ и сгенерированный IPv6-адрес:

{
    // Private key:
    // Your confidentiality and data integrity depend on this key, keep it secret!
    "privateKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",

    // This key corresponds to the public key and ipv6 address:
    "publicKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.k",
    "ipv6": "fcxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx",

Естественно, privateKey никому рассказывать не стоит. ipv6 - это будет Ваш адрес в сети cjdns. В этом блоке, собственно, ничего менять больше не требуется, и вообще не стоит.

Секция "authorizedPasswords": требуется, если вы хотите к себе подключить каких-либо пиров. Для исходящих соединений дополнительная её настройка не требуется и поговорим о ней позже.

Пропустим еще кусок конфига и остановимся на секции "interfaces":. Настроим секцию "UDPInterface": для того, чтобы подключаться к cjdns через Интернет. Раскомментируем настройку "bind": "0.0.0.0:20789" и оставим как есть - это позволит слушать устройству все интерфейсы для построения UDP-тоннеля в cjdns. Получается так:

"UDPInterface":
[
    {
        // Bind to this port.
        "bind": "0.0.0.0:20789",
        // Set the DSCP value for Qos. Default is 0.
        // "dscp": 46,

Если же есть желание ограничить прослушиваемый сокет одним интерфейсом или поменять порт, можете вписать свой вариант.

Ниже в секции "connectTo" наиболее интересный момент - это настройка пиров, с которыми вы будете соединяться. Для начала нужно решить - к кому же подключиться, чтобы войти в сеть. Какую сеть? Подключаемся к сети Hyperboria. В сети есть ряд публичных нод, доступных для подключения любому желающему. Для того, чтобы подключиться, нужно получить ряд минимальных данных - IP (в сети Интернет) и порт подключения, публичный ключ и пароль. К дополнительным данным относятся имя пользователя (если используется для аутентификации), контактные данные, географическая локация. Записывается это в следующем виде. Как пример, данные для подключения к моей ноде:

"46.151.26.194:60574": {
    "login": "public",
    "password":"d41862xcrd0q6l9v6mwbvrkuk70p0ht",
    "publicKey":"34zb5vlg59rghf857h70tpp0ycqpts1cmtgbxltqcrp68c883vt0.k",
    "peerName":"netwhood.online"
},

Имеет смысл подключаться к географически-ближайшей ноде, чтобы минимизировать задержки. Некоторые ноды перечислены на hyperboria/peers. Я же просто пошел на карту fc00.org и посмотрел данные крупнейших. Я подключился к следующим нодам:

Итого весь кусок конфига может выглядеть следующим образом:

"UDPInterface":
[
    {
        // Bind to this port.
        "bind": "0.0.0.0:20789",
        // Set the DSCP value for Qos. Default is 0.
        // "dscp": 46,

        // Nodes to connect to (IPv4 only).
        "connectTo":
        {
            "46.151.26.194:60574": {
                "login": "public",
                "password":"d41862xcrd0q6l9v6mwbvrkuk70p0ht",
                "publicKey":"34zb5vlg59rghf857h70tpp0ycqpts1cmtgbxltqcrp68c883vt0.k",
                "peerName":"netwhood.online"
            },
            "164.132.111.49:53741": {
                "password" : "cr36pn2tp8u91s672pw2uu61u54ryu8",
                "publicKey" : "35mdjzlxmsnuhc30ny4rhjyu5r1wdvhb09dctd1q5dcbq6r40qs0.k",
            },
            "195.201.148.242:26721":{
                "password":"public_access",
                "publicKey":"1gy9myu5fhd2uhbjgju6nn54uwgz1dtr5g9r3kwsc5r9f32567s0.k",
                "user":"weuxel",
                "contact": "hype@smash-net.org",
                "peerName": "weuxel.modi"
            },
        }

        }
    },

Настройки секции "ETHInterface": используются для подключения к cjdns поверх Ethernet. Если вы не подключаетесь таким образом, то настройку "beacon" лучше поставить 0, чтобы не захламлять локальную сеть трафиком:

"beacon": 0,

Если все же собираетесь подключиться таким образом, то вот несколько слов об этом. Установка "beacon" в значение 1 позволяет только принимать запросы на подключение - интерфейс не будет броадкастить в сеть сообщения присутствия. Значение 2 заставляет интерфейс и принимать подключения, и броадкастить в локальную сеть сообщения присутствия. Очевидно, что для того, чтобы установить соединение, нужно чтобы с одной стороны было значение 2, а с другой либо тоже 2, либо 1. Чуть ниже можно определить конкретные MAC-адреса пиров для подключения, аналогично настройке пиров через UDP-интерфейс, чтобы не броадкастить фреймы в сеть.

В конце, для удобства настроим еще имя tun-интерфейса, который будет использоваться сетевым стеком для пересылки пакетов по cjdns. Задается оно в "tunDevice":. По умолчанию стоит tun0, но для исключения пересечения имен туннельных интерфейсов лучше задать другое. Например:

"tunDevice": "tun-cjdns"

Похоже, что все. Но все же еще раз напомню, что запустив cjdns, вы будете у всей сети как на ладони. А что это значит? Это значит, что все сервисы, которые слушают IPv6-адрес [::], будут слушать и коннекты на туннельный интерфейс tun-cjdns. А значит нужно настроить фаерволл. Это может быть как firewalld, для удобной настройки профилей на десктопе, так и ip6tables, например на серверной машине. Если используется firewalld, то скорее всего к интерфейсу уже будет применена зона public. Просто ограничьте на этой зоне сервисы, или создайте для cjdns отдельную зону.

Ну, сохраняем файл и запускаем cjdns. Не знаю у кого как, а у меня tun-интерфейс создается только запуском из-под рута:

sudo ./cjdroute < ./cjdroute.conf

Проверяем, что все запустилось. Смотрим, появился ли tun-интерфейс по команде ip addr:

tun-cjdns: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1304 qdisc fq_codel state UNKNOWN group default qlen 500
    link/none 
    inet6 fc52:c25:a033:ad59:d58a:613f:69c5:2ca7/8 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::984c:fb94:611c:e0d5/64 scope link stable-privacy 
       valid_lft forever preferred_lft forever

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

$ ./contrib/python/peerStats 
46.151.26.194:60574     fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d v20     0000.0000.0000.0019     in 43989        out 105183      ESTABLISHED     dup 0 los 6 oor 0     'netwhood.online'
104.200.29.163:53053    fc6a:30c9:53a1:2c6b:ccbf:1261:2aef:45d3 v20     0000.0000.0000.0017     in 683749       out 344596      ESTABLISHED     dup 0 los 20 oor 0    'outer'
164.132.111.49:53741    fc6c:99a2:171a:f36a:8cd0:cc6b:efb7:8bb4 v20     0000.0000.0000.0015     in 1301486      out 1518017     ESTABLISHED     dup 0 los 262 oor 0   'outer'
195.201.148.242:26721   fc1e:9771:3881:8cff:d5b2:bfac:930f:b4ed v20     0000.0000.0000.0013     in 892129       out 1407227     ESTABLISHED     dup 0 los 97 oor 0    'weuxel.modi'

ESTABLISHED напротив данных пира говорит о том, что соединение установлено.

Попингаем кого-нибудь из сети. Например своего же пира:

$ ping6 fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d
PING fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d(fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d) 56 data bytes
64 bytes from fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d: icmp_seq=1 ttl=42 time=2.31 ms
64 bytes from fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d: icmp_seq=2 ttl=42 time=2.67 ms
64 bytes from fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d: icmp_seq=3 ttl=42 time=3.02 ms
64 bytes from fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d: icmp_seq=4 ttl=42 time=3.43 ms
^C
--- fc70:e98e:5c8f:3971:494c:361b:4ca8:4c5d ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 2.316/2.862/3.436/0.418 ms

Можно даже пройти на наш сайт внутри сети Hyperboria по адресу h.netwhood.online.

Собственно, с этого момента вы полноправный участник сети. Можно шарить свои сервисы, без труда использовать p2p приложения (если они могут в IPv6).

Ресурсы внутри (и снаружи)

Не так уж и много на данный момент.

  • Cписок внутренних ресурсов Hyperboria Intelligence Agency - ‘Services for the Meshes’: hia.cjdns.ca. Здесь же огромный наскан внутренних адресов - hia.cjdns.ca/watchlist.

  • IRC-сервер внутри сети Hyperboria HypeIRC hypeirc.net. Прямая ссылка на сервер - irc.hypeirc.net

  • DC-хаб adc://dc.on.hyperboria.name:1511. Для подключения нужен DC-клиент, поддерживающий IPv6. На Linux это, например, nсdc.

  • HTTP-прокси внутри сети Hyperboria для доступа в Интернет - proxy.on.hyperboria.name:8118.

  • Поиск по Hyperboria с помощью поисковой машины YaCy - hyperborian.org. Весь индекс можно посмотреть тут. Доступны ресурсы через прокси, даже без подключения к Hyperboria.

  • Карта fc00.org, только внутри сети - h.fc00.org.

  • Еще одна карта сети, от CJD - h.snode.cjd.li

Своя большая нода

Если есть желание сделать свою 24/7 ноду, с возможностью входящих подключений, нужно внести несколько настроек.

Во-первых, хорошо бы для подключения из Интернета иметь статический IP-адрес. И соответственно, пробросить на маршрутизаторе порт из настройки "UDPInterface" -> "bind" во внутреннюю сеть.

Во-вторых, сделать публичный или персональный ключ для входящего подключения. Делается это в секции "authorizedPasswords". Генерируем пароль скриптом mkpasswd в папке с дистрибутивом. Потом задаем логин и пароль. Например:

"authorizedPasswords":
[
    {"password": "d41862xcrd0q6l9v6mwbvrkuk70p0ht", "user": "public"}
],

И передать товарищу, или опубликовать всем, ключ и пароль, в следующем виде:

"46.151.26.194:60574": {
    "login": "public",
    "password":"d41862xcrd0q6l9v6mwbvrkuk70p0ht",
    "publicKey":"34zb5vlg59rghf857h70tpp0ycqpts1cmtgbxltqcrp68c883vt0.k",
    "peerName":"netwhood.online"
},

В-третьих, еще раз напомню, что у всей сети будете как на ладони. Поэтому настраиваем ip6tables закрывая порты.

В-четвертых, нужно сделать автозапуск. Я просто засунул в cron строку:

@reboot /opt/cjdns/cjdroute < /opt/cjdns/cjdroute.conf

Но лучше конечно сделать через init.d.

Ну и, собственно, после этого сохраняем конфиг и запускаем cjdns.

Подытоживание

Cjdns очень интересен в плане обеспечения полного p2p и легкостью развертывания. NAT, маршрутизация? Забудьте и хостите что угодно, из любой точки, с любой машины. Передача данных напрямую без участия сервера? Да, конечно. Начиная просто от запуска обычной сетевой шары, и заканчивая запуском IPFS. Просто сделать свою маленькую сеть с друзьями, чтобы играть по вечерам в сетевые игры и вообще быть в изоляции от Большой Сети? Почему бы и нет. Если расстояние небольшое - можно через WiFi. Для подключения через Интернет нужен только один хаб-связующий, да и настройка даже в чем-то проще, чем OpenVPN. Выписка сертификатов, обмен ключами? Зачем, если сама сеть обеспечивает p2p шифрование.

Поддержать проект и попробовать в любом случае стоит.

Напоследок, интервью с автором cjdns - Caleb James DeLisle.

По теме

Конфренция в Telegram “Meshnet & Crypto-Anarchy” - общение по данной теме;
distributed.earth - сайт и сообщество о распределенных системах;
“Прекрасные интернеты будущего”: интервью с создателем протокола Cjdns;
wiki - cjdns;
cjdns.cupivan.ru - еще одна карта cjdns;
habr.com - Как подключиться к Hyperboria;
habr.com - Mesh-роутер — это просто;
archlinux.org - Cjdns.