
Прототип mesh-сети IoT/любительского радио с глобальной маршрутизацией через интернет

Система mesh-маршрутизации, которая поддерживает использование прокси OpenDHT в качестве бэкенда, благодаря чему узлы могут связываться напрямую через интернет. Очень ранняя пре-альфа, концепт-прототип, может не работать на деле и т. д.
Она предназначена как для любительских/HAM-сценариев, так и для более типичных потребительских/коммерческих задач Интернета вещей (IoT), но при этом специально не пытается заменить интернет или покрывать большие территории с высокой нагрузкой всенаправленными антеннами.
В ней нет маршрутизации следующего прыжка в стиле Meshtastic, отсюда и название LazyMesh. Если вы хотите использовать её для автономных (offgrid) приложений в плотно застроенной местности, вам, вероятно, понадобятся направленные антенны и вручную согласованные номера маршрутов.
Пока это единственное реальное приложение. Откройте пример скетча Arduino, измените его, указав ваше имя пользователя и секретный ключ канала — это должен быть надёжный пароль.
Введите сообщение в последовательный монитор Arduino — вы сможете общаться со всеми другими устройствами.
Сообщения должны доходить, если узлы находятся в одной сети или у обоих есть доступ в интернет.
Посетите Веб-клиент, чтобы общаться с узлом через MQTT из интернета. Просто укажите имя пользователя, добавьте канал и введите пароль канала. Имя канала может быть любым и влияет только на подписи в интерфейсе.
Вы также можете попробовать пример скетча чтения/записи данных. Он открывает ID данных 195 для чтения, а ID данных 196 — для чтения и записи. Зайдите на сайт, введите данные канала и используйте диалог запроса данных, чтобы запросить ID 196 у всех устройств.
Затем нажмите «set» и установите другое значение, после чего попробуйте прочитать его снова.
Веб-клиент в настоящее время жёстко привязан к test.mosquitto.org, в будущем это изменится.
Узлам нужен способ синхронизировать время для связи. Сейчас, если они никогда не получали время из доверенного источника, они устанавливают своё время на основе любого случайного пакета.
Это может быть риском для безопасности, допускающим атаки повторного воспроизведения, поэтому устройствам, которым нужна безопасность, следует иметь доверенный источник времени.
После первичной установки времени код будет корректировать системное время до 1 секунды в день, чтобы оставаться синхронизированным с другими узлами, так что рассинхронизация не должна быть серьёзной проблемой.
В LazyMesh всё является каналом, прямых сообщений нет. Если вам нужны прямые сообщения, просто создайте выделенный приватный канал.
Каналы определяются паролем: знание пароля даёт доступ на чтение и запись.
Всего существует 256 номеров маршрутов. Каждый пакет имеет один из них, и повторители повторяют пакет только в том случае, если у них включён соответствующий номер маршрута. По умолчанию всё отправляется с номером маршрута 0, который включён по умолчанию.
Это влияет только на повторители: узлы будут слушать любой номер mesh-маршрута, если пакет предназначен непосредственно им.
Полезная нагрузка пакетов — это массивы MessagePack. В них чередуются целочисленные ID данных и элементы данных. ID 192–256 зарезервированы для прикладных сообщений.
ID 32 используется для текстовых сообщений, которые могут иметь префикс с именем пользователя и двоеточием.
ID 2 используется для уникального идентификатора, который должен быть целым числом. Многим приложениям он может вообще не понадобиться.
Добавьте байт длины метаданных и N байт метаданных.
Чтобы создать IV, возьмите 12 случайных байтов для IV. Затем зашифруйте всё целиком. Добавьте IV в начало и 4 байта тега аутентификации в конец.
Затем возьмите первые 8 байтов хеша маршрутного ID и преобразуйте их в hex.
Тема MQTT будет lazymesh_route_HEX
Обратите внимание, что мы используем тему верхнего уровня. Это сделано для того, чтобы вы не могли с помощью подстановочных знаков подписаться сразу на все каналы lazymesh на публичных брокерах, что позволило бы довольно легко устроить DoS для всех.
Просто необработанные пакеты, отправляемые широковещательно на 224.0.0.251:2221
Ничто из этого не является окончательным!!!
All numbers are little-endian.
1 byte header:
2 bit packet type(Either 1 or 2, depending on if we want ACK)
3 bits TTL hops remaining
1 bit allow slow transport(LoRa etc)
1 bit allow global routing
1 bit was already global routed
1 byte header 2:
1 bit first send attempt:
Whenever we create a or recieve a packet, set this bit. After trying to send it,
clear it. This way, as long as we assume packet loss is low-ish, we can count the repeaters in the area
without extra overhead.
1 bit repeater bit:
Marks that this packet should be included when counting repeaters.
Set it if you would repeat the packet or one like it, even if you originated it.
1 bit interest bit:
If this is set, the node who sent it is directly interested in the channel,
not purely just a repeater. If first send is also set, it is treated as an
implicit ack
1 bit location enabled
If this bit is set, repeaters may add location metadata to the packets forwarded to the internet. This metadata must be encrypted with the routing ID as the key,
meaning nearby people could track you for 1 hour after you get out of range.
Not implemented anywhere at the moment.
5 reserved 0 bits
1 byte mesh route number
1 byte path loss accumulated:
5 bits total
3 bits last hop
Every hop is a point of path loss,
plus whatever extra cost heuristic the transport applies.
1 extra point of loss should be roughly the same "badness" as
10dbm extra loss on wifi.
16 bytes routing ID:
Changes every hour, derived from the channel PSK by a hash.
The PSK is just the 16 byte SHA256 of the password.
The routing ID changes hourly, and is the SHA256 of:
The letter 'cr'
The count of hours since 1970 as a 32 bit unsigned int
the PSK
8 Bytes random entropy:
Used as part of the IV for the cipher
4 bytes timestamp:
Also part of the IV, also prevents replay attacks
N bytes ciphertext:
AES-GCM encrypted.
The encryption key changes hourly, and is the SHA256 of:
The letter 'c'
The count of hours since 1970 as a 32 bit unsigned int
the PSK
6 bytes auth tag:
The last 6 bytes are the GCM tag
Некоторые реализации могут вообще игнорировать это.
ACK являются чисто посегментными (per hop), если только вышестоящий протокольный уровень не захочет сквозных ACK.
ACK-пакеты не повторяются, не маршрутизируются и никак не аутентифицируются. Каждый шаг mesh-повторения имеет собственное подтверждение; с точки зрения исходного отправителя это работает по принципу «отправил и забыл».
Как и Meshtastic и большинство других, протокол является «полунадёжным»: как и во всех сетях, существуют пограничные случаи, приводящие к сбоям.
Настоящая надёжность должна обеспечиваться на более высоком уровне.
Для каждого пакета каждый заинтересованный слушатель конкретного канала отправляет канальное подтверждение ровно один раз, если только он не обнаружит, что другие узлы уже отправили более 8 ACK.
Этот пакет должен отправляться по всем транспортам, а не только по тому, через который пришёл пакет, иначе другие узлы могут получить неверное представление о количестве слушателей.
Когда пакет имеет одновременно бит первой попытки отправки и флаг заинтересованности, это действует как неявный ACK. Если мы отправляем копию пакета, нам не нужно также отправлять ACK, чтобы добавить себя в счётчик заинтересованных слушателей.
Повторители подтверждают, просто отправляя копию пакета; когда она помечена флагом первой копии, мы учитываем её. По этой причине такие транспорты, как WiFi, должны повторять передачу, даже если это не имеет особого смысла.
Это НЕ относится к транспортам глобальной маршрутизации: глобальная маршрутизация обрабатывается отдельно и не подлежит никаким ACK, повторам и т. п. Мы предполагаем, что обработкой занимается MQTT-сервер.
Узлы могут повторно отправить сообщение несколько раз, если они получат меньше ответов, чем ожидалось.
Узлы никогда не должны ожидать более 6 повторителей и 6 слушателей канала, даже если они получают больше ответов, поскольку простая схема ACK становится неточной за этим пределом при умеренной потере пакетов.
1 byte header:
Always 0, packet type is control, and these are not routable or repeatable
1 byte header 2:
Same as on the data packets. Not really used at the moment
1 byte subtype:
CONTROL_TYPE_CHANNEL_ACKNOWLEDGE
You can acknowlege as a channel listener
so the sender knows how many there are.
4 byte message ID:
just the first 4 bytes of the random IV from the packet we are ACKing
Каждый час, за несколько минут до наступления нового часа, узлы должны отправлять анонс каналов, в которых они заинтересованы.
Это должно отправляться со сменными кодами для следующего часа, а не текущего, чтобы соединения можно было установить заранее и всё работало даже при рассинхронизации времени.
Узлы связываются через bluetooth, используя расширенный рекламный пакет с UUID сервиса d1a77e11-420f-9f11-1a00-10a6beef0001, а полезная нагрузка — это просто формат пакета, описанный выше.
В BLE-пакетах никогда не должен быть установлен флаг «первой копии», и мы не учитываем повторители через BLE. Потери пакетов слишком высоки для любой простой и масштабируемой схемы, которую я могу придумать.
Поэтому мы просто рассматриваем его как канал с неотъемлемыми потерями, которые мы частично компенсируем повторением пакетов до 4 раз или до тех пор, пока нам не нужно остановиться, чтобы отправить какой-то другой пакет.
Пока узел не пытается отправлять больше одного-двух пакетов в секунду, простая схема повторов обеспечит некоторую надёжность, а если мы превысим этот предел, то она отступит, чтобы не забивать эфир.
Мы также прекращаем отправку, если видим, что слишком много других узлов в той же зоне отправляют слишком много копий пакета.