Любительская почта — чебурнет судного дня

e196291102eb8bf470cf130d91c64e34.png

Всем привет!

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

Что такое sneakernet-сети и зачем они нужны в 2022 году?

Sneakernet — это термин, обозначающий компьютерную сеть, основанную на физическом перемещении данных, например, при помощи перемещения дискеты из рук в руки, при отправки флешек корреспондентам на голубях, и так далее.

Несмотря на повсеместное распространение интернета, такие сети могут быть интересны в качестве резервного канала связи. С одной стороны, живя в большом городе, можно обложиться домашним интернетом, несколькими сим-картами с оплаченным LTE, ещё несколькими VPN для надёжного доступами к зарубежным сервисам. С другой стороны, известные интернет-сервисы принадлежат не конечным пользователям, а большим компаниям, которые теоретически могут ограничивать доступ пользователям по тому или иному признаку (адрес IP не в правильном регионе, паспорт не той юрисдикции, банковская карта, выпущенная в твоей стране, больше не принимается к оплате, итд). Плюс, бывает так, что сами правительства практически полностью режут доступ к интернету на какое-то время — те, кто жил в Беларуси в августе 2020 года или в Казахстане в январе 2022 года, не дадут мне соврать. Поэтому в периоды таких отключений интернета было бы хорошо иметь запасную возможность отправить сообщение близким, друзьям, пускай и с сильно большей задержкой. Любительская почта была создана, чтобы реализовывать именно этот сценарий работы. В нескольких словах, это микс идеи почты и старого флоппинета, распространённого в 90-е годы прошлого века. На такую идею меня подтолкнула другая статья на хабре, в которой описывается историческое развитие почты.

Как устроена любительская почта?

ca6634a8db7c43ee57bd1dd56acf122c.png

Полное название сети — «Служба любительской цифровой почты» (Amateur Digital Post Service), в дальнейшем буду писать просто «любительская почта». Она сильно похожа на любые другие sneakernet-сети, наглядная картинка приведена выше.

Как хранятся сообщения в любительской почте?

64cf2f3c1168e942482a591853b9f644.png

Сообщения хранятся в репозитории. Репозиторий — это папка, которая содержит две другие папки:

  1. adps_messages — содержит сообщения в JSON-формате

  2. adps_attachments — содержит файлы, прикреплённые к вложениям

Примерная структура репозитория с сообщением приведена на картинке выше. Любой файл в репозитории содержит первые 10 символов от хешсуммы sha512 этого файла. Такое правило в названии файлов позволяет относительно быстро находить вложения, а также избегать дублирования одинаковых сообщений во время их копирования между репозиториями. То есть, такое цифровое сообщение можно отправить разными маршрутами, а адресат получит ровно одно сообщение, потому что хешсумма письма не изменяется во время пересылки. JSON-файл сообщения ограничен размером в 4096 байт, поскольку это позволяет быстро десериализовать сообщение или понять, что файл имеет неверную структуру. Если надо отправить что-то потяжелее, то можно использовать механизм вложений. Файлы вложений не имеют ограничение по размеру, но оператор следующего узла может отказаться принять такое сообщение к себе в репозиторий.

На иллюстрации выше приведена схема сообщения, давайте разберём некоторые его поля:

  1. Имя (name) — это первый по важности идентификатор получателя после координат. Значение этого поля должно быть коротким и позволять идентифицировать получателя. Например, это может быть адресом email, номером телефона или даже номером аськи. Важно учитывать, что это поле регистрозависимое в текущих реализациях поиска. Данное поле необходимо указывать в любом сообщении. Оно позволяет выгрести почту для конкретного получателя среди тысяч других для одной и той же координаты, что актуально для плотных городов.

  2. Сообщение (inline_message) может содержать короткое сообщения, типа СМС. Из-за лимита в 4 КБ на одно сообщение оно не будет содержать много информации. Повторюсь, если надо отправить что-то побольше, используйте вложения.

  3. Примечание (additional_notes) обычно выставлено в null. Но в некоторых случаях оно может содержать важную информацию для маршрутизации сообщения. Например, один узел может изменить письмо, добавив полезную информацию по доставке для следующих узлов. Вообще, лучше избегать редактирования сообщения после его создания, потому что поменяется значение хешсуммы и получатель может получить много сообщений с почти одинаковым содержимым, за исключением примечания. Но, как мне кажется, лучше доставить несколько сообщений, чем иметь больший риск не доставить ни одного.

  4. Дата создания (date_created) реализует механизм TTL (Time To Live). Это строковое поле должно быть в формате YYYY-MM-DDThh:mm:ss. В обеих существующих реализациях любительской почты по умолчанию включены фильтры на это поле со значениями (30 дней назад; через 3 дня). Нижняя граница реализует TTL, а верхняя не позволяет его обманывать, но допускает отправку писем в разных часовых поясах и с адекватными ошибками в локальном времени. То есть, обычно у письма есть 30 дней, чтобы доставиться получателю, или оно, скорее всего, потеряется.

  5. Координаты получателя (recipient_coords) — это главное поле, используемое для фильтрации сообщений. Оно сделано в виде списка географических координат. Причина использования списка, а не одной координаты была в том, что, во-первых, со списком можно отправлять одно письмо в несколько локаций, и во-вторых, через список можно вручную управлять маршрутизацией письма. То есть, если известно, что транспортный поток между вашим местом и местом получателя низкий, то вы можете разделить маршрут на несколько других с более плотным движением. Эта идея будет раскрыта в следующей части.

Как видно, все поля содержат информацию о получателе, не об отправителе.

Используя ПО для работы с любительской почтой, вы можете фильтровать письма по всем вышеприведённым полям, но самое главное поле — это список координат (recipient_coords), потому что оно даёт возможность маршрутизировать письма внутри сети.

Как поле списка координат используется для маршрутизации сообщения?

Поле recipient_coords является основным полем для маршрутизации внутри любительской почты. Давайте рассмотрим два фильтра, использующие это поле.

Простой фильтр координат

6046bf31297fdb99308ca297088e7cd7.png

Это самый важный фильтр в любительской почте. Например, вы хотите отправить сообщение из Лихославля (Тверская область) во Владимир. Вероятность того, что кто-то на днях поедет из Лихославля во Владимир, очень мала. Гораздо лучше будет разбить маршрут «Лихославль — Владимир» на несколько сегментов, например, «Лихославль — Тверь — Москва — Владимир». Простой фильтр координат выгребает все сообщения с заданными центральной точкой и радиусом. Переводя на человеческий язык, запросы в фильтр будут выглядеть примерно так:

  • Дайте мне все письма, получатели которых находятся в радиусе 20 км от Твери.

  • Дайте мне все письма, получатели которых находятся в радиусе 50 км от Москвы.

  • Дайте мне все письма, получатели которых находятся в радиусе 15 км от Владимира.

Если вы зададите только финальную точку в списке координат, то сработает только последний запрос для вашего письма. Но если вы зададите промежуточные точки, то все запросы выше будут работать и больше людей (узлов) будут вовлечены в доставку вашего сообщения. Иллюстрация этой идеи приведена выше.

Умный фильтр координат (или фильтр убывающего расстояния)

Фильтр предназначен для передачи писем из одного репозитория в другой, который находится в крупной агломерации (например, Москва и МО). Главная идея заключается в том, что в больших городах больше вероятность встретить людей, которые иногда катаются в далёкие населённые пункты. Модель этого фильтра предполагает, что чем более населённый город, то тем на большее расстояние можно отправить письмо. Умный фильтр принимает два параметра:

  1. Центральная точка (по аналогии с простым фильтром)

  2. Базовое расстояние (base_distance)

Механика работы следующая:

  1. Если дистанция между получателем и центральной точкой равна одной базовой дистанции, то вероятность фильтрации письма 50%.

  2. Если дистанция между получателем и центральной точкой равна двум базовым дистанциям, то вероятность фильтрации письма 25%.

  3. Если дистанция между получателем и центральной точкой равна трём базовым дистанциям, то вероятность фильтрации письма 12.5%.

  4. И так далее, пока вероятность не опустится ниже порогового значения в 5%.

Математически вероятность (probability) считается по такой формуле:

probability = 2^\frac{-distance}{base\_distance}

Базовая дистанция (base_distance — метры) получается из значения населения (population — человек) в городе по следующей эмпирической формуле:

base\_distance = population / 10

Значение населения можно вытащить из открытого датасета от simplemaps.com.

Давайте посмотрим на примеры двух самых больших городов России: Москву и Санкт-Петербург. Благодаря проекту ns6t.net я смог построить азимутальные эквидистантные проекции (любые два равных отрезка, начинающиеся из центра, проецируются в равные расстояния) для наглядной иллюстрации работы этого фильтра.

efccb59f4e0cc352d6b50051100288d6.png

Если применить фильтр к Москве, то вся территория Франции, Италии или Англии будет лежать в зоне вероятностей между 50% и 25%. Это связано с тем, что Москва является большим городом, и значение базового расстояния довольно большое (1712 км).

3ca3ea3bb5f01281be59cc2278570bf4.png

Несмотря на относительно близкое расстояние (600 км) к Москве, «круги» Санкт-Петербурга имеют гораздо меньшее расстояние между окружностями. Из-за того, что базовое расстояние меньше Московского в 3 раза (как и население), то некоторые регионы Франции, Италии и Англии находятся за пределами 6.25-процентной зоны. В этом и заключается идея, что вероятность доставки письма из Москвы в Англию будет гораздо выше, чем из Питера в Англию, потому что в Москве живёт больше людей (на практике это может не работать, но модель, в силу простоты, пока такая).

Реализации

8ca7ac352346535d2f89bad3c82ff2ea.png

Я написал две реализации. Сначала я написал прототип на питоне с CLI в качестве пользовательского интерфейса. После этого я написал генератор фейковых сообщений, чтобы посмотреть, как работает прототип под нагрузкой. Скрипт в git-репозитории питоновской реализации генерирует 50000 сообщений вместе со 100000 вложений на 5 ГБ. 80% писем распределены по России, остальные 20% — за её пределами. Количество писем пропорционально количеству жителей, кроме того, для каждой координаты внесена небольшая ошибка, чтобы избежать скопления писем с одинаковой координатой в одной и той же локации. Тестирование под нагрузкой вскрыло некоторые баги, который я поправил. После этого мне захотелось портировать питоновскую реализацию на какую-нибудь массовую платформу. В качестве такой я выбрал C#, а конкретнее, .NET 4.0. Причина заключалась в том, что эта платформа запускается на огромном количестве версий Windows (от Windows XP до «десятки»). Также у этой платформы есть гибкий и мощный фреймворк WPF для написания графического GUI вместо консольного CLI. В текущей версии GUI имеется возможность подгружать внешние переводы, помимо двух внутренних (русский, английский).

Бенчмарк

Вышеупомянутый скрипт генерирует 50000 фейковых сообщений и 100000 вложений на 5 ГБ, которые складываются в отдельный репозиторий, на котором, в свою очередь, проводится нагрузочное тестирование.

Параметры фильтров

  1. Простой фильтр координат: 55.7558, 37.6173 (Москва), радиус 35 км.

  2. Включен умный фильтр координат, в качестве центральной точки также используется Москва.

  3. Даты: с 10 января 2022 года по 10 июля 2022 года.

При таких настройках из репозитория с 50000 письмами фильтруются, примерно, 14300 сообщения. Я написал «примерно», потому что включён умный фильтр координат, а результат его работы зависит от генератора случайных чисел.

Команды в Python-реализации

# Filtering
time adps search [REPO] --output-format=COUNT --datetime-from=2022-01-10 --datetime-to=2022-07-10 --latitude=55.7558 --longitude=37.6173 --radius-meters=35000 --damping-distance-latitude=55.7558 --damping-distance-longitude=37.6173

# Copying
time adps search [SOURCE_REPO] --output-format=COUNT --datetime-from=2022-01-10 --datetime-to=2022-07-10 --latitude=55.7558 --longitude=37.6173 --radius-meters=35000 --damping-distance-latitude=55.7558 --damping-distance-longitude=37.6173 --target-repo-folder [TARGET_REPO] --copy

# Delete
time adps search [REPO] --output-format=COUNT --datetime-from=2022-01-10 --datetime-to=2022-07-10 --latitude=55.7558 --longitude=37.6173 --radius-meters=35000 --damping-distance-latitude=55.7558 --damping-distance-longitude=37.6173 --delete

Результаты

Окружение

Фильтрация, сек.

Копирование, сек.

Удаление, сек.

Linux, Intel Core i5 Whiskey Lake, NVMe M2 SSD, Python 3.10

42

86 (44)

85 (43)

MacOS, MacBook Air M1, Python 3.9

24

57 (33)

53 (30)

Windows XP, Intel Core 2 Duo, SATA HDD 5400 rpm, .NET 4.0

127

1722

759

Windows 10, Intel Pentium G4400, SATA SSD, .NET 4.5.1

61

255

52

Результаты для Windows 10 нестабильны из-за того, что первое время операция занимает намного больше времени, чем обычно. Возможно, это из-за того, что во второй и последующие запуски программа подгружает файлы из кеша, а не жёсткого диска. В таблице приведены наилучшие результаты. Также можно увидеть 2 числа в некоторых ячейках, относящихся к питоновской реализации. Два числа были указаны из-за того, что консольный интерфейс не позволяет разделить стадию фильтрации и стадию копирования/удаления, поэтому в скобках указано время за вычетом стадии фильтрации.

Результаты для Windows XP сильно выбиваются в худшую сторону по сравнению с другими окружениями. Но, всё равно, то, что любительскую почту можно запустить даже на таком довольно старом компьютере, меня радует.

Итог

Любительская почта — это полностью офлайн сеть, которая способна соединить участников по всему миру. Эта сеть работает даже на старом оборудовании. Дизайн этой сети предельно простой, нет необходимости даже в базе данных, нужна только возможность работы с файловой системой. Это значит, что любой программист может написать свою реализацию любительской почты. Сегодня, конечно, можно посмеяться над тем, что кто-то пишет ПО для создания сетей, которые были в ходу лет 30 назад. И хорошо, если лет через 10 мы тоже будем над этим смеяться, но пускай, всё-таки, возможность резервной связи будет, хоть она и гораздо менее удобная по сравнению с мессенджерами и электронной почтой.

Ссылки

© Habrahabr.ru