Проект Невидимый Интернет (далее просто I2P) представляет разработчикам платформу для разработки приложений с усиленными требованиями по приватности пользователей. Это виртуальная сеть поверх обычного Интернета, в которой узлы могут обмениваться данными и при этом не раскрывать свой настоящий IP адрес. Вместо IP адресов внутри Невидимого Интернета соединения происходят между виртуальными адресами, которые называются I2P Destination. Можно иметь сколько угодно таких адресов и менять их хоть для каждого соединения, они не предоставляют другой стороне никакой информации о настоящем IP адресе клиента.
В этой статье описаны базовые вещи, которые нужно знать для написания I2P приложений. Примеры кода приведены на Python с использованием встроенного асинхронного фреймворка asyncio.
Включение SAM API и установка i2plib
I2P предлагает несколько API для взаимодействия с клиентскими приложениями. Для приложений на Java используется I2CP, для обычных client-server приложений можно использовать I2PTunnel, HTTP и Socks прокси. Мы же будем делать приложение на Python, поэтому выбираем SAM. По-умолчанию, в оригинальном Java клиенте SAM API выключен, так что нужно его включить. Зайдите в веб-консоль I2P роутера, страница "I2P internals" -> "Clients". Поставьте галочку "Run at Startup" и нажмите "Start", затем "Save Client Configuration".
В C++ клиенте i2pd SAM уже включен по-умолчанию.
Для простоты использования SAM API мною была написана Python библиотека i2plib. Установить ее можно через pip или скачать исходный код с GitHub.
pip install i2plib
Так как эта библиотека работает со встроенным асинхронным фреймворком asyncio, имейте в виду, что примеры кода тоже взяты из асинхронных функций (coroutines), которые работают в event loop'е. Дополнительные примеры использования есть в репозитории.
Destination и создание сессии
По своей сути, I2P Destination это связка ключей шифрования и подписи данных. Публичные ключи из этой связки публикуются в сети I2P и используются вместо IP адреса для создания соединений.
Сгенерируем i2plib.Destination, который мы будем использовать в дальнейшем:
dest = await i2plib.new_destination()
print(dest.base32 + ".b32.i2p") # вывод base32 адреса
base32 адрес это хэш, по которому другие пиры могут найти ваш Destination в сети. Если вы планируете использовать этот Destination в своей программе на постоянной основе, сохраните содержимое dest.private_key.data в локальный файл.
Теперь можно создать SAM сессию, что буквально значит сделать этот Destination онлайн в сети:
session_nickname = "test-i2p" # каждая сессия должна иметь уникальный nickname
_, session_writer = await i2plib.create_session(session_nickname, destination=dest)
Тут важно отметить, что Destination будет онлайн до тех пор, пока открыт сокет session_writer. Если захотите "выключить" данный Destination из сети, вызывайте session_writer.close().
Делаем исходящие соединения
Теперь, когда Destination вышел в онлайн, мы можем использовать его для связи с другими узлами. Для примера, подключаемся к узлу "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p", отправляем HTTP GET запрос и читаем ответ (там находится веб-сервер "i2p-projekt.i2p"):
remote_host = "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p"
reader, writer = await i2plib.stream_connect(session_nickname, remote_host)
writer.write("GET /en/ HTTP/1.0\nHost: {}\r\n\r\n".format(remote_host).encode())
buflen, resp = 4096, b""
while 1:
data = await reader.read(buflen)
if len(data) > 0:
resp += data
else:
break
writer.close()
print(resp.decode())
Принимаем входящие соединения
При подключении к другому хосту, как видите, все просто, но с принятием входящих есть один ньюанс. Когда к вам подключается новый клиент, SAM отправляет в сокет ASCII строку с Destination этого клиента. Так как Destination и данные могут прийти одим куском, нужно это учитывать.
Вот так выглядит простой PING-PONG сервер, который принимает входящее соединение, сохраняет Destination клиента в rеременную remote_destination, и отправляет обратно PONG:
async def handle_client(incoming, reader, writer):
"""Обработка клиентского соединения"""
dest, data = incoming.split(b"\n", 1)
remote_destination = i2plib.Destination(dest.decode())
if not data:
data = await reader.read(BUFFER_SIZE)
if data == b"PING":
writer.write(b"PONG")
writer.close()
# Вечный цикл, который принимает клиентские соединения и запускает обработчик
while True:
reader, writer = await i2plib.stream_accept(session_nickname)
incoming = await reader.read(BUFFER_SIZE)
asyncio.ensure_future(handle_client(incoming, reader, writer))
Больше информации
Тут описано использование Streaming протокола, который выполняет функции TCP/IP в сети I2P. SAM API так же предоставляет возможность посылать и принимать анонимные датаграмы, на подобие UDP протокола. Этот функционал в i2plib пока отсутствует и будет добавлен позже.
Это только самая базовая информация, но ее уже вполне достаточно, чтобы начать свой проект в I2P. Невидимый Интернет подходит для написания различных приложений, в которых в первую очередь важно сохранять приватность пользователей. Никаких ограничений на разработчиков сеть не накладывает, это могут быть как client-server, так и P2P приложения.