Compare commits

..

No commits in common. "openssl" and "2.55.0" have entirely different histories.

117 changed files with 1612 additions and 3372 deletions

View file

@ -133,8 +133,6 @@ jobs:
git clone https://github.com/msys2/MINGW-packages git clone https://github.com/msys2/MINGW-packages
cd MINGW-packages cd MINGW-packages
git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost
cd mingw-w64-boost
sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD
# headers # headers
- name: Get headers package version - name: Get headers package version

View file

@ -108,7 +108,7 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: purplei2p/i2pd:latest inputs: purplei2p/i2pd:latest
tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
push: true push: true
- name: Create and push latest manifest image to GHCR - name: Create and push latest manifest image to GHCR
@ -116,7 +116,7 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: ghcr.io/purplei2p/i2pd:latest inputs: ghcr.io/purplei2p/i2pd:latest
tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
push: true push: true
- name: Store release version to env - name: Store release version to env
@ -128,7 +128,7 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
tags: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7
push: true push: true
- name: Create and push release manifest to GHCR - name: Create and push release manifest to GHCR
@ -136,5 +136,5 @@ jobs:
uses: Noelware/docker-manifest-action@master uses: Noelware/docker-manifest-action@master
with: with:
inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }}
tags: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7
push: true push: true

View file

@ -1,29 +1,6 @@
# for this file format description, # for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog # see https://github.com/olivierlacan/keep-a-changelog
## [2.56.0] - 2025-02-11
### Added
- Config params for shared local destination
- AddressBook full addresses cache
- Decline transit tunnel to duplicated router
- Recreate tunnels in random order
### Changed
- Exclude disk operations from SSU2 and NTCP2 threads
- Set minimal version for peer test to 0.9.62
- Send ack requested flag after second SSU2 resend attempt
- Shorter ECIESx25519 ack request interval for datagram and I2CP sessions
- Don't change datagram routing path too often if unidirectional data stream
- Reduce LeaseSet and local RouterInfo publishing confirmation intervals
- Don't delete buffer of connected routers or if an update received
- Smaller RouterInfo request timeout if sent directly
- Persist local RouterInfo in separate thread
- Don't recalculate and process ranges for every SSU2 Ack block
- Reseeds list
### Fixed
- Termination deadlock if SAM session is active
- Race condition at tunnel endpoint
- Inbound tunnel build encryption
## [2.55.0] - 2024-12-30 ## [2.55.0] - 2024-12-30
### Added ### Added
- Support boost 1.87 - Support boost 1.87
@ -85,12 +62,12 @@
- Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP - Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP
- Publish LeaseSet with new timestamp update if tunnel was replaced in the same second - Publish LeaseSet with new timestamp update if tunnel was replaced in the same second
- Increase max number of generated tags to 800 per tagset - Increase max number of generated tags to 800 per tagset
- Routing path expiration by time instead num attempts - Routing path expiration by time instead num attempts
- Save timestamp from epoch instead local time to profiles - Save timestamp from epoch instead local time to profiles
- Update introducer's iTag if session to introducer was replaced to new one - Update introducer's iTag if session to introducer was replaced to new one
- RTT, window size and number of NACKs calculation for streaming - RTT, window size and number of NACKs calculation for streaming
- Don't select same peer for tunnel too often - Don't select same peer for tunnel too often
- Use WinApi for data path UTF-8 conversion for Windows - Use WinApi for data path UTF-8 conversion for Windows
### Fixed ### Fixed
- Jump link crash if address book is disabled - Jump link crash if address book is disabled
- Race condition if connect through an introducer - Race condition if connect through an introducer

View file

@ -1,4 +1,4 @@
Copyright (c) 2013-2025, The PurpleI2P Project Copyright (c) 2013-2023, The PurpleI2P Project
All rights reserved. All rights reserved.

View file

@ -69,9 +69,6 @@ else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS)))
else ifneq (, $(findstring haiku, $(SYS))) else ifneq (, $(findstring haiku, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.haiku include Makefile.haiku
else ifneq (, $(findstring solaris, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.solaris
else # not supported else # not supported
$(error Not supported platform) $(error Not supported platform)
endif endif

View file

@ -3,7 +3,7 @@ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misl
DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1 DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1
INCFLAGS = -I/usr/include/ -I/usr/local/include/ INCFLAGS = -I/usr/include/ -I/usr/local/include/
LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib
LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options LDLIBS = -lcrypto -lssl -lz -lpthread -lboost_system -lboost_program_options
## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time
## **without** overwriting the CXXFLAGS which we need in order to build. ## **without** overwriting the CXXFLAGS which we need in order to build.

View file

@ -1,12 +1,8 @@
ifeq ($(shell $(CXX) -dumpmachine | cut -c 1-4), i586)
CXX = g++-x86
else
CXX = g++ CXX = g++
endif CXXFLAGS := -Wall -std=c++17
CXXFLAGS := -Wall -std=c++20
INCFLAGS = -I/system/develop/headers INCFLAGS = -I/system/develop/headers
DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE
LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_program_options -lpthread LDLIBS = -lbe -lbsd -lnetwork -lz -lcrypto -lssl -lboost_system -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes) ifeq ($(USE_UPNP),yes)
DEFINES += -DUSE_UPNP DEFINES += -DUSE_UPNP

View file

@ -18,7 +18,7 @@ endif
LDLIBS += -lpthread -ldl LDLIBS += -lpthread -ldl
else else
LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes) ifeq ($(USE_UPNP),yes)
LDFLAGS += -L${UPNPROOT}/lib LDFLAGS += -L${UPNPROOT}/lib
LDLIBS += -lminiupnpc LDLIBS += -lminiupnpc

View file

@ -40,7 +40,7 @@ ifeq ($(USE_UPNP),yes)
endif endif
LDLIBS += -lpthread -ldl LDLIBS += -lpthread -ldl
else else
LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic LDLIBS += -lcrypto -lssl -lz -lboost_program_options -lpthread -latomic
ifeq ($(USE_UPNP),yes) ifeq ($(USE_UPNP),yes)
LDLIBS += -lminiupnpc LDLIBS += -lminiupnpc
endif endif

View file

@ -7,9 +7,9 @@ LDFLAGS += -Wl,-dead_strip
LDFLAGS += -Wl,-dead_strip_dylibs LDFLAGS += -Wl,-dead_strip_dylibs
ifeq ($(USE_STATIC),yes) ifeq ($(USE_STATIC),yes)
LDLIBS = -lz /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
else else
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_filesystem -lboost_program_options -lpthread
endif endif
ifeq ($(USE_UPNP),yes) ifeq ($(USE_UPNP),yes)

View file

@ -1,9 +0,0 @@
CXX = g++
INCFLAGS = -I/usr/openssl/3/include
CXXFLAGS := -Wall -std=c++20
LDLIBS = -L/usr/openssl/3/lib/64 -lssl -lcrypto -lboost_program_options -lz -lpthread -lsocket
ifeq ($(USE_UPNP),yes)
DEFINES += -DUSE_UPNP
LDLIBS += -lminiupnpc
endif

View file

@ -59,7 +59,7 @@ get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
# function returns an empty string via _git_dir_var. # function returns an empty string via _git_dir_var.
# #
# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and # Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
# neither foo nor bar contain a file/directory .git. This will return # neither foo nor bar contain a file/directory .git. This wil return
# C:/bla/.git # C:/bla/.git
# #
function(_git_find_closest_git_dir _start_dir _git_dir_var) function(_git_find_closest_git_dir _start_dir _git_dir_var)

View file

@ -243,7 +243,7 @@ verify = true
## Default: reg.i2p at "mainline" I2P Network ## Default: reg.i2p at "mainline" I2P Network
# defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt # defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt
## Optional subscriptions URLs, separated by comma ## Optional subscriptions URLs, separated by comma
# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt # subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt
[limits] [limits]
## Maximum active transit sessions (default: 5000) ## Maximum active transit sessions (default: 5000)

View file

@ -1,8 +1,7 @@
[Unit] [Unit]
Description=I2P Router written in C++ Description=I2P Router written in C++
Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/ Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/
Wants=network.target After=network.target
After=network.target network-online.target
[Service] [Service]
User=i2pd User=i2pd

View file

@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7) %define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git Name: i2pd-git
Version: 2.56.0 Version: 2.55.0
Release: git%{git_hash}%{?dist} Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++ Summary: I2P router written in C++
Conflicts: i2pd Conflicts: i2pd
@ -148,9 +148,6 @@ getent passwd i2pd >/dev/null || \
%changelog %changelog
* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
- update to 2.56.0
* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0 * Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
- update to 2.55.0 - update to 2.55.0

View file

@ -1,5 +1,5 @@
Name: i2pd Name: i2pd
Version: 2.56.0 Version: 2.55.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: I2P router written in C++ Summary: I2P router written in C++
Conflicts: i2pd-git Conflicts: i2pd-git
@ -146,9 +146,6 @@ getent passwd i2pd >/dev/null || \
%changelog %changelog
* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
- update to 2.56.0
* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0 * Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
- update to 2.55.0 - update to 2.55.0

View file

@ -5,7 +5,6 @@ port = 6668
destination = irc.ilita.i2p destination = irc.ilita.i2p
destinationport = 6667 destinationport = 6667
keys = irc-keys.dat keys = irc-keys.dat
i2p.streaming.profile=2
#[IRC-IRC2P] #[IRC-IRC2P]
#type = client #type = client

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -132,8 +132,7 @@ namespace http {
static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
{ {
std::string state; std::string state, stateText;
std::string_view stateText;
switch (eState) switch (eState)
{ {
case i2p::tunnel::eTunnelStateBuildReplyReceived : case i2p::tunnel::eTunnelStateBuildReplyReceived :
@ -147,7 +146,7 @@ namespace http {
} }
if (stateText.empty ()) stateText = tr(state); if (stateText.empty ()) stateText = tr(state);
s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << "</span>, "; // TODO: s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>, ";
ShowTraffic(s, bytes); ShowTraffic(s, bytes);
s << "\r\n"; s << "\r\n";
} }
@ -214,7 +213,7 @@ namespace http {
"</html>\r\n"; "</html>\r\n";
} }
static void ShowError(std::stringstream& s, std::string_view string) static void ShowError(std::stringstream& s, const std::string& string)
{ {
s << "<b>" << tr("ERROR") << ":</b>&nbsp;" << string << "<br>\r\n"; s << "<b>" << tr("ERROR") << ":</b>&nbsp;" << string << "<br>\r\n";
} }
@ -1263,7 +1262,7 @@ namespace http {
ShowLeasesSets(s); ShowLeasesSets(s);
else { else {
res.code = 400; res.code = 400;
ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO ShowError(s, tr("Unknown page") + ": " + page);
return; return;
} }
} }
@ -1419,11 +1418,13 @@ namespace http {
{ {
auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); auto signatureLen = dest->GetIdentity ()->GetSignatureLen ();
uint8_t * signature = new uint8_t[signatureLen]; uint8_t * signature = new uint8_t[signatureLen];
char * sig = new char[signatureLen*2];
std::stringstream out; std::stringstream out;
out << name << "=" << dest->GetIdentity ()->ToBase64 (); out << name << "=" << dest->GetIdentity ()->ToBase64 ();
dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature);
auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen); auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2);
sig[len] = 0;
out << "#!sig=" << sig; out << "#!sig=" << sig;
s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n" s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n"
"<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n" "<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n"
@ -1432,6 +1433,7 @@ namespace http {
"<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n" "<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n"
"</form>\r\n<br>\r\n"; "</form>\r\n<br>\r\n";
delete[] signature; delete[] signature;
delete[] sig;
} }
else else
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n"; s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n";
@ -1460,7 +1462,7 @@ namespace http {
else else
{ {
res.code = 400; res.code = 400;
ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO ShowError(s, tr("Unknown command") + ": " + cmd);
return; return;
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -15,6 +15,7 @@
// Use global placeholders from boost introduced when local_time.hpp is loaded // Use global placeholders from boost introduced when local_time.hpp is loaded
#define BOOST_BIND_GLOBAL_PLACEHOLDERS #define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/lexical_cast.hpp>
#include "FS.h" #include "FS.h"
#include "Log.h" #include "Log.h"
@ -29,24 +30,11 @@ namespace i2p
namespace client namespace client
{ {
I2PControlService::I2PControlService (const std::string& address, int port): I2PControlService::I2PControlService (const std::string& address, int port):
m_IsRunning (false), m_IsRunning (false), m_Thread (nullptr),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)),
m_SSLContext (boost::asio::ssl::context::sslv23), m_SSLContext (boost::asio::ssl::context::sslv23),
m_ShutdownTimer (m_Service) m_ShutdownTimer (m_Service)
{ {
if (port)
m_Acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(m_Service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port));
else
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
{
std::remove (address.c_str ()); // just in case
m_LocalAcceptor = std::make_unique<boost::asio::local::stream_protocol::acceptor>(m_Service,
boost::asio::local::stream_protocol::endpoint(address));
}
#else
LogPrint(eLogError, "I2PControl: Local sockets are not supported");
#endif
i2p::config::GetOption("i2pcontrol.password", m_Password); i2p::config::GetOption("i2pcontrol.password", m_Password);
// certificate / keys // certificate / keys
@ -110,7 +98,7 @@ namespace client
{ {
Accept (); Accept ();
m_IsRunning = true; m_IsRunning = true;
m_Thread = std::make_unique<std::thread>(std::bind (&I2PControlService::Run, this)); m_Thread = new std::thread (std::bind (&I2PControlService::Run, this));
} }
} }
@ -119,19 +107,12 @@ namespace client
if (m_IsRunning) if (m_IsRunning)
{ {
m_IsRunning = false; m_IsRunning = false;
if (m_Acceptor) m_Acceptor->cancel (); m_Acceptor.cancel ();
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
if (m_LocalAcceptor)
{
auto path = m_LocalAcceptor->local_endpoint().path();
m_LocalAcceptor->cancel ();
std::remove (path.c_str ());
}
#endif
m_Service.stop (); m_Service.stop ();
if (m_Thread) if (m_Thread)
{ {
m_Thread->join (); m_Thread->join ();
delete m_Thread;
m_Thread = nullptr; m_Thread = nullptr;
} }
} }
@ -153,60 +134,40 @@ namespace client
void I2PControlService::Accept () void I2PControlService::Accept ()
{ {
if (m_Acceptor) auto newSocket = std::make_shared<ssl_socket> (m_Service, m_SSLContext);
{ m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this,
auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > (m_Service, m_SSLContext); std::placeholders::_1, newSocket));
m_Acceptor->async_accept (newSocket->lowest_layer(),
[this, newSocket](const boost::system::error_code& ecode)
{
HandleAccepted (ecode, newSocket);
});
}
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
else if (m_LocalAcceptor)
{
auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::local::stream_protocol::socket> > (m_Service, m_SSLContext);
m_LocalAcceptor->async_accept (newSocket->lowest_layer(),
[this, newSocket](const boost::system::error_code& ecode)
{
HandleAccepted (ecode, newSocket);
});
}
#endif
} }
template<typename ssl_socket> void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
void I2PControlService::HandleAccepted (const boost::system::error_code& ecode,
std::shared_ptr<ssl_socket> newSocket)
{ {
if (ecode != boost::asio::error::operation_aborted) if (ecode != boost::asio::error::operation_aborted)
Accept (); Accept ();
if (ecode) if (ecode) {
{
LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ()); LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ());
return; return;
} }
LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ()); LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ());
Handshake (newSocket); Handshake (socket);
} }
template<typename ssl_socket>
void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket) void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket)
{ {
socket->async_handshake(boost::asio::ssl::stream_base::server, socket->async_handshake(boost::asio::ssl::stream_base::server,
[this, socket](const boost::system::error_code& ecode) std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket));
{ }
if (ecode)
{ void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket)
LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); {
return; if (ecode) {
} LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ());
ReadRequest (socket); return;
}); }
//std::this_thread::sleep_for (std::chrono::milliseconds(5));
ReadRequest (socket);
} }
template<typename ssl_socket>
void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket) void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket)
{ {
auto request = std::make_shared<I2PControlBuffer>(); auto request = std::make_shared<I2PControlBuffer>();
@ -216,13 +177,10 @@ namespace client
#else #else
boost::asio::buffer (request->data (), request->size ()), boost::asio::buffer (request->data (), request->size ()),
#endif #endif
[this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred) std::bind(&I2PControlService::HandleRequestReceived, this,
{ std::placeholders::_1, std::placeholders::_2, socket, request));
HandleRequestReceived (ecode, bytes_transferred, socket, request);
});
} }
template<typename ssl_socket>
void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode,
size_t bytes_transferred, std::shared_ptr<ssl_socket> socket, size_t bytes_transferred, std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf) std::shared_ptr<I2PControlBuffer> buf)
@ -300,7 +258,6 @@ namespace client
} }
} }
template<typename ssl_socket>
void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket, void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml) std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml)
{ {
@ -310,7 +267,7 @@ namespace client
std::ostringstream header; std::ostringstream header;
header << "HTTP/1.1 200 OK\r\n"; header << "HTTP/1.1 200 OK\r\n";
header << "Connection: close\r\n"; header << "Connection: close\r\n";
header << "Content-Length: " << std::to_string(len) << "\r\n"; header << "Content-Length: " << boost::lexical_cast<std::string>(len) << "\r\n";
header << "Content-Type: application/json\r\n"; header << "Content-Type: application/json\r\n";
header << "Date: "; header << "Date: ";
std::time_t t = std::time (nullptr); std::time_t t = std::time (nullptr);
@ -323,11 +280,16 @@ namespace client
memcpy (buf->data () + offset, response.str ().c_str (), len); memcpy (buf->data () + offset, response.str ().c_str (), len);
boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len),
boost::asio::transfer_all (), boost::asio::transfer_all (),
[socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred) std::bind(&I2PControlService::HandleResponseSent, this,
{ std::placeholders::_1, std::placeholders::_2, socket, buf));
if (ecode) }
LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ());
}); void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf)
{
if (ecode) {
LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ());
}
} }
// handlers // handlers

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -35,6 +35,8 @@ namespace client
class I2PControlService: public I2PControlHandlers class I2PControlService: public I2PControlHandlers
{ {
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
public: public:
I2PControlService (const std::string& address, int port); I2PControlService (const std::string& address, int port);
@ -47,18 +49,16 @@ namespace client
void Run (); void Run ();
void Accept (); void Accept ();
template<typename ssl_socket> void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
void HandleAccepted (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> newSocket);
template<typename ssl_socket>
void Handshake (std::shared_ptr<ssl_socket> socket); void Handshake (std::shared_ptr<ssl_socket> socket);
template<typename ssl_socket> void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr<ssl_socket> socket);
void ReadRequest (std::shared_ptr<ssl_socket> socket); void ReadRequest (std::shared_ptr<ssl_socket> socket);
template<typename ssl_socket>
void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf); std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
template<typename ssl_socket>
void SendResponse (std::shared_ptr<ssl_socket> socket, void SendResponse (std::shared_ptr<ssl_socket> socket,
std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml); std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml);
void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
void CreateCertificate (const char *crt_path, const char *key_path); void CreateCertificate (const char *crt_path, const char *key_path);
@ -86,13 +86,10 @@ namespace client
std::string m_Password; std::string m_Password;
bool m_IsRunning; bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread; std::thread * m_Thread;
boost::asio::io_context m_Service; boost::asio::io_context m_Service;
std::unique_ptr<boost::asio::ip::tcp::acceptor> m_Acceptor; boost::asio::ip::tcp::acceptor m_Acceptor;
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
std::unique_ptr<boost::asio::local::stream_protocol::acceptor> m_LocalAcceptor;
#endif
boost::asio::ssl::context m_SSLContext; boost::asio::ssl::context m_SSLContext;
boost::asio::deadline_timer m_ShutdownTimer; boost::asio::deadline_timer m_ShutdownTimer;
std::set<std::string> m_Tokens; std::set<std::string> m_Tokens;

6
debian/changelog vendored
View file

@ -1,9 +1,3 @@
i2pd (2.56.0-1) unstable; urgency=medium
* updated to version 2.56.0/0.9.65
-- orignal <orignal@i2pmail.org> Tue, 11 Feb 2025 16:00:00 +0000
i2pd (2.55.0-1) unstable; urgency=medium i2pd (2.55.0-1) unstable; urgency=medium
* updated to version 2.55.0 * updated to version 2.55.0

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace afrikaans // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"failed", "Het misluk"}, {"failed", "Het misluk"},
{"unknown", "onbekend"}, {"unknown", "onbekend"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace armenian // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f ԿիԲ"}, {"%.2f KiB", "%.2f ԿիԲ"},
{"%.2f MiB", "%.2f ՄիԲ"}, {"%.2f MiB", "%.2f ՄիԲ"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace chinese // language namespace
return 0; return 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace czech // language namespace
return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -30,7 +30,7 @@ namespace english // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"", ""}, {"", ""},
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace french // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f Kio"}, {"%.2f KiB", "%.2f Kio"},
{"%.2f MiB", "%.2f Mio"}, {"%.2f MiB", "%.2f Mio"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace german // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -30,12 +30,12 @@ namespace i18n
} }
} }
std::string_view translate (std::string_view arg) std::string translate (const std::string& arg)
{ {
return i2p::client::context.GetLanguage ()->GetString (arg); return i2p::client::context.GetLanguage ()->GetString (arg);
} }
std::string translate (const std::string& arg, const std::string& arg2, const int n) std::string translate (const std::string& arg, const std::string& arg2, const int& n)
{ {
return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,7 +10,6 @@
#define __I18N_H__ #define __I18N_H__
#include <string> #include <string>
#include <string_view>
#include <map> #include <map>
#include <utility> #include <utility>
#include <functional> #include <functional>
@ -19,13 +18,12 @@ namespace i2p
{ {
namespace i18n namespace i18n
{ {
typedef std::map<std::string_view, std::string_view> LocaleStrings;
class Locale class Locale
{ {
public: public:
Locale ( Locale (
const std::string& language, const std::string& language,
const LocaleStrings& strings, const std::map<std::string, std::string>& strings,
const std::map<std::string, std::vector<std::string>>& plurals, const std::map<std::string, std::vector<std::string>>& plurals,
std::function<int(int)> formula std::function<int(int)> formula
): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { };
@ -36,7 +34,7 @@ namespace i18n
return m_Language; return m_Language;
} }
std::string_view GetString (std::string_view arg) const std::string GetString (const std::string& arg) const
{ {
const auto it = m_Strings.find(arg); const auto it = m_Strings.find(arg);
if (it == m_Strings.end()) if (it == m_Strings.end())
@ -49,7 +47,7 @@ namespace i18n
} }
} }
std::string GetPlural (const std::string& arg, const std::string& arg2, int n) const std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const
{ {
const auto it = m_Plurals.find(arg2); const auto it = m_Plurals.find(arg2);
if (it == m_Plurals.end()) // not found, fallback to english if (it == m_Plurals.end()) // not found, fallback to english
@ -65,14 +63,14 @@ namespace i18n
private: private:
const std::string m_Language; const std::string m_Language;
const LocaleStrings m_Strings; const std::map<std::string, std::string> m_Strings;
const std::map<std::string, std::vector<std::string>> m_Plurals; const std::map<std::string, std::vector<std::string>> m_Plurals;
std::function<int(int)> m_Formula; std::function<int(int)> m_Formula;
}; };
void SetLanguage(const std::string &lang); void SetLanguage(const std::string &lang);
std::string_view translate (std::string_view arg); std::string translate (const std::string& arg);
std::string translate (const std::string& arg, const std::string& arg2, int n); std::string translate (const std::string& arg, const std::string& arg2, const int& n);
} // i18n } // i18n
} // i2p } // i2p
@ -81,7 +79,7 @@ namespace i18n
* @param arg String with message * @param arg String with message
*/ */
template<typename TValue> template<typename TValue>
std::string_view tr (TValue&& arg) std::string tr (TValue&& arg)
{ {
return i2p::i18n::translate(std::forward<TValue>(arg)); return i2p::i18n::translate(std::forward<TValue>(arg));
} }
@ -94,7 +92,7 @@ std::string_view tr (TValue&& arg)
template<typename TValue, typename... TArgs> template<typename TValue, typename... TArgs>
std::string tr (TValue&& arg, TArgs&&... args) std::string tr (TValue&& arg, TArgs&&... args)
{ {
std::string tr_str = std::string (i2p::i18n::translate(std::forward<TValue>(arg))); // TODO: std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg));
size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...); size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...);
std::string str(size, 0); std::string str(size, 0);
@ -110,7 +108,7 @@ std::string tr (TValue&& arg, TArgs&&... args)
* @param n Integer, used for selection of form * @param n Integer, used for selection of form
*/ */
template<typename TValue, typename TValue2> template<typename TValue, typename TValue2>
std::string ntr (TValue&& arg, TValue2&& arg2, int n) std::string ntr (TValue&& arg, TValue2&& arg2, int& n)
{ {
return i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n)); return i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
} }
@ -123,7 +121,7 @@ std::string ntr (TValue&& arg, TValue2&& arg2, int n)
* @param args Array of arguments for string formatting * @param args Array of arguments for string formatting
*/ */
template<typename TValue, typename TValue2, typename... TArgs> template<typename TValue, typename TValue2, typename... TArgs>
std::string ntr (TValue&& arg, TValue2&& arg2, int n, TArgs&&... args) std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args)
{ {
std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n)); std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace italian // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace polish // language namespace
return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace portuguese // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace russian // language namespace
return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f КиБ"}, {"%.2f KiB", "%.2f КиБ"},
{"%.2f MiB", "%.2f МиБ"}, {"%.2f MiB", "%.2f МиБ"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace spanish // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace swedish // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023-2025, The PurpleI2P Project * Copyright (c) 2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace turkish // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace turkmen // language namespace
return n != 1 ? 1 : 0; return n != 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace ukrainian // language namespace
return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f КіБ"}, {"%.2f KiB", "%.2f КіБ"},
{"%.2f MiB", "%.2f МіБ"}, {"%.2f MiB", "%.2f МіБ"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021-2025, The PurpleI2P Project * Copyright (c) 2021-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -29,7 +29,7 @@ namespace uzbek // language namespace
return n > 1 ? 1 : 0; return n > 1 ? 1 : 0;
} }
static const LocaleStrings strings static std::map<std::string, std::string> strings
{ {
{"%.2f KiB", "%.2f KiB"}, {"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"}, {"%.2f MiB", "%.2f MiB"},

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -15,7 +15,7 @@ namespace i2p
{ {
namespace data namespace data
{ {
static constexpr char T32[32] = static const char T32[32] =
{ {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
@ -27,6 +27,11 @@ namespace data
{ {
return T32; return T32;
} }
bool IsBase32 (char ch)
{
return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7');
}
static void iT64Build(void); static void iT64Build(void);
@ -38,7 +43,7 @@ namespace data
* Direct Substitution Table * Direct Substitution Table
*/ */
static constexpr char T64[64] = static const char T64[64] =
{ {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
@ -54,17 +59,24 @@ namespace data
{ {
return T64; return T64;
} }
bool IsBase64 (char ch)
{
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~';
}
/* /*
* Reverse Substitution Table (built in run time) * Reverse Substitution Table (built in run time)
*/ */
static char iT64[256]; static char iT64[256];
static int isFirstTime = 1; static int isFirstTime = 1;
/* /*
* Padding * Padding
*/ */
static constexpr char P64 = '=';
static char P64 = '=';
/* /*
* *
@ -74,112 +86,134 @@ namespace data
* Converts binary encoded data to BASE64 format. * Converts binary encoded data to BASE64 format.
* *
*/ */
std::string ByteStreamToBase64 (// base64 encoded string
const uint8_t * InBuffer, // Input buffer, binary data size_t ByteStreamToBase64 ( /* Number of bytes in the encoded buffer */
size_t InCount // Number of bytes in the input buffer const uint8_t * InBuffer, /* Input buffer, binary data */
size_t InCount, /* Number of bytes in the input buffer */
char * OutBuffer, /* output buffer */
size_t len /* length of output buffer */
) )
{ {
unsigned char * ps; unsigned char * ps;
unsigned char * pd;
unsigned char acc_1; unsigned char acc_1;
unsigned char acc_2; unsigned char acc_2;
int i; int i;
int n; int n;
int m; int m;
size_t outCount;
ps = (unsigned char *)InBuffer; ps = (unsigned char *)InBuffer;
n = InCount / 3; n = InCount / 3;
m = InCount % 3; m = InCount % 3;
size_t outCount = m ? (4 * (n + 1)) : (4 * n); if (!m)
outCount = 4 * n;
else
outCount = 4 * (n + 1);
std::string out; if (outCount > len) return 0;
out.reserve (outCount);
pd = (unsigned char *)OutBuffer;
for ( i = 0; i < n; i++ ) for ( i = 0; i < n; i++ )
{ {
acc_1 = *ps++; acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x30; acc_2 = (acc_1 << 4) & 0x30;
acc_1 >>= 2; // base64 digit #1 acc_1 >>= 2; /* base64 digit #1 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
acc_1 = *ps++; acc_1 = *ps++;
acc_2 |= acc_1 >> 4; // base64 digit #2 acc_2 |= acc_1 >> 4; /* base64 digit #2 */
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
acc_1 &= 0x0f; acc_1 &= 0x0f;
acc_1 <<= 2; acc_1 <<= 2;
acc_2 = *ps++; acc_2 = *ps++;
acc_1 |= acc_2 >> 6; // base64 digit #3 acc_1 |= acc_2 >> 6; /* base64 digit #3 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
acc_2 &= 0x3f; // base64 digit #4 acc_2 &= 0x3f; /* base64 digit #4 */
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
} }
if ( m == 1 ) if ( m == 1 )
{ {
acc_1 = *ps++; acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f; // base64 digit #2 acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */
acc_1 >>= 2; // base64 digit #1 acc_1 >>= 2; /* base64 digit #1 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
out.push_back (P64); *pd++ = P64;
out.push_back (P64); *pd++ = P64;
} }
else if ( m == 2 ) else if ( m == 2 )
{ {
acc_1 = *ps++; acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f; acc_2 = (acc_1 << 4) & 0x3f;
acc_1 >>= 2; // base64 digit #1 acc_1 >>= 2; /* base64 digit #1 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
acc_1 = *ps++; acc_1 = *ps++;
acc_2 |= acc_1 >> 4; // base64 digit #2 acc_2 |= acc_1 >> 4; /* base64 digit #2 */
out.push_back (T64[acc_2]); *pd++ = T64[acc_2];
acc_1 &= 0x0f; acc_1 &= 0x0f;
acc_1 <<= 2; // base64 digit #3 acc_1 <<= 2; /* base64 digit #3 */
out.push_back (T64[acc_1]); *pd++ = T64[acc_1];
out.push_back (P64); *pd++ = P64;
} }
return out; return outCount;
} }
/* /*
* *
* Base64ToByteStream * Base64ToByteStream
* ------------------ * ------------------
* *
* Converts BASE64 encoded string to binary format. If input buffer is * Converts BASE64 encoded data to binary format. If input buffer is
* not properly padded, buffer of negative length is returned * not properly padded, buffer of negative length is returned
* *
*/ */
size_t Base64ToByteStream ( // Number of output bytes
std::string_view base64Str, // BASE64 encoded string size_t Base64ToByteStream ( /* Number of output bytes */
uint8_t * OutBuffer, // output buffer length const char * InBuffer, /* BASE64 encoded buffer */
size_t len // length of output buffer size_t InCount, /* Number of input bytes */
uint8_t * OutBuffer, /* output buffer length */
size_t len /* length of output buffer */
) )
{ {
unsigned char * ps;
unsigned char * pd; unsigned char * pd;
unsigned char acc_1; unsigned char acc_1;
unsigned char acc_2; unsigned char acc_2;
int i;
int n;
int m;
size_t outCount; size_t outCount;
if (base64Str.empty () || base64Str[0] == P64) return 0; if (isFirstTime)
auto d = std::div (base64Str.length (), 4); iT64Build();
if (!d.rem)
outCount = 3 * d.quot; n = InCount / 4;
m = InCount % 4;
if (InCount && !m)
outCount = 3 * n;
else else
return 0; return 0;
if (isFirstTime) iT64Build(); if(*InBuffer == P64)
return 0;
ps = (unsigned char *)(InBuffer + InCount - 1);
while ( *ps-- == P64 )
outCount--;
ps = (unsigned char *)InBuffer;
if (outCount > len)
return 0;
auto pos = base64Str.find_last_not_of (P64);
if (pos == base64Str.npos) return 0;
outCount -= (base64Str.length () - pos - 1);
if (outCount > len) return 0;
auto ps = base64Str.begin ();
pd = OutBuffer; pd = OutBuffer;
auto endOfOutBuffer = OutBuffer + outCount; auto endOfOutBuffer = OutBuffer + outCount;
for (int i = 0; i < d.quot; i++) for ( i = 0; i < n; i++ )
{ {
acc_1 = iT64[int(*ps++)]; acc_1 = iT64[*ps++];
acc_2 = iT64[int(*ps++)]; acc_2 = iT64[*ps++];
acc_1 <<= 2; acc_1 <<= 2;
acc_1 |= acc_2 >> 4; acc_1 |= acc_2 >> 4;
*pd++ = acc_1; *pd++ = acc_1;
@ -187,30 +221,45 @@ namespace data
break; break;
acc_2 <<= 4; acc_2 <<= 4;
acc_1 = iT64[int(*ps++)]; acc_1 = iT64[*ps++];
acc_2 |= acc_1 >> 2; acc_2 |= acc_1 >> 2;
*pd++ = acc_2; *pd++ = acc_2;
if (pd >= endOfOutBuffer) if (pd >= endOfOutBuffer)
break; break;
acc_2 = iT64[int(*ps++)]; acc_2 = iT64[*ps++];
acc_2 |= acc_1 << 6; acc_2 |= acc_1 << 6;
*pd++ = acc_2; *pd++ = acc_2;
} }
return outCount; return outCount;
} }
std::string ToBase64Standard (std::string_view in) size_t Base64EncodingBufferSize (const size_t input_size)
{ {
auto str = ByteStreamToBase64 ((const uint8_t *)in.data (), in.length ()); auto d = div (input_size, 3);
if (d.rem)
d.quot++;
return 4 * d.quot;
}
std::string ToBase64Standard (const std::string& in)
{
auto len = Base64EncodingBufferSize (in.length ());
char * str = new char[len + 1];
auto l = ByteStreamToBase64 ((const uint8_t *)in.c_str (), in.length (), str, len);
str[l] = 0;
// replace '-' by '+' and '~' by '/' // replace '-' by '+' and '~' by '/'
for (auto& ch: str) for (size_t i = 0; i < l; i++)
if (ch == '-') if (str[i] == '-')
ch = '+'; str[i] = '+';
else if (ch == '~') else if (str[i] == '~')
ch = '/'; str[i] = '/';
return str;
std::string s(str);
delete[] str;
return s;
} }
/* /*
@ -231,12 +280,13 @@ namespace data
iT64[(int)P64] = 0; iT64[(int)P64] = 0;
} }
size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen) size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen)
{ {
unsigned int tmp = 0, bits = 0; unsigned int tmp = 0, bits = 0;
size_t ret = 0; size_t ret = 0;
for (auto ch: base32Str) for (size_t i = 0; i < len; i++)
{ {
char ch = inBuf[i];
if (ch >= '2' && ch <= '7') // digit if (ch >= '2' && ch <= '7') // digit
ch = (ch - '2') + 26; // 26 means a-z ch = (ch - '2') + 26; // 26 means a-z
else if (ch >= 'a' && ch <= 'z') else if (ch >= 'a' && ch <= 'z')
@ -256,15 +306,13 @@ namespace data
tmp <<= 5; tmp <<= 5;
} }
return ret; return ret;
} }
std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len) size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen)
{ {
std::string out; size_t ret = 0, pos = 1;
out.reserve ((len * 8 + 4) / 5);
size_t pos = 1;
unsigned int bits = 8, tmp = inBuf[0]; unsigned int bits = 8, tmp = inBuf[0];
while (bits > 0 || pos < len) while (ret < outLen && (bits > 0 || pos < len))
{ {
if (bits < 5) if (bits < 5)
{ {
@ -284,9 +332,10 @@ namespace data
bits -= 5; bits -= 5;
int ind = (tmp >> bits) & 0x1F; int ind = (tmp >> bits) & 0x1F;
out.push_back ((ind < 26) ? (ind + 'a') : ((ind - 26) + '2')); outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
ret++;
} }
return out; return ret;
} }
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,42 +11,27 @@
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <string_view> #include <iostream>
#include <cstdlib>
namespace i2p
{
namespace data
{
std::string ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount);
size_t Base64ToByteStream (std::string_view base64Str, uint8_t * OutBuffer, size_t len);
namespace i2p {
namespace data {
size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len);
size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len );
const char * GetBase32SubstitutionTable (); const char * GetBase32SubstitutionTable ();
const char * GetBase64SubstitutionTable (); const char * GetBase64SubstitutionTable ();
constexpr bool IsBase64 (char ch) bool IsBase64 (char ch);
{
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '~';
}
size_t Base32ToByteStream (std::string_view base32Str, uint8_t * outBuf, size_t outLen); size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);
std::string ByteStreamToBase32 (const uint8_t * inBuf, size_t len); size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen);
constexpr bool IsBase32 (char ch) bool IsBase32 (char ch);
{
return (ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7');
}
/** /**
* Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes * Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes
*/ */
inline size_t Base64EncodingBufferSize(size_t input_size) size_t Base64EncodingBufferSize(const size_t input_size);
{
auto d = std::div (input_size, 3); std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization
if (d.rem) d.quot++;
return 4 * d.quot;
}
std::string ToBase64Standard (std::string_view in); // using standard table, for Proxy-Authorization
} // data } // data
} // i2p } // i2p

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2022, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -152,11 +152,11 @@ namespace data
m_BlindedSigType = m_SigType; m_BlindedSigType = m_SigType;
} }
BlindedPublicKey::BlindedPublicKey (std::string_view b33): BlindedPublicKey::BlindedPublicKey (const std::string& b33):
m_SigType (0) // 0 means invalid, we can't blind DSA, set it later m_SigType (0) // 0 means invalid, we can't blind DSA, set it later
{ {
uint8_t addr[40]; // TODO: define length from b33 uint8_t addr[40]; // TODO: define length from b33
size_t l = i2p::data::Base32ToByteStream (b33, addr, 40); size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40);
if (l < 32) if (l < 32)
{ {
LogPrint (eLogError, "Blinding: Malformed b33 ", b33); LogPrint (eLogError, "Blinding: Malformed b33 ", b33);
@ -198,7 +198,7 @@ namespace data
std::string BlindedPublicKey::ToB33 () const std::string BlindedPublicKey::ToB33 () const
{ {
if (m_PublicKey.size () > 32) return ""; // assume 25519 if (m_PublicKey.size () > 32) return ""; // assume 25519
uint8_t addr[35]; uint8_t addr[35]; char str[60]; // TODO: define actual length
uint8_t flags = 0; uint8_t flags = 0;
if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG;
addr[0] = flags; // flags addr[0] = flags; // flags
@ -208,7 +208,8 @@ namespace data
uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ());
// checksum is Little Endian // checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
return ByteStreamToBase32 (addr, m_PublicKey.size () + 3); auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60);
return std::string (str, str + l);
} }
void BlindedPublicKey::GetCredential (uint8_t * credential) const void BlindedPublicKey::GetCredential (uint8_t * credential) const

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2020, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include "Identity.h" #include "Identity.h"
@ -24,7 +23,7 @@ namespace data
public: public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, bool clientAuth = false); BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, bool clientAuth = false);
BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p
std::string ToB33 () const; std::string ToB33 () const;
const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; const uint8_t * GetPublicKey () const { return m_PublicKey.data (); };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -154,17 +154,6 @@ namespace config {
("socksproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)") ("socksproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
; ;
options_description shareddest("Shared local destination options");
shareddest.add_options()
("shareddest.inbound.length", value<std::string>()->default_value("3"), "Shared local destination inbound tunnel length")
("shareddest.outbound.length", value<std::string>()->default_value("3"), "Shared local destination outbound tunnel length")
("shareddest.inbound.quantity", value<std::string>()->default_value("3"), "Shared local destination inbound tunnels quantity")
("shareddest.outbound.quantity", value<std::string>()->default_value("3"), "Shared local destination outbound tunnels quantity")
("shareddest.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Shared local destination's LeaseSet type")
("shareddest.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Shared local destination's LeaseSet encryption type")
("shareddest.i2p.streaming.profile", value<std::string>()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
;
options_description sam("SAM bridge options"); options_description sam("SAM bridge options");
sam.add_options() sam.add_options()
("sam.enabled", value<bool>()->default_value(true), "Enable or disable SAM Application bridge") ("sam.enabled", value<bool>()->default_value(true), "Enable or disable SAM Application bridge")
@ -238,7 +227,7 @@ namespace config {
"https://reseed.onion.im/," "https://reseed.onion.im/,"
"https://i2pseed.creativecowpat.net:8443/," "https://i2pseed.creativecowpat.net:8443/,"
"https://reseed.i2pgit.org/," "https://reseed.i2pgit.org/,"
"https://coconut.incognet.io/," "https://banana.incognet.io/,"
"https://reseed-pl.i2pd.xyz/," "https://reseed-pl.i2pd.xyz/,"
"https://www2.mk16.de/," "https://www2.mk16.de/,"
"https://i2p.ghativega.in/," "https://i2p.ghativega.in/,"
@ -352,7 +341,6 @@ namespace config {
.add(httpserver) .add(httpserver)
.add(httpproxy) .add(httpproxy)
.add(socksproxy) .add(socksproxy)
.add(shareddest)
.add(sam) .add(sam)
.add(bob) .add(bob)
.add(i2cp) .add(i2cp)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -19,10 +19,6 @@
#if OPENSSL_HKDF #if OPENSSL_HKDF
#include <openssl/kdf.h> #include <openssl/kdf.h>
#endif #endif
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
#include <openssl/param_build.h>
#include <openssl/core_names.h>
#endif
#include "CPU.h" #include "CPU.h"
#include "Crypto.h" #include "Crypto.h"
#include "Ed25519.h" #include "Ed25519.h"
@ -33,7 +29,7 @@ namespace i2p
{ {
namespace crypto namespace crypto
{ {
constexpr uint8_t elgp_[256]= const uint8_t elgp_[256]=
{ {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
@ -53,9 +49,9 @@ namespace crypto
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
}; };
constexpr int elgg_ = 2; const int elgg_ = 2;
constexpr uint8_t dsap_[128]= const uint8_t dsap_[128]=
{ {
0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c, 0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c,
0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15, 0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15,
@ -67,13 +63,13 @@ namespace crypto
0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93
}; };
constexpr uint8_t dsaq_[20]= const uint8_t dsaq_[20]=
{ {
0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d, 0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d,
0x68, 0x40, 0x46, 0xb7 0x68, 0x40, 0x46, 0xb7
}; };
constexpr uint8_t dsag_[128]= const uint8_t dsag_[128]=
{ {
0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0, 0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0,
0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81, 0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81,
@ -85,7 +81,7 @@ namespace crypto
0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82
}; };
constexpr int rsae_ = 65537; const int rsae_ = 65537;
struct CryptoConstants struct CryptoConstants
{ {
@ -150,37 +146,6 @@ namespace crypto
#define dsap GetCryptoConstants ().dsap #define dsap GetCryptoConstants ().dsap
#define dsaq GetCryptoConstants ().dsaq #define dsaq GetCryptoConstants ().dsaq
#define dsag GetCryptoConstants ().dsag #define dsag GetCryptoConstants ().dsag
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * CreateDSA (BIGNUM * pubKey, BIGNUM * privKey)
{
EVP_PKEY * pkey = nullptr;
int selection = EVP_PKEY_KEY_PARAMETERS;
auto bld = OSSL_PARAM_BLD_new();
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, dsap);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, dsaq);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, dsag);
if (pubKey)
{
OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PUB_KEY, pubKey);
selection = EVP_PKEY_PUBLIC_KEY;
}
if (privKey)
{
OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PRIV_KEY, privKey);
selection = EVP_PKEY_KEYPAIR;
}
auto params = OSSL_PARAM_BLD_to_param(bld);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "DSA", NULL);
EVP_PKEY_fromdata_init(ctx);
EVP_PKEY_fromdata(ctx, &pkey, selection, params);
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
return pkey;
}
#else
DSA * CreateDSA () DSA * CreateDSA ()
{ {
DSA * dsa = DSA_new (); DSA * dsa = DSA_new ();
@ -188,8 +153,7 @@ namespace crypto
DSA_set0_key (dsa, NULL, NULL); DSA_set0_key (dsa, NULL, NULL);
return dsa; return dsa;
} }
#endif
// DH/ElGamal // DH/ElGamal
#if !IS_X86_64 #if !IS_X86_64
@ -746,41 +710,19 @@ namespace crypto
{ {
return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false); return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false);
} }
static void ChaCha20 (EVP_CIPHER_CTX *ctx, const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
uint32_t iv[4]; uint32_t iv[4];
iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce
EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv); EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv);
int outlen = 0; int outlen = 0;
EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen); EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen);
EVP_EncryptFinal_ex(ctx, NULL, &outlen); EVP_EncryptFinal_ex(ctx, NULL, &outlen);
}
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
ChaCha20 (ctx, msg, msgLen, key, nonce, out);
EVP_CIPHER_CTX_free (ctx); EVP_CIPHER_CTX_free (ctx);
} }
ChaCha20Context::ChaCha20Context ()
{
m_Ctx = EVP_CIPHER_CTX_new ();
}
ChaCha20Context::~ChaCha20Context ()
{
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out);
}
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info,
uint8_t * out, size_t outLen) uint8_t * out, size_t outLen)
{ {
@ -821,18 +763,6 @@ namespace crypto
// Noise // Noise
void NoiseSymmetricState::Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub)
{
// pub is Bob's public static key, hh = SHA256(h)
memcpy (m_CK, ck, 32);
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, hh, 32);
SHA256_Update (&ctx, pub, 32);
SHA256_Final (m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub)
m_N = 0;
}
void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len)
{ {
SHA256_CTX ctx; SHA256_CTX ctx;
@ -856,95 +786,76 @@ namespace crypto
{ {
HKDF (m_CK, sharedSecret, 32, "", m_CK); HKDF (m_CK, sharedSecret, 32, "", m_CK);
// new ck is m_CK[0:31], key is m_CK[32:63] // new ck is m_CK[0:31], key is m_CK[32:63]
m_N = 0;
} }
bool NoiseSymmetricState::Encrypt (const uint8_t * in, uint8_t * out, size_t len) static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck,
const uint8_t * hh, const uint8_t * pub)
{ {
uint8_t nonce[12]; // pub is Bob's public static key, hh = SHA256(h)
if (m_N) memcpy (state.m_CK, ck, 32);
{ SHA256_CTX ctx;
memset (nonce, 0, 4); SHA256_Init (&ctx);
htole64buf (nonce + 4, m_N); SHA256_Update (&ctx, hh, 32);
} SHA256_Update (&ctx, pub, 32);
else SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub)
memset (nonce, 0, 12);
auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len + 16, true);
if (ret) m_N++;
return ret;
} }
bool NoiseSymmetricState::Decrypt (const uint8_t * in, uint8_t * out, size_t len)
{
uint8_t nonce[12];
if (m_N)
{
memset (nonce, 0, 4);
htole64buf (nonce + 4, m_N);
}
else
memset (nonce, 0, 12);
auto ret = AEADChaCha20Poly1305 (in, len, m_H, 32, m_CK + 32, nonce, out, len, false);
if (ret) m_N++;
return ret;
}
void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1,
0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54
}; // hh = SHA256(protocol_name || 0) }; // hh = SHA256(protocol_name || 0)
state.Init ((const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0
} }
void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr uint8_t protocolNameHash[32] = static const uint8_t protocolNameHash[32] =
{ {
0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed,
0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71
}; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256")
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5,
0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e
}; // SHA256 (protocolNameHash) }; // SHA256 (protocolNameHash)
state.Init (protocolNameHash, hh, pub); InitNoiseState (state, protocolNameHash, hh, pub);
} }
void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr uint8_t protocolNameHash[32] = static const uint8_t protocolNameHash[32] =
{ {
0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4, 0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4,
0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32 0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32
}; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256") }; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256")
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53, 0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53,
0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33 0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33
}; // SHA256 (protocolNameHash) }; // SHA256 (protocolNameHash)
state.Init (protocolNameHash, hh, pub); InitNoiseState (state, protocolNameHash, hh, pub);
} }
void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub)
{ {
static constexpr uint8_t protocolNameHash[32] = static const uint8_t protocolNameHash[32] =
{ {
0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba,
0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c
}; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes
static constexpr uint8_t hh[32] = static const uint8_t hh[32] =
{ {
0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32,
0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c
}; // SHA256 (protocolNameHash) }; // SHA256 (protocolNameHash)
state.Init (protocolNameHash, hh, pub); InitNoiseState (state, protocolNameHash, hh, pub);
} }
// init and terminate // init and terminate
/* std::vector <std::unique_ptr<std::mutex> > m_OpenSSLMutexes; /* std::vector <std::unique_ptr<std::mutex> > m_OpenSSLMutexes;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -33,9 +33,6 @@
# if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL # if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL
# define OPENSSL_SIPHASH 1 # define OPENSSL_SIPHASH 1
# endif # endif
# if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0
# define OPENSSL_PQ 1
# endif
#endif #endif
namespace i2p namespace i2p
@ -45,11 +42,7 @@ namespace crypto
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len);
// DSA // DSA
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * CreateDSA (BIGNUM * pubKey = nullptr, BIGNUM * privKey = nullptr);
#else
DSA * CreateDSA (); DSA * CreateDSA ();
#endif
// RSA // RSA
const BIGNUM * GetRSAE (); const BIGNUM * GetRSAE ();
@ -233,19 +226,6 @@ namespace crypto
// ChaCha20 // ChaCha20
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
class ChaCha20Context
{
public:
ChaCha20Context ();
~ChaCha20Context ();
void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
private:
EVP_CIPHER_CTX * m_Ctx;
};
// HKDF // HKDF
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32
@ -255,23 +235,17 @@ namespace crypto
struct NoiseSymmetricState struct NoiseSymmetricState
{ {
uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/; uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/;
uint64_t m_N;
void Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub);
void MixHash (const uint8_t * buf, size_t len); void MixHash (const uint8_t * buf, size_t len);
void MixHash (const std::vector<std::pair<uint8_t *, size_t> >& bufs); void MixHash (const std::vector<std::pair<uint8_t *, size_t> >& bufs);
void MixKey (const uint8_t * sharedSecret); void MixKey (const uint8_t * sharedSecret);
bool Encrypt (const uint8_t * in, uint8_t * out, size_t len); // out length = len + 16
bool Decrypt (const uint8_t * in, uint8_t * out, size_t len); // len without 16 bytes tag
}; };
void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router) void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router)
void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (NTCP2) void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (NTCP2)
void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (SSU2) void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (SSU2)
void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets) void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets)
// init and terminate // init and terminate
void InitCrypto (bool precomputation); void InitCrypto (bool precomputation);
void TerminateCrypto (); void TerminateCrypto ();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -181,21 +181,5 @@ namespace crypto
k.GetPrivateKey (priv); k.GetPrivateKey (priv);
memcpy (pub, k.GetPublicKey (), 32); memcpy (pub, k.GetPublicKey (), 32);
} }
LocalEncryptionKey::LocalEncryptionKey (i2p::data::CryptoKeyType t): keyType(t)
{
pub.resize (GetCryptoPublicKeyLen (keyType));
priv.resize (GetCryptoPrivateKeyLen (keyType));
}
void LocalEncryptionKey::GenerateKeys ()
{
i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv.data (), pub.data ());
}
void LocalEncryptionKey::CreateDecryptor ()
{
decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv.data ());
}
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2021, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,6 @@
#include <inttypes.h> #include <inttypes.h>
#include "Crypto.h" #include "Crypto.h"
#include "Identity.h"
namespace i2p namespace i2p
{ {
@ -158,50 +157,7 @@ namespace crypto
X25519Keys m_StaticKeys; X25519Keys m_StaticKeys;
}; };
void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub); // including hybrid void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub);
constexpr size_t GetCryptoPrivateKeyLen (i2p::data::CryptoKeyType type)
{
switch (type)
{
case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32;
// ML-KEM hybrid
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return 32;
};
return 0;
}
constexpr size_t GetCryptoPublicKeyLen (i2p::data::CryptoKeyType type)
{
switch (type)
{
case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32;
case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32;
// ML-KEM hybrid
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return 32;
};
return 0;
}
struct LocalEncryptionKey
{
std::vector<uint8_t> pub, priv;
i2p::data::CryptoKeyType keyType;
std::shared_ptr<CryptoKeyDecryptor> decryptor;
LocalEncryptionKey (i2p::data::CryptoKeyType t);
void GenerateKeys ();
void CreateDecryptor ();
};
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -104,7 +104,8 @@ namespace datagram
if (verified) if (verified)
{ {
auto session = ObtainSession (identity.GetIdentHash()); auto h = identity.GetIdentHash();
auto session = ObtainSession(h);
session->Ack(); session->Ack();
auto r = FindReceiver(toPort); auto r = FindReceiver(toPort);
if(r) if(r)
@ -380,19 +381,15 @@ namespace datagram
if (!found) if (!found)
{ {
m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true);
if (m_RoutingSession) if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ())
{ m_PendingRoutingSessions.push_back (m_RoutingSession);
m_RoutingSession->SetAckRequestInterval (DATAGRAM_SESSION_ACK_REQUEST_INTERVAL);
if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ())
m_PendingRoutingSessions.push_back (m_RoutingSession);
}
} }
} }
auto path = m_RoutingSession->GetSharedRoutingPath(); auto path = m_RoutingSession->GetSharedRoutingPath();
if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ()) if (path && m_RoutingSession->IsRatchets () && (m_RoutingSession->CleanupUnconfirmedTags () ||
m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT))
{ {
LogPrint (eLogDebug, "Datagram: path reset");
m_RoutingSession->SetSharedRoutingPath (nullptr); m_RoutingSession->SetSharedRoutingPath (nullptr);
path = nullptr; path = nullptr;
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -31,6 +31,8 @@ namespace datagram
{ {
// milliseconds for max session idle time // milliseconds for max session idle time
const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000; const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000;
// milliseconds for how long we try sticking to a dead routing path before trying to switch
const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000;
// milliseconds interval a routing path is used before switching // milliseconds interval a routing path is used before switching
const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000; const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000;
// milliseconds before lease expire should we try switching leases // milliseconds before lease expire should we try switching leases
@ -42,7 +44,6 @@ namespace datagram
// max 64 messages buffered in send queue for each datagram session // max 64 messages buffered in send queue for each datagram session
const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64; const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64;
const uint64_t DATAGRAM_MAX_FLUSH_INTERVAL = 5; // in milliseconds const uint64_t DATAGRAM_MAX_FLUSH_INTERVAL = 5; // in milliseconds
const int DATAGRAM_SESSION_ACK_REQUEST_INTERVAL = 5500; // in milliseconds
class DatagramSession : public std::enable_shared_from_this<DatagramSession> class DatagramSession : public std::enable_shared_from_this<DatagramSession>
{ {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -13,7 +13,6 @@
#include <vector> #include <vector>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include "Crypto.h" #include "Crypto.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "Log.h" #include "Log.h"
#include "FS.h" #include "FS.h"
#include "Timestamp.h" #include "Timestamp.h"
@ -196,7 +195,7 @@ namespace client
m_IsPublic = itr->second != "true"; m_IsPublic = itr->second != "true";
} }
int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0; int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency;
std::map<std::string, int&> intOpts = { std::map<std::string, int&> intOpts = {
{I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen},
{I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen}, {I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen},
@ -378,12 +377,10 @@ namespace client
{ {
I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]); I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]);
uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET); uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET);
LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID);
GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID, nullptr);
} }
bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
{ {
switch (typeID) switch (typeID)
{ {
@ -398,7 +395,7 @@ namespace client
m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET)); m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET));
break; break;
case eI2NPDatabaseStore: case eI2NPDatabaseStore:
HandleDatabaseStoreMessage (payload, len, from); HandleDatabaseStoreMessage (payload, len);
break; break;
case eI2NPDatabaseSearchReply: case eI2NPDatabaseSearchReply:
HandleDatabaseSearchReplyMessage (payload, len); HandleDatabaseSearchReplyMessage (payload, len);
@ -413,8 +410,7 @@ namespace client
return true; return true;
} }
void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
i2p::garlic::ECIESX25519AEADRatchetSession * from)
{ {
if (len < DATABASE_STORE_HEADER_SIZE) if (len < DATABASE_STORE_HEADER_SIZE)
{ {
@ -469,8 +465,7 @@ namespace client
if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET)
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet
else else
leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2
buf + offset, len - offset, true, from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType () ); // LeaseSet2
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
{ {
if (leaseSet->GetIdentHash () != GetIdentHash ()) if (leaseSet->GetIdentHash () != GetIdentHash ())
@ -499,8 +494,7 @@ namespace client
if (request->requestedBlindedKey) if (request->requestedBlindedKey)
{ {
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset, auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr, request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ());
from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType ());
if (ls2->IsValid () && !ls2->IsExpired ()) if (ls2->IsValid () && !ls2->IsExpired ())
{ {
leaseSet = ls2; leaseSet = ls2;
@ -603,8 +597,7 @@ namespace client
m_ExcludedFloodfills.clear (); m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 0; m_PublishReplyToken = 0;
// schedule verification // schedule verification
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
(m_Pool ? m_Pool->GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE : 0)));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1)); shared_from_this (), std::placeholders::_1));
} }
@ -683,8 +676,8 @@ namespace client
m_ExcludedFloodfills.clear (); m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 1; // dummy non-zero value m_PublishReplyToken = 1; // dummy non-zero value
// try again after a while // try again after a while
LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds"); LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds");
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1)); shared_from_this (), std::placeholders::_1));
return; return;
@ -703,7 +696,7 @@ namespace client
s->HandlePublishConfirmationTimer (boost::system::error_code()); s->HandlePublishConfirmationTimer (boost::system::error_code());
}); });
}; };
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1)); shared_from_this (), std::placeholders::_1));
outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg); outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg);
@ -719,15 +712,15 @@ namespace client
m_PublishReplyToken = 0; m_PublishReplyToken = 0;
if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
{ {
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again"); LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds or failed. will try again");
Publish (); Publish ();
} }
else else
{ {
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ());
// Java floodfill never sends confirmation back for unknown crypto type // Java floodfill never sends confirmation back for unknown crypto type
// assume it successive and try to verify // assume it successive and try to verify
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + PUBLISH_VERIFICATION_TIMEOUT_VARIANCE)); // always max m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1)); shared_from_this (), std::placeholders::_1));
@ -1000,10 +993,17 @@ namespace client
} }
} }
i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const
{
if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL;
}
ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * params): bool isPublic, const std::map<std::string, std::string> * params):
LeaseSetDestination (service, isPublic, params), LeaseSetDestination (service, isPublic, params),
m_Keys (keys), m_PreferredCryptoType (0), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED), m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED),
m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED), m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED),
m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS), m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS),
@ -1028,15 +1028,7 @@ namespace client
{ {
try try
{ {
i2p::data::CryptoKeyType cryptoType = std::stoi(it1); encryptionKeyTypes.insert (std::stoi(it1));
#if !OPENSSL_PQ
if (cryptoType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported
#endif
{
if (!m_PreferredCryptoType && cryptoType)
m_PreferredCryptoType = cryptoType; // first non-zero in the list
encryptionKeyTypes.insert (cryptoType);
}
} }
catch (std::exception& ex) catch (std::exception& ex)
{ {
@ -1053,15 +1045,20 @@ namespace client
for (auto& it: encryptionKeyTypes) for (auto& it: encryptionKeyTypes)
{ {
auto encryptionKey = std::make_shared<i2p::crypto::LocalEncryptionKey> (it); auto encryptionKey = new EncryptionKey (it);
if (IsPublic ()) if (IsPublic ())
PersistTemporaryKeys (encryptionKey); PersistTemporaryKeys (encryptionKey);
else else
encryptionKey->GenerateKeys (); encryptionKey->GenerateKeys ();
encryptionKey->CreateDecryptor (); encryptionKey->CreateDecryptor ();
if (it > i2p::data::CRYPTO_KEY_TYPE_ELGAMAL && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Only DSA can use LeaseSet1 {
m_EncryptionKeys.emplace (it, encryptionKey); m_ECIESx25519EncryptionKey.reset (encryptionKey);
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2
}
else
m_StandardEncryptionKey.reset (encryptionKey);
} }
if (IsPublic ()) if (IsPublic ())
@ -1411,56 +1408,31 @@ namespace client
return ret; return ret;
} }
void ClientDestination::PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys) void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys)
{ {
if (!keys) return; if (!keys) return;
std::string ident = GetIdentHash().ToBase32(); std::string ident = GetIdentHash().ToBase32();
std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat"); std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat");
std::ifstream f(path, std::ifstream::binary); std::ifstream f(path, std::ifstream::binary);
if (f)
{ if (f) {
size_t len = 0; f.read ((char *)keys->pub, 256);
if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) f.read ((char *)keys->priv, 256);
len = 512; return;
else if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
{
f.seekg (0, std::ios::end);
len = f.tellg();
f.seekg (0, std::ios::beg);
}
if (len == 512)
{
char pub[256], priv[256];
f.read (pub, 256);
memcpy (keys->pub.data(), pub, keys->pub.size());
f.read (priv, 256);
memcpy (keys->priv.data (), priv, keys->priv.size ());
}
else
{
f.read ((char *)keys->pub.data(), keys->pub.size());
f.read ((char *)keys->priv.data(), keys->priv.size());
}
if (f)
return;
else
LogPrint(eLogWarning, "Destination: Can't read keys from ", path);
} }
LogPrint (eLogInfo, "Destination: Creating new temporary keys of type ", keys->keyType, " for address ", ident, ".b32.i2p"); LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p");
memset (keys->priv.data (), 0, keys->priv.size ()); memset (keys->priv, 0, 256);
memset (keys->pub.data (), 0, keys->pub.size ()); memset (keys->pub, 0, 256);
keys->GenerateKeys (); keys->GenerateKeys ();
// TODO:: persist crypto key type
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
if (f1) if (f1) {
{ f1.write ((char *)keys->pub, 256);
f1.write ((char *)keys->pub.data (), keys->pub.size ()); f1.write ((char *)keys->priv, 256);
f1.write ((char *)keys->priv.data (), keys->priv.size ()); return;
} }
if (!f1) LogPrint(eLogCritical, "Destinations: Can't save keys to ", path);
LogPrint(eLogError, "Destination: Can't save keys to ", path);
} }
void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
@ -1468,10 +1440,9 @@ namespace client
std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet; std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet;
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
{ {
auto it = m_EncryptionKeys.find (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); if (m_StandardEncryptionKey)
if (it != m_EncryptionKeys.end ())
{ {
leaseSet = std::make_shared<i2p::data::LocalLeaseSet> (GetIdentity (), it->second->pub.data (), tunnels); leaseSet = std::make_shared<i2p::data::LocalLeaseSet> (GetIdentity (), m_StandardEncryptionKey->pub, tunnels);
// sign // sign
Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ());
} }
@ -1481,27 +1452,12 @@ namespace client
else else
{ {
// standard LS2 (type 3) first // standard LS2 (type 3) first
if (m_EncryptionKeys.empty ()) i2p::data::LocalLeaseSet2::KeySections keySections;
{ if (m_ECIESx25519EncryptionKey)
LogPrint (eLogError, "Destinations: No encryption keys"); keySections.push_back ({m_ECIESx25519EncryptionKey->keyType, 32, m_ECIESx25519EncryptionKey->pub} );
return; if (m_StandardEncryptionKey)
} keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} );
i2p::data::LocalLeaseSet2::EncryptionKeys keySections;
std::shared_ptr<const i2p::crypto::LocalEncryptionKey> preferredSection;
if (m_EncryptionKeys.size () == 1)
preferredSection = m_EncryptionKeys.begin ()->second; // only key
else
{
for (const auto& it: m_EncryptionKeys)
if (it.first == m_PreferredCryptoType)
preferredSection = it.second;
else
keySections.push_back (it.second);
}
if (preferredSection)
keySections.push_front (preferredSection); // make preferred first
auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch (); auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch ();
if (publishedTimestamp <= m_LastPublishedTimestamp) if (publishedTimestamp <= m_LastPublishedTimestamp)
{ {
@ -1526,22 +1482,11 @@ namespace client
bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const
{ {
std::shared_ptr<i2p::crypto::LocalEncryptionKey> encryptionKey; if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
if (!m_EncryptionKeys.empty ()) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor)
{ return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data);
if (m_EncryptionKeys.rbegin ()->first == preferredCrypto) if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor)
encryptionKey = m_EncryptionKeys.rbegin ()->second; return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data);
else
{
auto it = m_EncryptionKeys.find (preferredCrypto);
if (it != m_EncryptionKeys.end ())
encryptionKey = it->second;
}
if (!encryptionKey)
encryptionKey = m_EncryptionKeys.rbegin ()->second;
}
if (encryptionKey)
return encryptionKey->decryptor->Decrypt (encrypted, data);
else else
LogPrint (eLogError, "Destinations: Decryptor is not set"); LogPrint (eLogError, "Destinations: Decryptor is not set");
return false; return false;
@ -1549,26 +1494,14 @@ namespace client
bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const
{ {
#if __cplusplus >= 202002L // C++20 return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey;
return m_EncryptionKeys.contains (keyType);
#else
return m_EncryptionKeys.count (keyType) > 0;
#endif
} }
i2p::data::CryptoKeyType ClientDestination::GetRatchetsHighestCryptoType () const
{
if (m_EncryptionKeys.empty ()) return 0;
auto cryptoType = m_EncryptionKeys.rbegin ()->first;
return cryptoType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? cryptoType : 0;
}
const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const
{ {
auto it = m_EncryptionKeys.find (keyType); if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
if (it != m_EncryptionKeys.end ()) return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr;
return it->second->pub.data (); return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr;
return nullptr;
} }
void ClientDestination::ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params) void ClientDestination::ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -22,7 +22,6 @@
#include "Identity.h" #include "Identity.h"
#include "TunnelPool.h" #include "TunnelPool.h"
#include "Crypto.h" #include "Crypto.h"
#include "CryptoKey.h"
#include "LeaseSet.h" #include "LeaseSet.h"
#include "Garlic.h" #include "Garlic.h"
#include "NetDb.hpp" #include "NetDb.hpp"
@ -37,9 +36,8 @@ namespace client
const uint8_t PROTOCOL_TYPE_STREAMING = 6; const uint8_t PROTOCOL_TYPE_STREAMING = 6;
const uint8_t PROTOCOL_TYPE_DATAGRAM = 17; const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
const uint8_t PROTOCOL_TYPE_RAW = 18; const uint8_t PROTOCOL_TYPE_RAW = 18;
const int PUBLISH_CONFIRMATION_TIMEOUT = 1800; // in milliseconds const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
const int PUBLISH_VERIFICATION_TIMEOUT = 5; // in seconds after successful publish const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successful publish
const int PUBLISH_VERIFICATION_TIMEOUT_VARIANCE = 3; // in seconds
const int PUBLISH_MIN_INTERVAL = 20; // in seconds const int PUBLISH_MIN_INTERVAL = 20; // in seconds
const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically
const int LEASESET_REQUEST_TIMEOUT = 1600; // in milliseconds const int LEASESET_REQUEST_TIMEOUT = 1600; // in milliseconds
@ -164,19 +162,17 @@ namespace client
// implements GarlicDestination // implements GarlicDestination
void HandleI2NPMessage (const uint8_t * buf, size_t len) override; void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) override;
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet); void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet);
int GetLeaseSetType () const { return m_LeaseSetType; }; int GetLeaseSetType () const { return m_LeaseSetType; };
void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; }; void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; };
int GetAuthType () const { return m_AuthType; }; int GetAuthType () const { return m_AuthType; };
virtual void CleanupDestination () {}; // additional clean up in derived classes virtual void CleanupDestination () {}; // additional clean up in derived classes
virtual i2p::data::CryptoKeyType GetPreferredCryptoType () const = 0;
// I2CP // I2CP
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
virtual void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) = 0; virtual void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) = 0;
private: private:
void UpdateLeaseSet (); void UpdateLeaseSet ();
@ -185,7 +181,7 @@ namespace client
void HandlePublishConfirmationTimer (const boost::system::error_code& ecode); void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
void HandlePublishVerificationTimer (const boost::system::error_code& ecode); void HandlePublishVerificationTimer (const boost::system::error_code& ecode);
void HandlePublishDelayTimer (const boost::system::error_code& ecode); void HandlePublishDelayTimer (const boost::system::error_code& ecode);
void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len, i2p::garlic::ECIESX25519AEADRatchetSession * from); void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len); void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len);
void HandleDeliveryStatusMessage (uint32_t msgID); void HandleDeliveryStatusMessage (uint32_t msgID);
@ -195,6 +191,7 @@ namespace client
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
void HandleCleanupTimer (const boost::system::error_code& ecode); void HandleCleanupTimer (const boost::system::error_code& ecode);
void CleanupRemoteLeaseSets (); void CleanupRemoteLeaseSets ();
i2p::data::CryptoKeyType GetPreferredCryptoType () const;
private: private:
@ -231,14 +228,25 @@ namespace client
class ClientDestination: public LeaseSetDestination class ClientDestination: public LeaseSetDestination
{ {
struct EncryptionKey
{
uint8_t pub[256], priv[256];
i2p::data::CryptoKeyType keyType;
std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> decryptor;
EncryptionKey (i2p::data::CryptoKeyType t):keyType(t) { memset (pub, 0, 256); memset (priv, 0, 256); };
void GenerateKeys () { i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv, pub); };
void CreateDecryptor () { decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv); };
};
public: public:
ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys, ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * params = nullptr); bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~ClientDestination (); ~ClientDestination ();
void Start () override; void Start ();
void Stop () override; void Stop ();
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
@ -275,28 +283,24 @@ namespace client
i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true);
// implements LocalDestination // implements LocalDestination
bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override; bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const;
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override { return m_Keys.GetPublic (); }; std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const override; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const;
const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const override; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const;
protected: protected:
// GarlicDestionation void CleanupDestination ();
i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const override;
// LeaseSetDestination
void CleanupDestination () override;
i2p::data::CryptoKeyType GetPreferredCryptoType () const override { return m_PreferredCryptoType; }
// I2CP // I2CP
void HandleDataMessage (const uint8_t * buf, size_t len) override; void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels) override; void CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels);
private: private:
std::shared_ptr<ClientDestination> GetSharedFromThis () { std::shared_ptr<ClientDestination> GetSharedFromThis () {
return std::static_pointer_cast<ClientDestination>(shared_from_this ()); return std::static_pointer_cast<ClientDestination>(shared_from_this ());
} }
void PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys); void PersistTemporaryKeys (EncryptionKey * keys);
void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params); void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params);
template<typename Dest> template<typename Dest>
@ -305,9 +309,9 @@ namespace client
private: private:
i2p::data::PrivateKeys m_Keys; i2p::data::PrivateKeys m_Keys;
std::map<i2p::data::CryptoKeyType, std::shared_ptr<i2p::crypto::LocalEncryptionKey> > m_EncryptionKeys; // last is most preferable std::unique_ptr<EncryptionKey> m_StandardEncryptionKey;
i2p::data::CryptoKeyType m_PreferredCryptoType; std::unique_ptr<EncryptionKey> m_ECIESx25519EncryptionKey;
int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams; int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams;
bool m_IsStreamingAnswerPings; bool m_IsStreamingAnswerPings;
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,6 @@
#include "Log.h" #include "Log.h"
#include "util.h" #include "util.h"
#include "Crypto.h" #include "Crypto.h"
#include "PostQuantum.h"
#include "Elligator.h" #include "Elligator.h"
#include "Tag.h" #include "Tag.h"
#include "I2PEndian.h" #include "I2PEndian.h"
@ -95,17 +94,6 @@ namespace garlic
m_ItermediateSymmKeys.erase (index); m_ItermediateSymmKeys.erase (index);
} }
ReceiveRatchetTagSet::ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS):
m_Session (session), m_IsNS (isNS)
{
}
ReceiveRatchetTagSet::~ReceiveRatchetTagSet ()
{
if (m_IsNS && m_Session)
m_Session->CleanupReceiveNSRKeys ();
}
void ReceiveRatchetTagSet::Expire () void ReceiveRatchetTagSet::Expire ()
{ {
if (!m_ExpirationTimestamp) if (!m_ExpirationTimestamp)
@ -174,12 +162,12 @@ namespace garlic
return false; return false;
} }
if (m_Destination) if (m_Destination)
m_Destination->HandleECIESx25519GarlicClove (buf + offset, size, nullptr); m_Destination->HandleECIESx25519GarlicClove (buf + offset, size);
return true; return true;
} }
ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS): ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS):
GarlicRoutingSession (owner, true), m_RemoteStaticKeyType (0) GarlicRoutingSession (owner, true)
{ {
if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate); if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate);
RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0;
@ -263,82 +251,34 @@ namespace garlic
} }
return false; return false;
} }
void ECIESX25519AEADRatchetSession::CleanupReceiveNSRKeys ()
{
m_EphemeralKeys = nullptr;
#if OPENSSL_PQ
m_PQKeys = nullptr;
#endif
}
bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len)
{ {
if (!GetOwner ()) return false; if (!GetOwner ()) return false;
// we are Bob // we are Bob
// KDF1 // KDF1
i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk
if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk))
{ {
LogPrint (eLogError, "Garlic: Can't decode elligator"); LogPrint (eLogError, "Garlic: Can't decode elligator");
return false; return false;
} }
buf += 32; len -= 32; buf += 32; len -= 32;
MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
uint8_t sharedSecret[32]; uint8_t sharedSecret[32];
bool decrypted = false; if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk)
auto cryptoType = GetOwner ()->GetRatchetsHighestCryptoType ();
#if OPENSSL_PQ
if (cryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // we support post quantum
{ {
i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), cryptoType, GetOwner ()->GetEncryptionPublicKey (cryptoType)); // bpk LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key");
MixHash (m_Aepk, 32); // h = SHA256(h || aepk) return false;
}
if (GetOwner ()->Decrypt (m_Aepk, sharedSecret, cryptoType)) // x25519(bsk, aepk) MixKey (sharedSecret);
{
MixKey (sharedSecret);
auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (cryptoType);
std::vector<uint8_t> encapsKey(keyLen);
if (Decrypt (buf, encapsKey.data (), keyLen))
{
decrypted = true; // encaps section has right hash
MixHash (buf, keyLen + 16);
buf += keyLen + 16;
len -= keyLen + 16;
m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType);
m_PQKeys->SetPublicKey (encapsKey.data ());
}
}
}
#endif
if (!decrypted)
{
if (cryptoType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
GetOwner ()->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{
cryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk
MixHash (m_Aepk, 32); // h = SHA256(h || aepk)
if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk)
{
LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key");
return false;
}
MixKey (sharedSecret);
}
else
{
LogPrint (eLogWarning, "Garlic: No supported encryption type");
return false;
}
}
// decrypt flags/static // decrypt flags/static
uint8_t fs[32]; uint8_t nonce[12], fs[32];
if (!Decrypt (buf, fs, 32)) CreateNonce (0, nonce);
if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 32, m_H, 32, m_CK + 32, nonce, fs, 32, false)) // decrypt
{ {
LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed "); LogPrint (eLogWarning, "Garlic: Flags/static section AEAD verification failed ");
return false; return false;
@ -350,19 +290,21 @@ namespace garlic
bool isStatic = !i2p::data::Tag<32> (fs).IsZero (); bool isStatic = !i2p::data::Tag<32> (fs).IsZero ();
if (isStatic) if (isStatic)
{ {
// static key, fs is apk // static key, fs is apk
SetRemoteStaticKey (cryptoType, fs); memcpy (m_RemoteStaticKey, fs, 32);
if (!GetOwner ()->Decrypt (fs, sharedSecret, m_RemoteStaticKeyType)) // x25519(bsk, apk) if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk)
{ {
LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
} }
else // all zeros flags
CreateNonce (1, nonce);
// decrypt payload // decrypt payload
std::vector<uint8_t> payload (len - 16); // we must save original ciphertext std::vector<uint8_t> payload (len - 16); // we must save original ciphertext
if (!Decrypt (buf, payload.data (), len - 16)) if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt
{ {
LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed");
return false; return false;
@ -398,7 +340,7 @@ namespace garlic
{ {
case eECIESx25519BlkGalicClove: case eECIESx25519BlkGalicClove:
if (GetOwner ()) if (GetOwner ())
GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size, this); GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size);
break; break;
case eECIESx25519BlkNextKey: case eECIESx25519BlkNextKey:
LogPrint (eLogDebug, "Garlic: Next key"); LogPrint (eLogDebug, "Garlic: Next key");
@ -550,16 +492,7 @@ namespace garlic
offset += 32; offset += 32;
// KDF1 // KDF1
#if OPENSSL_PQ i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
{
i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), m_RemoteStaticKeyType, m_RemoteStaticKey); // bpk
m_PQKeys = i2p::crypto::CreateMLKEMKeys (m_RemoteStaticKeyType);
m_PQKeys->GenerateKeys ();
}
else
#endif
i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk
MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk)
uint8_t sharedSecret[32]; uint8_t sharedSecret[32];
if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk)
@ -568,32 +501,18 @@ namespace garlic
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
{
auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType);
std::vector<uint8_t> encapsKey(keyLen);
m_PQKeys->GetPublicKey (encapsKey.data ());
// encrypt encapsKey
if (!Encrypt (encapsKey.data (), out + offset, keyLen))
{
LogPrint (eLogWarning, "Garlic: ML-KEM encap_key section AEAD encryption failed ");
return false;
}
MixHash (out + offset, keyLen + 16); // h = SHA256(h || ciphertext)
offset += keyLen + 16;
}
#endif
// encrypt flags/static key section // encrypt flags/static key section
uint8_t nonce[12];
CreateNonce (0, nonce);
const uint8_t * fs; const uint8_t * fs;
if (isStatic) if (isStatic)
fs = GetOwner ()->GetEncryptionPublicKey (m_RemoteStaticKeyType); fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
else else
{ {
memset (out + offset, 0, 32); // all zeros flags section memset (out + offset, 0, 32); // all zeros flags section
fs = out + offset; fs = out + offset;
} }
if (!Encrypt (fs, out + offset, 32)) if (!i2p::crypto::AEADChaCha20Poly1305 (fs, 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed "); LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed ");
return false; return false;
@ -604,11 +523,13 @@ namespace garlic
// KDF2 // KDF2
if (isStatic) if (isStatic)
{ {
GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bpk) GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk)
MixKey (sharedSecret); MixKey (sharedSecret);
} }
else
CreateNonce (1, nonce);
// encrypt payload // encrypt payload
if (!Encrypt (payload, out + offset, len)) if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed");
return false; return false;
@ -646,7 +567,7 @@ namespace garlic
} }
memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR
memcpy (m_NSRH, m_H, 32); memcpy (m_NSRH, m_H, 32);
offset += 32; offset += 32;
// KDF for Reply Key Section // KDF for Reply Key Section
MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag)
MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk)
@ -657,33 +578,16 @@ namespace garlic
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
#if OPENSSL_PQ
if (m_PQKeys)
{
size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
std::vector<uint8_t> kemCiphertext(cipherTextLen);
m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret);
if (!Encrypt (kemCiphertext.data (), out + offset, cipherTextLen))
{
LogPrint (eLogWarning, "Garlic: NSR ML-KEM ciphertext section AEAD encryption failed");
return false;
}
m_NSREncodedPQKey = std::make_unique<std::vector<uint8_t> > (cipherTextLen + 16);
memcpy (m_NSREncodedPQKey->data (), out + offset, cipherTextLen + 16);
MixHash (out + offset, cipherTextLen + 16);
MixKey (sharedSecret);
offset += cipherTextLen + 16;
}
#endif
if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk) if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk)
{ {
LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); LogPrint (eLogWarning, "Garlic: Incorrect Alice static key");
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
uint8_t nonce[12];
CreateNonce (0, nonce);
// calculate hash for zero length // calculate hash for zero length
if (!Encrypt (sharedSecret /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
{ {
LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed");
return false; return false;
@ -704,7 +608,6 @@ namespace garlic
GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS); GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS);
i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32)
// encrypt payload // encrypt payload
uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed");
@ -726,34 +629,16 @@ namespace garlic
memcpy (m_H, m_NSRH, 32); memcpy (m_H, m_NSRH, 32);
MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag)
MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk)
m_N = 0; uint8_t nonce[12];
size_t offset = 40; CreateNonce (0, nonce);
#if OPENSSL_PQ if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + 40, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
if (m_PQKeys)
{
if (m_NSREncodedPQKey)
{
size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
memcpy (out + offset, m_NSREncodedPQKey->data (), cipherTextLen + 16);
MixHash (out + offset, cipherTextLen + 16);
offset += cipherTextLen + 16;
}
else
{
LogPrint (eLogWarning, "Garlic: No stored ML-KEM keys");
return false;
}
}
#endif
if (!Encrypt (m_NSRH /* can be anything */, out + offset, 0)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad)
{ {
LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed");
return false; return false;
} }
MixHash (out + offset, 16); // h = SHA256(h || ciphertext) MixHash (out + 40, 16); // h = SHA256(h || ciphertext)
// encrypt payload // encrypt payload
uint8_t nonce[12]; memset (nonce, 0, 12); if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset + 16, len + 16, true)) // encrypt
{ {
LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed"); LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed");
return false; return false;
@ -785,30 +670,13 @@ namespace garlic
return false; return false;
} }
MixKey (sharedSecret); MixKey (sharedSecret);
#if OPENSSL_PQ GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk)
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
{
// decrypt kem_ciphertext section
size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType);
std::vector<uint8_t> kemCiphertext(cipherTextLen);
if (!Decrypt (buf, kemCiphertext.data (), cipherTextLen))
{
LogPrint (eLogWarning, "Garlic: Reply ML-KEM ciphertext section AEAD decryption failed");
return false;
}
MixHash (buf, cipherTextLen + 16);
buf += cipherTextLen + 16;
len -= cipherTextLen + 16;
// decaps
m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret);
MixKey (sharedSecret);
}
#endif
GetOwner ()->Decrypt (bepk, sharedSecret, m_RemoteStaticKeyType); // x25519 (ask, bepk)
MixKey (sharedSecret); MixKey (sharedSecret);
uint8_t nonce[12];
CreateNonce (0, nonce);
// calculate hash for zero length // calculate hash for zero length
if (!Decrypt (buf, sharedSecret/* can be anything */, 0)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 0, m_H, 32, m_CK + 32, nonce, sharedSecret/* can be anything */, 0, false)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only
{ {
LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed"); LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed");
return false; return false;
@ -833,7 +701,6 @@ namespace garlic
} }
i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32)
// decrypt payload // decrypt payload
uint8_t nonce[12]; memset (nonce, 0, 12); // seqn = 0
if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt
{ {
LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed");
@ -843,8 +710,7 @@ namespace garlic
if (m_State == eSessionStateNewSessionSent) if (m_State == eSessionStateNewSessionSent)
{ {
m_State = eSessionStateEstablished; m_State = eSessionStateEstablished;
// don't delete m_EpehemralKey and m_PQKeys because delayd NSR's migth come //m_EphemeralKeys = nullptr; // TODO: delete after a while
// done in CleanupReceiveNSRKeys called from NSR tagset destructor
m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch ();
GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ());
} }
@ -937,10 +803,6 @@ namespace garlic
m_State = eSessionStateEstablished; m_State = eSessionStateEstablished;
m_NSRSendTagset = nullptr; m_NSRSendTagset = nullptr;
m_EphemeralKeys = nullptr; m_EphemeralKeys = nullptr;
#if OPENSSL_PQ
m_PQKeys = nullptr;
m_NSREncodedPQKey = nullptr;
#endif
[[fallthrough]]; [[fallthrough]];
case eSessionStateEstablished: case eSessionStateEstablished:
if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ()) if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ())
@ -971,12 +833,7 @@ namespace garlic
if (!payload) return nullptr; if (!payload) return nullptr;
size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload); size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload);
if (!len) return nullptr; if (!len) return nullptr;
#if OPENSSL_PQ
auto m = NewI2NPMessage (len + (m_State == eSessionStateEstablished ? 28 :
i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 116));
#else
auto m = NewI2NPMessage (len + 100); // 96 + 4 auto m = NewI2NPMessage (len + 100); // 96 + 4
#endif
m->Align (12); // in order to get buf aligned to 16 (12 + 4) m->Align (12); // in order to get buf aligned to 16 (12 + 4)
uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
@ -991,28 +848,16 @@ namespace garlic
if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen)) if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen))
return nullptr; return nullptr;
len += 96; len += 96;
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
len += i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType) + 16;
#endif
break; break;
case eSessionStateNewSessionReceived: case eSessionStateNewSessionReceived:
if (!NewSessionReplyMessage (payload, len, buf, m->maxLen)) if (!NewSessionReplyMessage (payload, len, buf, m->maxLen))
return nullptr; return nullptr;
len += 72; len += 72;
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16;
#endif
break; break;
case eSessionStateNewSessionReplySent: case eSessionStateNewSessionReplySent:
if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen)) if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen))
return nullptr; return nullptr;
len += 72; len += 72;
#if OPENSSL_PQ
if (m_RemoteStaticKeyType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD)
len += i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType) + 16;
#endif
break; break;
case eSessionStateOneTime: case eSessionStateOneTime:
if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false)) if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false))
@ -1068,7 +913,7 @@ namespace garlic
} }
} }
if (!sendAckRequest && !first && if (!sendAckRequest && !first &&
((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + m_AckRequestInterval) || // regular request ((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + ECIESX25519_ACK_REQUEST_INTERVAL) || // regular request
(m_AckRequestMsgID && ts > m_LastAckRequestSendTime + LEASESET_CONFIRMATION_TIMEOUT))) // previous request failed. try again (m_AckRequestMsgID && ts > m_LastAckRequestSendTime + LEASESET_CONFIRMATION_TIMEOUT))) // previous request failed. try again
{ {
// not LeaseSet // not LeaseSet

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -14,12 +14,10 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <array>
#include <list> #include <list>
#include <unordered_map> #include <unordered_map>
#include "Identity.h" #include "Identity.h"
#include "Crypto.h" #include "Crypto.h"
#include "PostQuantum.h"
#include "Garlic.h" #include "Garlic.h"
#include "Tag.h" #include "Tag.h"
@ -35,7 +33,7 @@ namespace garlic
const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received
const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds
const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds
const int ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL = 33000; // in milliseconds const int ECIESX25519_ACK_REQUEST_INTERVAL = 33000; // in milliseconds
const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3; const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3;
const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after
const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24;
@ -81,8 +79,8 @@ namespace garlic
{ {
public: public:
ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false); ReceiveRatchetTagSet (std::shared_ptr<ECIESX25519AEADRatchetSession> session, bool isNS = false):
~ReceiveRatchetTagSet () override; m_Session (session), m_IsNS (isNS) {};
bool IsNS () const { return m_IsNS; }; bool IsNS () const { return m_IsNS; };
std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; }; std::shared_ptr<ECIESX25519AEADRatchetSession> GetSession () { return m_Session; };
@ -166,32 +164,27 @@ namespace garlic
~ECIESX25519AEADRatchetSession (); ~ECIESX25519AEADRatchetSession ();
bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0);
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) override; std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg);
std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg); std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg);
const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; }
i2p::data::CryptoKeyType GetRemoteStaticKeyType () const { return m_RemoteStaticKeyType; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); }
void SetRemoteStaticKey (i2p::data::CryptoKeyType keyType, const uint8_t * key)
{
m_RemoteStaticKeyType = keyType;
memcpy (m_RemoteStaticKey, key, 32);
}
void Terminate () { m_IsTerminated = true; } void Terminate () { m_IsTerminated = true; }
void SetDestination (const i2p::data::IdentHash& dest) void SetDestination (const i2p::data::IdentHash& dest)
{ {
if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest));
} }
bool CheckExpired (uint64_t ts); // true is expired bool CheckExpired (uint64_t ts); // true is expired
bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; }
bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); }
void CleanupReceiveNSRKeys (); // called from ReceiveRatchetTagSet at Alice's side
bool IsRatchets () const { return true; };
bool IsRatchets () const override { return true; }; bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; };
bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; }; bool IsTerminated () const { return m_IsTerminated; }
bool IsTerminated () const override { return m_IsTerminated; } uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; };
uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; }; bool CleanupUnconfirmedTags (); // return true if unaswered Ack requests, called from I2CP
void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; };
bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP
protected: protected:
@ -199,7 +192,7 @@ namespace garlic
void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; }; void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; };
void CreateNonce (uint64_t seqn, uint8_t * nonce); void CreateNonce (uint64_t seqn, uint8_t * nonce);
void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr<ReceiveRatchetTagSet>& receiveTagset, int index); void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr<ReceiveRatchetTagSet>& receiveTagset, int index);
bool MessageConfirmed (uint32_t msgID) override; bool MessageConfirmed (uint32_t msgID);
private: private:
@ -225,15 +218,10 @@ namespace garlic
private: private:
i2p::data::CryptoKeyType m_RemoteStaticKeyType;
uint8_t m_RemoteStaticKey[32]; uint8_t m_RemoteStaticKey[32];
uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only
uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only
std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys; std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
#if OPENSSL_PQ
std::unique_ptr<i2p::crypto::MLKEMKeys> m_PQKeys;
std::unique_ptr<std::vector<uint8_t> > m_NSREncodedPQKey;
#endif
SessionState m_State = eSessionStateNew; SessionState m_State = eSessionStateNew;
uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds) uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds)
m_LastSentTimestamp = 0; // in milliseconds m_LastSentTimestamp = 0; // in milliseconds
@ -247,7 +235,6 @@ namespace garlic
uint64_t m_LastAckRequestSendTime = 0; // milliseconds uint64_t m_LastAckRequestSendTime = 0; // milliseconds
uint32_t m_AckRequestMsgID = 0; uint32_t m_AckRequestMsgID = 0;
int m_AckRequestNumAttempts = 0; int m_AckRequestNumAttempts = 0;
int m_AckRequestInterval = ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL; // milliseconds
public: public:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -15,10 +15,6 @@
#include <TargetConditionals.h> #include <TargetConditionals.h>
#endif #endif
#if defined(__HAIKU__)
#include <FindDirectory.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
#include <shlobj.h> #include <shlobj.h>
#include <windows.h> #include <windows.h>
@ -173,11 +169,12 @@ namespace fs {
dataDir += "/Library/Application Support/" + appName; dataDir += "/Library/Application Support/" + appName;
return; return;
#elif defined(__HAIKU__) #elif defined(__HAIKU__)
char home[PATH_MAX]; // /boot/home/config/settings char *home = getenv("HOME");
if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, home, PATH_MAX) == B_OK) if (home != NULL && strlen(home) > 0) {
dataDir = std::string(home) + "/" + appName; dataDir = std::string(home) + "/config/settings/" + appName;
else } else {
dataDir = "/tmp/" + appName; dataDir = "/tmp/" + appName;
}
return; return;
#else /* other unix */ #else /* other unix */
#if defined(ANDROID) #if defined(ANDROID)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -90,10 +90,10 @@ namespace data
} }
bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
std::string_view signature, const char * key) const const char * signature, const char * key) const
{ {
uint8_t buf[100], signatureBuf[64]; uint8_t buf[100], signatureBuf[64];
size_t len = family.length (); size_t len = family.length (), signatureLen = strlen (signature);
if (len + 32 > 100) if (len + 32 > 100)
{ {
LogPrint (eLogError, "Family: ", family, " is too long"); LogPrint (eLogError, "Family: ", family, " is too long");
@ -105,7 +105,7 @@ namespace data
memcpy (buf, family.c_str (), len); memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32); memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32; len += 32;
auto signatureBufLen = Base64ToByteStream (signature, signatureBuf, 64); auto signatureBufLen = Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
if (signatureBufLen) if (signatureBufLen)
{ {
EVP_MD_CTX * ctx = EVP_MD_CTX_create (); EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
@ -154,7 +154,12 @@ namespace data
memcpy (buf + len, (const uint8_t *)ident, 32); memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32; len += 32;
signer.Sign (buf, len, signature); signer.Sign (buf, len, signature);
sig = ByteStreamToBase64 (signature, 64); len = Base64EncodingBufferSize (64);
char * b64 = new char[len+1];
len = ByteStreamToBase64 (signature, 64, b64, len);
b64[len] = 0;
sig = b64;
delete[] b64;
} }
else else
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,6 @@
#include <map> #include <map>
#include <string> #include <string>
#include <string_view>
#include <memory> #include <memory>
#include <openssl/evp.h> #include <openssl/evp.h>
#include "Identity.h" #include "Identity.h"
@ -29,7 +28,7 @@ namespace data
~Families (); ~Families ();
void LoadCertificates (); void LoadCertificates ();
bool VerifyFamily (const std::string& family, const IdentHash& ident, bool VerifyFamily (const std::string& family, const IdentHash& ident,
std::string_view signature, const char * key = nullptr) const; const char * signature, const char * key = nullptr) const;
FamilyID GetFamilyID (const std::string& family) const; FamilyID GetFamilyID (const std::string& family) const;
private: private:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -498,8 +498,7 @@ namespace garlic
buf += 4; // length buf += 4; // length
bool found = false; bool found = false;
bool supportsRatchets = SupportsRatchets (); if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
if (supportsRatchets)
// try ECIESx25519 tag // try ECIESx25519 tag
found = HandleECIESx25519TagMessage (buf, length); found = HandleECIESx25519TagMessage (buf, length);
if (!found) if (!found)
@ -536,7 +535,7 @@ namespace garlic
decryption->Decrypt(buf + 514, length - 514, iv, buf + 514); decryption->Decrypt(buf + 514, length - 514, iv, buf + 514);
HandleAESBlock (buf + 514, length - 514, decryption, msg->from); HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
} }
else if (supportsRatchets) else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{ {
// otherwise ECIESx25519 // otherwise ECIESx25519
auto ts = i2p::util::GetMillisecondsSinceEpoch (); auto ts = i2p::util::GetMillisecondsSinceEpoch ();
@ -748,35 +747,31 @@ namespace garlic
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet, std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet,
bool requestNewIfNotFound) bool requestNewIfNotFound)
{ {
if (destination->GetEncryptionType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD &&
SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{ {
if (SupportsEncryptionType (destination->GetEncryptionType ())) ECIESX25519AEADRatchetSessionPtr session;
{ uint8_t staticKey[32];
ECIESX25519AEADRatchetSessionPtr session; destination->Encrypt (nullptr, staticKey); // we are supposed to get static key
uint8_t staticKey[32]; auto it = m_ECIESx25519Sessions.find (staticKey);
destination->Encrypt (nullptr, staticKey); // we are supposed to get static key if (it != m_ECIESx25519Sessions.end ())
auto it = m_ECIESx25519Sessions.find (staticKey); {
if (it != m_ECIESx25519Sessions.end ()) session = it->second;
if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ()))
{ {
session = it->second; LogPrint (eLogDebug, "Garlic: Session restarted");
if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) requestNewIfNotFound = true; // it's not a new session
{ session = nullptr;
LogPrint (eLogDebug, "Garlic: Session restarted");
requestNewIfNotFound = true; // it's not a new session
session = nullptr;
}
} }
if (!session && requestNewIfNotFound)
{
session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true);
session->SetRemoteStaticKey (destination->GetEncryptionType (), staticKey);
}
if (session && destination->IsDestination ())
session->SetDestination (destination->GetIdentHash ()); // NS or NSR
return session;
} }
else if (!session && requestNewIfNotFound)
LogPrint (eLogError, "Garlic: Non-supported encryption type ", destination->GetEncryptionType ()); {
session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true);
session->SetRemoteStaticKey (staticKey);
}
if (session && destination->IsDestination ())
session->SetDestination (destination->GetIdentHash ()); // NS or NSR
return session;
} }
else else
{ {
@ -1004,8 +999,7 @@ namespace garlic
i2p::fs::Remove (it); i2p::fs::Remove (it);
} }
void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len)
ECIESX25519AEADRatchetSession * from)
{ {
const uint8_t * buf1 = buf; const uint8_t * buf1 = buf;
uint8_t flag = buf[0]; buf++; // flag uint8_t flag = buf[0]; buf++; // flag
@ -1025,7 +1019,7 @@ namespace garlic
buf += 4; // expiration buf += 4; // expiration
ptrdiff_t offset = buf - buf1; ptrdiff_t offset = buf - buf1;
if (offset <= (int)len) if (offset <= (int)len)
HandleCloveI2NPMessage (typeID, buf, len - offset, msgID, from); HandleCloveI2NPMessage (typeID, buf, len - offset, msgID);
else else
LogPrint (eLogError, "Garlic: Clove is too long"); LogPrint (eLogError, "Garlic: Clove is too long");
break; break;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -116,8 +116,7 @@ namespace garlic
virtual bool IsReadyToSend () const { return true; }; virtual bool IsReadyToSend () const { return true; };
virtual bool IsTerminated () const { return !GetOwner (); }; virtual bool IsTerminated () const { return !GetOwner (); };
virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only
virtual void SetAckRequestInterval (int interval) {}; // in milliseconds, override in ECIESX25519AEADRatchetSession
void SetLeaseSetUpdated () void SetLeaseSetUpdated ()
{ {
if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated;
@ -257,7 +256,7 @@ namespace garlic
uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset);
void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session);
void RemoveECIESx25519Session (const uint8_t * staticKey); void RemoveECIESx25519Session (const uint8_t * staticKey);
void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len, ECIESX25519AEADRatchetSession * from); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len);
uint8_t * GetPayloadBuffer (); uint8_t * GetPayloadBuffer ();
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg); virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
@ -266,27 +265,21 @@ namespace garlic
virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0; virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
virtual i2p::data::CryptoKeyType GetRatchetsHighestCryptoType () const
{
return GetIdentity ()->GetCryptoKeyType () >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? GetIdentity ()->GetCryptoKeyType () : 0;
}
protected: protected:
void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag
bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only
virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0;
size_t len, uint32_t msgID, ECIESX25519AEADRatchetSession * from) = 0;
void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg); void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void HandleDeliveryStatusMessage (uint32_t msgID); void HandleDeliveryStatusMessage (uint32_t msgID);
void SaveTags (); void SaveTags ();
void LoadTags (); void LoadTags ();
private: private:
bool SupportsRatchets () const { return GetRatchetsHighestCryptoType () > 0; }
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption, void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption,
std::shared_ptr<i2p::tunnel::InboundTunnel> from); std::shared_ptr<i2p::tunnel::InboundTunnel> from);
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from); void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,7 +10,6 @@
#include <utility> #include <utility>
#include <stdio.h> #include <stdio.h>
#include <ctime> #include <ctime>
#include <charconv>
#include "util.h" #include "util.h"
#include "Base.h" #include "Base.h"
#include "HTTP.h" #include "HTTP.h"
@ -19,51 +18,54 @@ namespace i2p
{ {
namespace http namespace http
{ {
// list of valid HTTP methods const std::vector<std::string> HTTP_METHODS = {
static constexpr std::array<std::string_view, 16> HTTP_METHODS =
{
"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods
"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323 "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323
}; };
const std::vector<std::string> HTTP_VERSIONS = {
// list of valid HTTP versions
static constexpr std::array<std::string_view, 2> HTTP_VERSIONS =
{
"HTTP/1.0", "HTTP/1.1" "HTTP/1.0", "HTTP/1.1"
}; };
const std::vector<const char *> weekdays = {
static constexpr std::array<const char *, 7> weekdays =
{
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
}; };
const std::vector<const char *> months = {
static constexpr std::array<const char *, 12> months =
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
}; };
static inline bool is_http_version(std::string_view str) inline bool is_http_version(const std::string & str) {
{
return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
} }
static inline bool is_http_method(std::string_view str) inline bool is_http_method(const std::string & str) {
{
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
} }
static void strsplit(std::string_view line, std::vector<std::string_view> &tokens, char delim, std::size_t limit = 0) static void strsplit(std::stringstream& ss, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{ {
size_t count = 0, pos; std::size_t count = 0;
while ((pos = line.find (delim)) != line.npos) std::string token;
while (1)
{ {
count++; count++;
if (limit > 0 && count >= limit) delim = '\n'; // reset delimiter if (limit > 0 && count >= limit)
tokens.push_back (line.substr (0, pos)); delim = '\n'; /* reset delimiter */
line = line.substr (pos + 1); if (!std::getline(ss, token, delim))
break;
tokens.push_back(token);
} }
if (!line.empty ()) tokens.push_back (line); }
static void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{
std::stringstream ss{line};
strsplit (ss, tokens, delim, limit);
}
static void strsplit(std::string_view line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{
std::stringstream ss{std::string(line)};
strsplit (ss, tokens, delim, limit);
} }
static std::pair<std::string, std::string> parse_header_line(std::string_view line) static std::pair<std::string, std::string> parse_header_line(std::string_view line)
@ -209,9 +211,8 @@ namespace http
return true; return true;
} }
bool URL::parse_query(std::map<std::string, std::string> & params) bool URL::parse_query(std::map<std::string, std::string> & params) {
{ std::vector<std::string> tokens;
std::vector<std::string_view> tokens;
strsplit(query, tokens, '&'); strsplit(query, tokens, '&');
params.clear(); params.clear();
@ -307,9 +308,8 @@ namespace http
if (expect == REQ_LINE) if (expect == REQ_LINE)
{ {
std::string_view line = str.substr(pos, eol - pos); std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string_view> tokens; std::vector<std::string> tokens;
strsplit(line, tokens, ' '); strsplit(line, tokens, ' ');
if (tokens.size() != 3) if (tokens.size() != 3)
return -1; return -1;
if (!is_http_method(tokens[0])) if (!is_http_method(tokens[0]))
@ -333,11 +333,11 @@ namespace http
else else
return -1; return -1;
} }
pos = eol + CRLF.length(); pos = eol + strlen(CRLF);
if (pos >= eoh) if (pos >= eoh)
break; break;
} }
return eoh + HTTP_EOH.length(); return eoh + strlen(HTTP_EOH);
} }
void HTTPReq::write(std::ostream & o) void HTTPReq::write(std::ostream & o)
@ -381,7 +381,7 @@ namespace http
} }
} }
std::string HTTPReq::GetHeader (std::string_view name) const std::string HTTPReq::GetHeader (const std::string& name) const
{ {
for (auto& it : headers) for (auto& it : headers)
if (it.first == name) if (it.first == name)
@ -389,7 +389,7 @@ namespace http
return ""; return "";
} }
size_t HTTPReq::GetNumHeaders (std::string_view name) const size_t HTTPReq::GetNumHeaders (const std::string& name) const
{ {
size_t num = 0; size_t num = 0;
for (auto& it : headers) for (auto& it : headers)
@ -451,15 +451,13 @@ namespace http
if (expect == RES_LINE) if (expect == RES_LINE)
{ {
std::string_view line = str.substr(pos, eol - pos); std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string_view> tokens; std::vector<std::string> tokens;
strsplit(line, tokens, ' ', 3); strsplit(line, tokens, ' ', 3);
if (tokens.size() != 3) if (tokens.size() != 3)
return -1; return -1;
if (!is_http_version(tokens[0])) if (!is_http_version(tokens[0]))
return -1; return -1;
auto res = std::from_chars(tokens[1].data (), tokens[1].data() + tokens[1].size(), code); code = atoi(tokens[1].c_str());
if (res.ec != std::errc())
return -1;
if (code < 100 || code >= 600) if (code < 100 || code >= 600)
return -1; return -1;
/* all ok */ /* all ok */
@ -476,11 +474,11 @@ namespace http
else else
return -1; return -1;
} }
pos = eol + CRLF.length(); pos = eol + strlen(CRLF);
if (pos >= eoh) if (pos >= eoh)
break; break;
} }
return eoh + HTTP_EOH.length(); return eoh + strlen(HTTP_EOH);
} }
std::string HTTPRes::to_string() { std::string HTTPRes::to_string() {
@ -505,11 +503,9 @@ namespace http
return ss.str(); return ss.str();
} }
std::string_view HTTPCodeToStatus(int code) const char * HTTPCodeToStatus(int code) {
{ const char *ptr;
std::string_view ptr; switch (code) {
switch (code)
{
case 105: ptr = "Name Not Resolved"; break; case 105: ptr = "Name Not Resolved"; break;
/* success */ /* success */
case 200: ptr = "OK"; break; case 200: ptr = "OK"; break;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -21,8 +21,10 @@ namespace i2p
{ {
namespace http namespace http
{ {
constexpr std::string_view CRLF = "\r\n"; /**< HTTP line terminator */ const char CRLF[] = "\r\n"; /**< HTTP line terminator */
constexpr std::string_view HTTP_EOH = "\r\n\r\n"; /**< HTTP end-of-headers mark */ const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */
extern const std::vector<std::string> HTTP_METHODS; /**< list of valid HTTP methods */
extern const std::vector<std::string> HTTP_VERSIONS; /**< list of valid HTTP versions */
struct URL struct URL
{ {
@ -101,8 +103,8 @@ namespace http
void UpdateHeader (const std::string& name, const std::string& value); void UpdateHeader (const std::string& name, const std::string& value);
void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt
void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); };
std::string GetHeader (std::string_view name) const; std::string GetHeader (const std::string& name) const;
size_t GetNumHeaders (std::string_view name) const; size_t GetNumHeaders (const std::string& name) const;
size_t GetNumHeaders () const { return headers.size (); }; size_t GetNumHeaders () const { return headers.size (); };
}; };
@ -152,7 +154,7 @@ namespace http
* @param code HTTP code [100, 599] * @param code HTTP code [100, 599]
* @return Immutable string with status * @return Immutable string with status
*/ */
std::string_view HTTPCodeToStatus(int code); const char * HTTPCodeToStatus(int code);
/** /**
* @brief Replaces %-encoded characters in string with their values * @brief Replaces %-encoded characters in string with their values

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,7 +10,6 @@
#include "I2PEndian.h" #include "I2PEndian.h"
#include "Log.h" #include "Log.h"
#include "Timestamp.h" #include "Timestamp.h"
#include "CryptoKey.h"
#include "Identity.h" #include "Identity.h"
namespace i2p namespace i2p
@ -28,15 +27,18 @@ namespace data
size_t Identity::FromBuffer (const uint8_t * buf, size_t len) size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
{ {
if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow if ( len < DEFAULT_IDENTITY_SIZE ) {
memcpy (this, buf, DEFAULT_IDENTITY_SIZE); // buffer too small, don't overflow
return 0;
}
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
return DEFAULT_IDENTITY_SIZE; return DEFAULT_IDENTITY_SIZE;
} }
IdentHash Identity::Hash () const IdentHash Identity::Hash () const
{ {
IdentHash hash; IdentHash hash;
SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash); SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
return hash; return hash;
} }
@ -120,16 +122,6 @@ namespace data
memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH); memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH);
break; break;
} }
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
{
memcpy (m_StandardIdentity, signingKey, 384);
excessLen = i2p::crypto::MLDSA44_PUBLIC_KEY_LENGTH - 384;
excessBuf = new uint8_t[excessLen];
memcpy (excessBuf, signingKey + 384, excessLen);
break;
}
#endif
default: default:
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported"); LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported");
} }
@ -270,19 +262,23 @@ namespace data
return fullLen; return fullLen;
} }
size_t IdentityEx::FromBase64(std::string_view s) size_t IdentityEx::FromBase64(const std::string& s)
{ {
std::vector<uint8_t> buf(s.length ()); // binary data can't exceed base64 const size_t slen = s.length();
auto len = Base64ToByteStream (s, buf.data(), buf.size ()); std::vector<uint8_t> buf(slen); // binary data can't exceed base64
const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen);
return FromBuffer (buf.data(), len); return FromBuffer (buf.data(), len);
} }
std::string IdentityEx::ToBase64 () const std::string IdentityEx::ToBase64 () const
{ {
const size_t bufLen = GetFullLen(); const size_t bufLen = GetFullLen();
const size_t strLen = Base64EncodingBufferSize(bufLen);
std::vector<uint8_t> buf(bufLen); std::vector<uint8_t> buf(bufLen);
std::vector<char> str(strLen);
size_t l = ToBuffer (buf.data(), bufLen); size_t l = ToBuffer (buf.data(), bufLen);
return i2p::data::ByteStreamToBase64 (buf.data(), l); size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen);
return std::string (str.data(), l1);
} }
size_t IdentityEx::GetSigningPublicKeyLen () const size_t IdentityEx::GetSigningPublicKeyLen () const
@ -359,10 +355,6 @@ namespace data
return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512); return new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512);
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
return new i2p::crypto::RedDSA25519Verifier (); return new i2p::crypto::RedDSA25519Verifier ();
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
return new i2p::crypto::MLDSA44Verifier ();
#endif
case SIGNING_KEY_TYPE_RSA_SHA256_2048: case SIGNING_KEY_TYPE_RSA_SHA256_2048:
case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096: case SIGNING_KEY_TYPE_RSA_SHA512_4096:
@ -384,18 +376,6 @@ namespace data
auto keyLen = verifier->GetPublicKeyLen (); auto keyLen = verifier->GetPublicKeyLen ();
if (keyLen <= 128) if (keyLen <= 128)
verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen);
#if OPENSSL_PQ
else if (keyLen > 384)
{
// for post-quantum
uint8_t * signingKey = new uint8_t[keyLen];
memcpy (signingKey, m_StandardIdentity.signingKey, 384);
size_t excessLen = keyLen - 384;
memcpy (signingKey + 384, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
verifier->SetPublicKey (signingKey);
delete[] signingKey;
}
#endif
else else
{ {
// for P521 // for P521
@ -419,14 +399,15 @@ namespace data
return std::make_shared<i2p::crypto::ElGamalEncryptor>(key); return std::make_shared<i2p::crypto::ElGamalEncryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetEncryptor>(key); return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetEncryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
return std::make_shared<i2p::crypto::ECIESP256Encryptor>(key); return std::make_shared<i2p::crypto::ECIESP256Encryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<i2p::crypto::ECIESGOSTR3410Encryptor>(key);
break;
default: default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType); LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType);
}; };
@ -570,18 +551,26 @@ namespace data
return ret; return ret;
} }
size_t PrivateKeys::FromBase64(std::string_view s) size_t PrivateKeys::FromBase64(const std::string& s)
{ {
std::vector<uint8_t> buf(s.length ()); uint8_t * buf = new uint8_t[s.length ()];
size_t l = i2p::data::Base64ToByteStream (s, buf.data (), buf.size ()); size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
return FromBuffer (buf.data (), l); size_t ret = FromBuffer (buf, l);
delete[] buf;
return ret;
} }
std::string PrivateKeys::ToBase64 () const std::string PrivateKeys::ToBase64 () const
{ {
std::vector<uint8_t> buf(GetFullLen ()); uint8_t * buf = new uint8_t[GetFullLen ()];
size_t l = ToBuffer (buf.data (), buf.size ()); char * str = new char[GetFullLen ()*2];
return i2p::data::ByteStreamToBase64 (buf.data (), l); size_t l = ToBuffer (buf, GetFullLen ());
size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, GetFullLen ()*2);
str[l1] = 0;
delete[] buf;
std::string ret(str);
delete[] str;
return ret;
} }
void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const
@ -644,11 +633,6 @@ namespace data
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
return new i2p::crypto::RedDSA25519Signer (priv); return new i2p::crypto::RedDSA25519Signer (priv);
break; break;
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
return new i2p::crypto::MLDSA44Signer (priv);
break;
#endif
default: default:
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
} }
@ -662,7 +646,8 @@ namespace data
size_t PrivateKeys::GetPrivateKeyLen () const size_t PrivateKeys::GetPrivateKeyLen () const
{ {
return i2p::crypto::GetCryptoPrivateKeyLen (m_Public->GetCryptoKeyType ()); // private key length always 256, but type 4
return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) ? 32 : 256;
} }
uint8_t * PrivateKeys::GetPadding() uint8_t * PrivateKeys::GetPadding()
@ -688,14 +673,15 @@ namespace data
return std::make_shared<i2p::crypto::ElGamalDecryptor>(key); return std::make_shared<i2p::crypto::ElGamalDecryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetDecryptor>(key); return std::make_shared<i2p::crypto::ECIESX25519AEADRatchetDecryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
return std::make_shared<i2p::crypto::ECIESP256Decryptor>(key); return std::make_shared<i2p::crypto::ECIESP256Decryptor>(key);
break; break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<i2p::crypto::ECIESGOSTR3410Decryptor>(key);
break;
default: default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType); LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType);
}; };
@ -742,7 +728,9 @@ namespace data
case SIGNING_KEY_TYPE_RSA_SHA384_3072: case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096: case SIGNING_KEY_TYPE_RSA_SHA512_4096:
LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA"); LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA");
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]]; [[fallthrough]];
#endif
// no break here // no break here
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub); i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub);
@ -756,11 +744,6 @@ namespace data
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub); i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub);
break; break;
#if OPENSSL_PQ
case SIGNING_KEY_TYPE_MLDSA44:
i2p::crypto::CreateMLDSA44RandomKeys (priv, pub);
break;
#endif
default: default:
LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1 i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1
@ -775,12 +758,13 @@ namespace data
i2p::crypto::GenerateElGamalKeyPair(priv, pub); i2p::crypto::GenerateElGamalKeyPair(priv, pub);
break; break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
i2p::crypto::CreateECIESP256RandomKeys (priv, pub); i2p::crypto::CreateECIESP256RandomKeys (priv, pub);
break; break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub);
break;
case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD:
case CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD:
i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub); i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub);
break; break;
default: default:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -12,19 +12,14 @@
#include <inttypes.h> #include <inttypes.h>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <string_view>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "Base.h" #include "Base.h"
#include "Signature.h" #include "Signature.h"
#include "CryptoKey.h"
namespace i2p namespace i2p
{ {
namespace crypto
{
class CryptoKeyEncryptor;
class CryptoKeyDecryptor;
}
namespace data namespace data
{ {
typedef Tag<32> IdentHash; typedef Tag<32> IdentHash;
@ -59,8 +54,6 @@ namespace data
Identity& operator=(const Keys& keys); Identity& operator=(const Keys& keys);
size_t FromBuffer (const uint8_t * buf, size_t len); size_t FromBuffer (const uint8_t * buf, size_t len);
IdentHash Hash () const; IdentHash Hash () const;
operator uint8_t * () { return reinterpret_cast<uint8_t *>(this); }
operator const uint8_t * () const { return reinterpret_cast<const uint8_t *>(this); }
}; };
Keys CreateRandomKeys (); Keys CreateRandomKeys ();
@ -70,10 +63,9 @@ namespace data
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0; const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1;
const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4; const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4;
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD = 5; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD = 6; const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES
const uint16_t CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD = 7;
const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0; const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2; const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA384_P384 = 2;
@ -82,12 +74,11 @@ namespace data
const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5; const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5;
const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6; const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6;
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7; const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7;
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // since openssl 3.0.0 const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // not implemented
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9; const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9;
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB
const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only
const uint16_t SIGNING_KEY_TYPE_MLDSA44 = 12;
typedef uint16_t SigningKeyType; typedef uint16_t SigningKeyType;
typedef uint16_t CryptoKeyType; typedef uint16_t CryptoKeyType;
@ -108,7 +99,7 @@ namespace data
size_t FromBuffer (const uint8_t * buf, size_t len); size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const; size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(std::string_view s); size_t FromBase64(const std::string& s);
std::string ToBase64 () const; std::string ToBase64 () const;
const Identity& GetStandardIdentity () const { return m_StandardIdentity; }; const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
@ -142,7 +133,7 @@ namespace data
IdentHash m_IdentHash; IdentHash m_IdentHash;
std::unique_ptr<i2p::crypto::Verifier> m_Verifier; std::unique_ptr<i2p::crypto::Verifier> m_Verifier;
size_t m_ExtendedLen; size_t m_ExtendedLen;
uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; // TODO: support PQ keys uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE];
}; };
size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer size_t GetIdentityBufferLen (const uint8_t * buf, size_t len); // return actual identity length in buffer
@ -171,7 +162,7 @@ namespace data
size_t FromBuffer (const uint8_t * buf, size_t len); size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const; size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(std::string_view s); size_t FromBase64(const std::string& s);
std::string ToBase64 () const; std::string ToBase64 () const;
std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const; std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -14,7 +14,6 @@
#include "Timestamp.h" #include "Timestamp.h"
#include "NetDb.hpp" #include "NetDb.hpp"
#include "Tunnel.h" #include "Tunnel.h"
#include "CryptoKey.h"
#include "LeaseSet.h" #include "LeaseSet.h"
namespace i2p namespace i2p
@ -400,7 +399,6 @@ namespace data
offset += propertiesLen; // skip for now. TODO: implement properties offset += propertiesLen; // skip for now. TODO: implement properties
// key sections // key sections
CryptoKeyType preferredKeyType = m_EncryptionType; CryptoKeyType preferredKeyType = m_EncryptionType;
m_EncryptionType = 0;
bool preferredKeyFound = false; bool preferredKeyFound = false;
if (offset + 1 > len) return 0; if (offset + 1 > len) return 0;
int numKeySections = buf[offset]; offset++; int numKeySections = buf[offset]; offset++;
@ -412,22 +410,14 @@ namespace data
if (offset + encryptionKeyLen > len) return 0; if (offset + encryptionKeyLen > len) return 0;
if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only
{ {
// we pick max key type if preferred not found // we pick first valid key if preferred not found
#if !OPENSSL_PQ auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset);
if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported if (encryptor && (!m_Encryptor || keyType == preferredKeyType))
#endif {
{ m_Encryptor = encryptor; // TODO: atomic
if (keyType == preferredKeyType || !m_Encryptor || keyType > m_EncryptionType) m_EncryptionType = keyType;
{ if (keyType == preferredKeyType) preferredKeyFound = true;
auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset); }
if (encryptor)
{
m_Encryptor = encryptor; // TODO: atomic
m_EncryptionType = keyType;
if (keyType == preferredKeyType) preferredKeyFound = true;
}
}
}
} }
offset += encryptionKeyLen; offset += encryptionKeyLen;
} }
@ -848,7 +838,7 @@ namespace data
} }
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const EncryptionKeys& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels, const KeySections& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted): bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted):
LocalLeaseSet (keys.GetPublic (), nullptr, 0) LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{ {
@ -858,7 +848,7 @@ namespace data
if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES;
size_t keySectionsLen = 0; size_t keySectionsLen = 0;
for (const auto& it: encryptionKeys) for (const auto& it: encryptionKeys)
keySectionsLen += 2/*key type*/ + 2/*key len*/ + it->pub.size()/*key*/; keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*key*/;
m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ + m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ +
1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen (); 1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen ();
uint16_t flags = 0; uint16_t flags = 0;
@ -893,9 +883,9 @@ namespace data
m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key
for (const auto& it: encryptionKeys) for (const auto& it: encryptionKeys)
{ {
htobe16buf (m_Buffer + offset, it->keyType); offset += 2; // key type htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type
htobe16buf (m_Buffer + offset, it->pub.size()); offset += 2; // key len htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len
memcpy (m_Buffer + offset, it->pub.data(), it->pub.size()); offset += it->pub.size(); // key memcpy (m_Buffer + offset, it.encryptionPublicKey, it.keyLen); offset += it.keyLen; // key
} }
// leases // leases
uint32_t expirationTime = 0; // in seconds uint32_t expirationTime = 0; // in seconds

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -12,14 +12,12 @@
#include <inttypes.h> #include <inttypes.h>
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <list>
#include <set> #include <set>
#include <memory> #include <memory>
#include "Identity.h" #include "Identity.h"
#include "Timestamp.h" #include "Timestamp.h"
#include "I2PEndian.h" #include "I2PEndian.h"
#include "Blinding.h" #include "Blinding.h"
#include "CryptoKey.h"
namespace i2p namespace i2p
{ {
@ -152,8 +150,8 @@ namespace data
public: public:
LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update
LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL);
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // store type 5, called from local netdb only LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); // store type 5, called from local netdb only
uint8_t GetStoreType () const { return m_StoreType; }; uint8_t GetStoreType () const { return m_StoreType; };
uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; }; uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; };
bool IsPublic () const { return m_IsPublic; }; bool IsPublic () const { return m_IsPublic; };
@ -249,10 +247,15 @@ namespace data
{ {
public: public:
typedef std::list<std::shared_ptr<const i2p::crypto::LocalEncryptionKey> > EncryptionKeys; struct KeySection
{
uint16_t keyType, keyLen;
const uint8_t * encryptionPublicKey;
};
typedef std::vector<KeySection> KeySections;
LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const EncryptionKeys& encryptionKeys, const KeySections& encryptionKeys,
const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, uint64_t publishedTimestamp, bool isPublic, uint64_t publishedTimestamp,
bool isPublishedEncrypted = false); bool isPublishedEncrypted = false);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -145,12 +145,10 @@ namespace transport
// 2 bytes reserved // 2 bytes reserved
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds
// 4 bytes reserved // 4 bytes reserved
// encrypt options // sign and encrypt options, use m_H as AD
if (!Encrypt (options, m_SessionRequestBuffer + 32, 16)) uint8_t nonce[12];
{ memset (nonce, 0, 12); // set nonce to zero
LogPrint (eLogWarning, "NTCP2: SessionRequest failed to encrypt options"); i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt
return false;
}
return true; return true;
} }
@ -169,16 +167,14 @@ namespace transport
memset (options, 0, 16); memset (options, 0, 16);
htobe16buf (options + 2, paddingLen); // padLen htobe16buf (options + 2, paddingLen); // padLen
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds
// encrypt options // sign and encrypt options, use m_H as AD
if (!Encrypt (options, m_SessionCreatedBuffer + 32, 16)) uint8_t nonce[12];
{ memset (nonce, 0, 12); // set nonce to zero
LogPrint (eLogWarning, "NTCP2: SessionCreated failed to encrypt options"); i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt
return false;
}
return true; return true;
} }
bool NTCP2Establisher::CreateSessionConfirmedMessagePart1 () void NTCP2Establisher::CreateSessionConfirmedMessagePart1 (const uint8_t * nonce)
{ {
// update AD // update AD
MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload
@ -186,28 +182,19 @@ namespace transport
if (paddingLength > 0) if (paddingLength > 0)
MixHash (m_SessionCreatedBuffer + 64, paddingLength); MixHash (m_SessionCreatedBuffer + 64, paddingLength);
// part1 48 bytes, n = 1 // part1 48 bytes
if (!Encrypt (i2p::context.GetNTCP2StaticPublicKey (), m_SessionConfirmedBuffer, 32)) i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part1");
return false;
}
return true;
} }
bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 () bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce)
{ {
// part 2 // part 2
// update AD again // update AD again
MixHash (m_SessionConfirmedBuffer, 48); MixHash (m_SessionConfirmedBuffer, 48);
// encrypt m3p2, it must be filled in SessionRequest // encrypt m3p2, it must be filled in SessionRequest
if (!KDF3Alice ()) return false; // MixKey, n = 0 if (!KDF3Alice ()) return false;
uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; uint8_t * m3p2 = m_SessionConfirmedBuffer + 48;
if (!Encrypt (m3p2, m3p2, m3p2Len - 16)) i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part2");
return false;
}
// update h again // update h again
MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext) MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext)
return true; return true;
@ -227,9 +214,10 @@ namespace transport
LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed"); LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed");
return false; return false;
} }
// verify MAC and decrypt options block (32 bytes) // verify MAC and decrypt options block (32 bytes), use m_H as AD
uint8_t options[16]; uint8_t nonce[12], options[16];
if (Decrypt (m_SessionRequestBuffer + 32, options, 16)) memset (nonce, 0, 12); // set nonce to zero
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, GetH (), 32, GetK (), nonce, options, 16, false)) // decrypt
{ {
// options // options
if (options[0] && options[0] != i2p::context.GetNetID ()) if (options[0] && options[0] != i2p::context.GetNetID ())
@ -286,7 +274,9 @@ namespace transport
} }
// decrypt and verify MAC // decrypt and verify MAC
uint8_t payload[16]; uint8_t payload[16];
if (Decrypt (m_SessionCreatedBuffer + 32, payload, 16)) uint8_t nonce[12];
memset (nonce, 0, 12); // set nonce to zero
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionCreatedBuffer + 32, 16, GetH (), 32, GetK (), nonce, payload, 16, false)) // decrypt
{ {
// options // options
paddingLen = bufbe16toh(payload + 2); paddingLen = bufbe16toh(payload + 2);
@ -307,7 +297,7 @@ namespace transport
return true; return true;
} }
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 () bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce)
{ {
// update AD // update AD
MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload
@ -315,8 +305,7 @@ namespace transport
if (paddingLength > 0) if (paddingLength > 0)
MixHash (m_SessionCreatedBuffer + 64, paddingLength); MixHash (m_SessionCreatedBuffer + 64, paddingLength);
// decrypt S, n = 1 if (!i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer, 32, GetH (), 32, GetK (), nonce, m_RemoteStaticKey, 32, false)) // decrypt S
if (!Decrypt (m_SessionConfirmedBuffer, m_RemoteStaticKey, 32))
{ {
LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed "); LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed ");
return false; return false;
@ -324,17 +313,17 @@ namespace transport
return true; return true;
} }
bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf) bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf)
{ {
// update AD again // update AD again
MixHash (m_SessionConfirmedBuffer, 48); MixHash (m_SessionConfirmedBuffer, 48);
if (!KDF3Bob ()) // MixKey, n = 0 if (!KDF3Bob ())
{ {
LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed"); LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed");
return false; return false;
} }
if (Decrypt (m_SessionConfirmedBuffer + 48, m3p2Buf, m3p2Len - 16)) if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt
// calculate new h again for KDF data // calculate new h again for KDF data
MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext)
else else
@ -668,12 +657,11 @@ namespace transport
void NTCP2Session::SendSessionConfirmed () void NTCP2Session::SendSessionConfirmed ()
{ {
if (!m_Establisher->CreateSessionConfirmedMessagePart1 ()) uint8_t nonce[12];
{ CreateNonce (1, nonce); // set nonce to 1
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); m_Establisher->CreateSessionConfirmedMessagePart1 (nonce);
return; memset (nonce, 0, 12); // set nonce back to 0
} if (!m_Establisher->CreateSessionConfirmedMessagePart2 (nonce))
if (!m_Establisher->CreateSessionConfirmedMessagePart2 ())
{ {
LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed"); LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
@ -752,11 +740,14 @@ namespace transport
// run on establisher thread // run on establisher thread
LogPrint (eLogDebug, "NTCP2: SessionConfirmed received"); LogPrint (eLogDebug, "NTCP2: SessionConfirmed received");
// part 1 // part 1
if (m_Establisher->ProcessSessionConfirmedMessagePart1 ()) uint8_t nonce[12];
CreateNonce (1, nonce);
if (m_Establisher->ProcessSessionConfirmedMessagePart1 (nonce))
{ {
// part 2 // part 2
auto buf = std::make_shared<std::vector<uint8_t> > (m_Establisher->m3p2Len - 16); // -MAC auto buf = std::make_shared<std::vector<uint8_t> > (m_Establisher->m3p2Len - 16); // -MAC
if (m_Establisher->ProcessSessionConfirmedMessagePart2 (buf->data ())) // TODO:handle in establisher thread memset (nonce, 0, 12); // set nonce to 0 again
if (m_Establisher->ProcessSessionConfirmedMessagePart2 (nonce, buf->data ())) // TODO:handle in establisher thread
{ {
// payload // payload
// RI block must be first // RI block must be first
@ -828,20 +819,15 @@ namespace transport
Terminate (); Terminate ();
return; return;
} }
std::shared_ptr<i2p::data::RouterProfile> profile; // not null if older
bool isOlder = false;
if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
{ {
// received RouterInfo is older than one in netdb // received RouterInfo is older than one in netdb
isOlder = true; profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile
if (ri1->HasProfile ()) if (profile && profile->IsDuplicated ())
{ {
auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile SendTerminationAndTerminate (eNTCP2Banned);
if (profile && profile->IsDuplicated ()) return;
{
SendTerminationAndTerminate (eNTCP2Banned);
return;
}
} }
} }
@ -858,12 +844,8 @@ namespace transport
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address
{ {
if (isOlder) // older router? if (profile) // older router?
i2p::data::UpdateRouterProfile (ri1->GetIdentHash (), profile->Duplicated (); // mark router as duplicated in profile
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Duplicated (); // mark router as duplicated in profile
});
else else
LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
SendTerminationAndTerminate (eNTCP2Banned); SendTerminationAndTerminate (eNTCP2Banned);

View file

@ -91,6 +91,7 @@ namespace transport
const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob
uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set
const uint8_t * GetK () const { return m_CK + 32; };
const uint8_t * GetCK () const { return m_CK; }; const uint8_t * GetCK () const { return m_CK; };
const uint8_t * GetH () const { return m_H; }; const uint8_t * GetH () const { return m_H; };
@ -107,13 +108,13 @@ namespace transport
bool CreateSessionRequestMessage (std::mt19937& rng); bool CreateSessionRequestMessage (std::mt19937& rng);
bool CreateSessionCreatedMessage (std::mt19937& rng); bool CreateSessionCreatedMessage (std::mt19937& rng);
bool CreateSessionConfirmedMessagePart1 (); void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce);
bool CreateSessionConfirmedMessagePart2 (); bool CreateSessionConfirmedMessagePart2 (const uint8_t * nonce);
bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew); bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew);
bool ProcessSessionCreatedMessage (uint16_t& paddingLen); bool ProcessSessionCreatedMessage (uint16_t& paddingLen);
bool ProcessSessionConfirmedMessagePart1 (); bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce);
bool ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf); bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf);
std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys; std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 uint8_t m_RemoteEphemeralPublicKey[32]; // x25519

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,6 +10,7 @@
#include <fstream> #include <fstream>
#include <vector> #include <vector>
#include <map> #include <map>
#include <random>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <stdexcept> #include <stdexcept>
@ -39,7 +40,7 @@ namespace data
NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr),
m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true),
m_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) m_LastExploratorySelectionUpdateTime (0)
{ {
} }
@ -118,9 +119,8 @@ namespace data
i2p::util::SetThreadName("NetDB"); i2p::util::SetThreadName("NetDB");
uint64_t lastManage = 0; uint64_t lastManage = 0;
uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), lastObsoleteProfilesCleanup = lastProfilesCleanup;
lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup; int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0;
int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0;
std::list<std::shared_ptr<const I2NPMessage> > msgs; std::list<std::shared_ptr<const I2NPMessage> > msgs;
while (m_IsRunning) while (m_IsRunning)
@ -181,7 +181,7 @@ namespace data
LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk"); LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk");
} }
lastProfilesCleanup = mts; lastProfilesCleanup = mts;
profilesCleanupVariance = m_Rng () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE; profilesCleanupVariance = rand () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE;
} }
if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000) if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000)
@ -197,20 +197,7 @@ namespace data
else else
LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk"); LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk");
lastObsoleteProfilesCleanup = mts; lastObsoleteProfilesCleanup = mts;
obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE; obsoleteProfilesCleanVariance = rand () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE;
}
if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance)
{
bool isApplying = m_ApplyingProfileUpdates.valid ();
if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
{
m_ApplyingProfileUpdates.get ();
isApplying = false;
}
if (!isApplying)
m_ApplyingProfileUpdates = i2p::data::FlushPostponedRouterProfileUpdates ();
lastApplyingProfileUpdates = mts;
applyingProfileUpdatesVariance = m_Rng () % i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE;
} }
} }
catch (std::exception& ex) catch (std::exception& ex)
@ -294,7 +281,6 @@ namespace data
} }
else else
{ {
r->CancelBufferToDelete (); // since an update received
if (CheckLogLevel (eLogDebug)) if (CheckLogLevel (eLogDebug))
LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64());
updated = false; updated = false;
@ -571,7 +557,7 @@ namespace data
while(n > 0) while(n > 0)
{ {
std::lock_guard<std::mutex> lock(m_RouterInfosMutex); std::lock_guard<std::mutex> lock(m_RouterInfosMutex);
uint32_t idx = m_Rng () % m_RouterInfos.size (); uint32_t idx = rand () % m_RouterInfos.size ();
uint32_t i = 0; uint32_t i = 0;
for (const auto & it : m_RouterInfos) { for (const auto & it : m_RouterInfos) {
if(i >= idx) // are we at the random start point? if(i >= idx) // are we at the random start point?
@ -674,21 +660,16 @@ namespace data
{ {
std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
buffer = r->CopyBuffer (); buffer = r->CopyBuffer ();
}
if (!i2p::transport::transports.IsConnected (ident))
r->ScheduleBufferToDelete (); r->ScheduleBufferToDelete ();
}
if (buffer) if (buffer)
saveToDisk.emplace_back(ident.ToBase64 (), buffer); saveToDisk.push_back(std::make_pair(ident.ToBase64 (), buffer));
} }
r->SetUpdated (false); r->SetUpdated (false);
updatedCount++; updatedCount++;
continue; continue;
} }
else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) if (r->GetProfile ()->IsUnreachable ())
// since update was long time ago we assume that router is not connected anymore
r->ScheduleBufferToDelete ();
if (r->HasProfile () && r->GetProfile ()->IsUnreachable ())
r->SetUnreachable (true); r->SetUnreachable (true);
// make router reachable back if too few routers or floodfills // make router reachable back if too few routers or floodfills
if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline || if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline ||
@ -715,16 +696,15 @@ namespace data
r->SetUnreachable (true); r->SetUnreachable (true);
} }
} }
// make router reachable back if connected now or trusted router // make router reachable back if connected now
if (r->IsUnreachable () && (i2p::transport::transports.IsConnected (ident) || if (r->IsUnreachable () && i2p::transport::transports.IsConnected (ident))
i2p::transport::transports.IsTrustedRouter (ident)))
r->SetUnreachable (false); r->SetUnreachable (false);
if (r->IsUnreachable ()) if (r->IsUnreachable ())
{ {
if (r->IsFloodfill ()) deletedFloodfillsCount++; if (r->IsFloodfill ()) deletedFloodfillsCount++;
// delete RI file // delete RI file
removeFromDisk.emplace_back (ident.ToBase64()); removeFromDisk.push_back (ident.ToBase64());
deletedCount++; deletedCount++;
if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false;
} }
@ -951,13 +931,14 @@ namespace data
LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored"); LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored");
return; return;
} }
std::string key; char key[48];
if (CheckLogLevel (eLogInfo)) int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
key = i2p::data::ByteStreamToBase64 (buf, 32); key[l] = 0;
IdentHash replyIdent(buf + 32); IdentHash replyIdent(buf + 32);
uint8_t flag = buf[64]; uint8_t flag = buf[64];
LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag); LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " received flags=", (int)flag);
uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK;
const uint8_t * excluded = buf + 65; const uint8_t * excluded = buf + 65;
@ -1350,7 +1331,7 @@ namespace data
if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE) if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE)
{ {
std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection), std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection),
NETDB_MAX_EXPLORATORY_SELECTION_SIZE, m_Rng); NETDB_MAX_EXPLORATORY_SELECTION_SIZE, std::mt19937(ts));
} }
else else
std::swap (m_ExploratorySelection, eligible); std::swap (m_ExploratorySelection, eligible);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -16,7 +16,6 @@
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <future> #include <future>
#include <random>
#include "Base.h" #include "Base.h"
#include "Gzip.h" #include "Gzip.h"
@ -53,7 +52,6 @@ namespace data
const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 58); // 0.9.58 const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 58); // 0.9.58
const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 59); // 0.9.59 const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 59); // 0.9.59
const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
const int NETDB_MIN_PEER_TEST_VERSION = MAKE_VERSION_NUMBER(0, 9, 62); // 0.9.62
const size_t NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES = 16; const size_t NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES = 16;
const size_t NETDB_MAX_EXPLORATORY_SELECTION_SIZE = 500; const size_t NETDB_MAX_EXPLORATORY_SELECTION_SIZE = 500;
const int NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL = 82; // in seconds. for floodfill const int NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL = 82; // in seconds. for floodfill
@ -187,11 +185,10 @@ namespace data
std::shared_ptr<NetDbRequests> m_Requests; std::shared_ptr<NetDbRequests> m_Requests;
bool m_PersistProfiles; bool m_PersistProfiles;
std::future<void> m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters; std::future<void> m_SavingProfiles, m_DeletingProfiles, m_PersistingRouters;
std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection; std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection;
uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds
std::mt19937 m_Rng;
i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool; i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool;
i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool; i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -20,10 +20,8 @@ namespace i2p
namespace data namespace data
{ {
RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct): RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct):
m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), m_IsActive (true),
m_IsActive (true), m_IsSentDirectly (false), m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()), m_LastRequestTime (0), m_NumAttempts (0)
m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()),
m_LastRequestTime (0), m_NumAttempts (0)
{ {
if (i2p::context.IsFloodfill ()) if (i2p::context.IsFloodfill ())
m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill
@ -48,7 +46,6 @@ namespace data
m_ExcludedPeers.insert (router->GetIdentHash ()); m_ExcludedPeers.insert (router->GetIdentHash ());
m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
m_NumAttempts++; m_NumAttempts++;
m_IsSentDirectly = false;
return msg; return msg;
} }
@ -59,7 +56,6 @@ namespace data
m_ExcludedPeers.insert (floodfill); m_ExcludedPeers.insert (floodfill);
m_NumAttempts++; m_NumAttempts++;
m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
m_IsSentDirectly = true;
return msg; return msg;
} }
@ -226,8 +222,7 @@ namespace data
bool done = false; bool done = false;
if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME) if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME)
{ {
if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME)) if (ts > dest->GetLastRequestTime () + MIN_REQUEST_TIME) // try next floodfill if no response after min interval
// try next floodfill if no response after min interval
done = !SendNextRequest (dest); done = !SendNextRequest (dest);
} }
else // request is expired else // request is expired
@ -360,12 +355,11 @@ namespace data
void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg) void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
{ {
const uint8_t * buf = msg->GetPayload (); const uint8_t * buf = msg->GetPayload ();
std::string key; char key[48];
int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
key[l] = 0;
size_t num = buf[32]; // num size_t num = buf[32]; // num
if (CheckLogLevel (eLogInfo))
key = i2p::data::ByteStreamToBase64 (buf, 32);
LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num); LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num);
IdentHash ident (buf); IdentHash ident (buf);
bool isExploratory = false; bool isExploratory = false;
auto dest = FindRequest (ident); auto dest = FindRequest (ident);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -28,7 +28,6 @@ namespace data
const uint64_t MANAGE_REQUESTS_INTERVAL_VARIANCE = 300; // in milliseconds const uint64_t MANAGE_REQUESTS_INTERVAL_VARIANCE = 300; // in milliseconds
const uint64_t MIN_REQUEST_TIME = 1200; // in milliseconds const uint64_t MIN_REQUEST_TIME = 1200; // in milliseconds
const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL + MANAGE_REQUESTS_INTERVAL_VARIANCE); const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL + MANAGE_REQUESTS_INTERVAL_VARIANCE);
const uint64_t MIN_DIRECT_REQUEST_TIME = 600; // in milliseconds
const uint64_t EXPLORATORY_REQUEST_INTERVAL = 55; // in seconds const uint64_t EXPLORATORY_REQUEST_INTERVAL = 55; // in seconds
const uint64_t EXPLORATORY_REQUEST_INTERVAL_VARIANCE = 170; // in seconds const uint64_t EXPLORATORY_REQUEST_INTERVAL_VARIANCE = 170; // in seconds
const uint64_t DISCOVERED_REQUEST_INTERVAL = 360; // in milliseconds const uint64_t DISCOVERED_REQUEST_INTERVAL = 360; // in milliseconds
@ -53,7 +52,6 @@ namespace data
bool IsExploratory () const { return m_IsExploratory; }; bool IsExploratory () const { return m_IsExploratory; };
bool IsDirect () const { return m_IsDirect; }; bool IsDirect () const { return m_IsDirect; };
bool IsActive () const { return m_IsActive; }; bool IsActive () const { return m_IsActive; };
bool IsSentDirectly () const { return m_IsSentDirectly; };
bool IsExcluded (const IdentHash& ident) const; bool IsExcluded (const IdentHash& ident) const;
uint64_t GetCreationTime () const { return m_CreationTime; }; uint64_t GetCreationTime () const { return m_CreationTime; };
uint64_t GetLastRequestTime () const { return m_LastRequestTime; }; uint64_t GetLastRequestTime () const { return m_LastRequestTime; };
@ -72,7 +70,7 @@ namespace data
private: private:
IdentHash m_Destination; IdentHash m_Destination;
bool m_IsExploratory, m_IsDirect, m_IsActive, m_IsSentDirectly; bool m_IsExploratory, m_IsDirect, m_IsActive;
std::unordered_set<IdentHash> m_ExcludedPeers; std::unordered_set<IdentHash> m_ExcludedPeers;
uint64_t m_CreationTime, m_LastRequestTime; // in milliseconds uint64_t m_CreationTime, m_LastRequestTime; // in milliseconds
std::list<RequestComplete> m_RequestComplete; std::list<RequestComplete> m_RequestComplete;

View file

@ -1,160 +0,0 @@
/*
* Copyright (c) 2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include "Log.h"
#include "PostQuantum.h"
#if OPENSSL_PQ
#include <openssl/param_build.h>
#include <openssl/core_names.h>
namespace i2p
{
namespace crypto
{
MLKEMKeys::MLKEMKeys (MLKEMTypes type):
m_Name (std::get<0>(MLKEMS[type])), m_KeyLen (std::get<1>(MLKEMS[type])),
m_CTLen (std::get<2>(MLKEMS[type])), m_Pkey (nullptr)
{
}
MLKEMKeys::~MLKEMKeys ()
{
if (m_Pkey) EVP_PKEY_free (m_Pkey);
}
void MLKEMKeys::GenerateKeys ()
{
if (m_Pkey) EVP_PKEY_free (m_Pkey);
m_Pkey = EVP_PKEY_Q_keygen(NULL, NULL, m_Name.c_str ());
}
void MLKEMKeys::GetPublicKey (uint8_t * pub) const
{
if (m_Pkey)
{
size_t len = m_KeyLen;
EVP_PKEY_get_octet_string_param (m_Pkey, OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len);
}
}
void MLKEMKeys::SetPublicKey (const uint8_t * pub)
{
if (m_Pkey)
{
EVP_PKEY_free (m_Pkey);
m_Pkey = nullptr;
}
OSSL_PARAM params[] =
{
OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)pub, m_KeyLen),
OSSL_PARAM_END
};
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, m_Name.c_str (), NULL);
if (ctx)
{
EVP_PKEY_fromdata_init (ctx);
EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLKEM can't create PKEY context");
}
void MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared)
{
if (!m_Pkey) return;
auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (ctx)
{
EVP_PKEY_encapsulate_init (ctx, NULL);
size_t len = m_CTLen, sharedLen = 32;
EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLKEM can't create PKEY context");
}
void MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared)
{
if (!m_Pkey) return;
auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (ctx)
{
EVP_PKEY_decapsulate_init (ctx, NULL);
size_t sharedLen = 32;
EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLKEM can't create PKEY context");
}
std::unique_ptr<MLKEMKeys> CreateMLKEMKeys (i2p::data::CryptoKeyType type)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return nullptr;
return std::make_unique<MLKEMKeys>((MLKEMTypes)(type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1));
}
static constexpr std::array<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32> >, 3> NoiseIKInitMLKEMKeys =
{
std::make_pair
(
std::array<uint8_t, 32>
{
0xb0, 0x8f, 0xb1, 0x73, 0x92, 0x66, 0xc9, 0x90, 0x45, 0x7f, 0xdd, 0xc6, 0x4e, 0x55, 0x40, 0xd8,
0x0a, 0x37, 0x99, 0x06, 0x92, 0x2a, 0x78, 0xc4, 0xb1, 0xef, 0x86, 0x06, 0xd0, 0x15, 0x9f, 0x4d
}, // SHA256("Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256")
std::array<uint8_t, 32>
{
0x95, 0x8d, 0xf6, 0x6c, 0x95, 0xce, 0xa9, 0xf7, 0x42, 0xfc, 0xfa, 0x62, 0x71, 0x36, 0x1e, 0xa7,
0xdc, 0x7a, 0xc0, 0x75, 0x01, 0xcf, 0xf9, 0xfc, 0x9f, 0xdb, 0x4c, 0x68, 0x3a, 0x53, 0x49, 0xeb
} // SHA256 (first)
),
std::make_pair
(
std::array<uint8_t, 32>
{
0x36, 0x03, 0x90, 0x2d, 0xf9, 0xa2, 0x2a, 0x5e, 0xc9, 0x3d, 0xdb, 0x8f, 0xa8, 0x1b, 0xdb, 0x4b,
0xae, 0x9d, 0x93, 0x9c, 0xdf, 0xaf, 0xde, 0x55, 0x49, 0x13, 0xfe, 0x98, 0xf8, 0x4a, 0xd4, 0xbd
}, // SHA256("Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256")
std::array<uint8_t, 32>
{
0x15, 0x44, 0x89, 0xbf, 0x30, 0xf0, 0xc9, 0x77, 0x66, 0x10, 0xcb, 0xb1, 0x57, 0x3f, 0xab, 0x68,
0x79, 0x57, 0x39, 0x57, 0x0a, 0xe7, 0xc0, 0x31, 0x8a, 0xa2, 0x96, 0xef, 0xbf, 0xa9, 0x6a, 0xbb
} // SHA256 (first)
),
std::make_pair
(
std::array<uint8_t, 32>
{
0x86, 0xa5, 0x36, 0x44, 0xc6, 0x12, 0xd5, 0x71, 0xa1, 0x2d, 0xd8, 0xb6, 0x0a, 0x00, 0x9f, 0x2c,
0x1a, 0xa8, 0x7d, 0x22, 0xa4, 0xff, 0x2b, 0xcd, 0x61, 0x34, 0x97, 0x6d, 0xa1, 0x49, 0xeb, 0x4a
}, // SHA256("Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256")
std::array<uint8_t, 32>
{
0x42, 0x0d, 0xc2, 0x1c, 0x7b, 0x18, 0x61, 0xb7, 0x4a, 0x04, 0x3d, 0xae, 0x0f, 0xdc, 0xf2, 0x71,
0xb9, 0xba, 0x19, 0xbb, 0xbd, 0x5f, 0xd4, 0x9c, 0x3f, 0x4b, 0x01, 0xed, 0x6d, 0x13, 0x1d, 0xa2
} // SHA256 (first)
)
};
void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)NoiseIKInitMLKEMKeys.size ()) return;
auto ind = type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1;
state.Init (NoiseIKInitMLKEMKeys[ind].first.data(), NoiseIKInitMLKEMKeys[ind].second.data(), pub);
}
}
}
#endif

View file

@ -1,88 +0,0 @@
/*
* Copyright (c) 2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef POST_QUANTUM_H__
#define POST_QUANTUM_H__
#include <memory>
#include <string_view>
#include <array>
#include <tuple>
#include "Crypto.h"
#include "Identity.h"
#if OPENSSL_PQ
namespace i2p
{
namespace crypto
{
enum MLKEMTypes
{
eMLKEM512 = 0,
eMLKEM768,
eMLKEM1024
};
constexpr size_t MLKEM512_KEY_LENGTH = 800;
constexpr size_t MLKEM512_CIPHER_TEXT_LENGTH = 768;
constexpr size_t MLKEM768_KEY_LENGTH = 1184;
constexpr size_t MLKEM768_CIPHER_TEXT_LENGTH = 1088;
constexpr size_t MLKEM1024_KEY_LENGTH = 1568;
constexpr size_t MLKEM1024_CIPHER_TEXT_LENGTH = 1568;
constexpr std::array<std::tuple<std::string_view, size_t, size_t>, 3> MLKEMS =
{
std::make_tuple ("ML-KEM-512", MLKEM512_KEY_LENGTH, MLKEM512_CIPHER_TEXT_LENGTH),
std::make_tuple ("ML-KEM-768", MLKEM768_KEY_LENGTH, MLKEM768_CIPHER_TEXT_LENGTH),
std::make_tuple ("ML-KEM-1024", MLKEM1024_KEY_LENGTH, MLKEM1024_CIPHER_TEXT_LENGTH)
};
constexpr size_t GetMLKEMPublicKeyLen (i2p::data::CryptoKeyType type)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
return std::get<1>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
}
constexpr size_t GetMLKEMCipherTextLen (i2p::data::CryptoKeyType type)
{
if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
return std::get<2>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
}
class MLKEMKeys
{
public:
MLKEMKeys (MLKEMTypes type);
~MLKEMKeys ();
void GenerateKeys ();
void GetPublicKey (uint8_t * pub) const;
void SetPublicKey (const uint8_t * pub);
void Encaps (uint8_t * ciphertext, uint8_t * shared);
void Decaps (const uint8_t * ciphertext, uint8_t * shared);
private:
const std::string m_Name;
const size_t m_KeyLen, m_CTLen;
EVP_PKEY * m_Pkey;
};
std::unique_ptr<MLKEMKeys> CreateMLKEMKeys (i2p::data::CryptoKeyType type);
void InitNoiseIKStateMLKEM (NoiseSymmetricState& state, i2p::data::CryptoKeyType type, const uint8_t * pub); // Noise_IK (ratchets) PQ ML-KEM5
}
}
#endif
#endif

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -27,15 +27,13 @@ namespace data
static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > g_Profiles; static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > g_Profiles;
static std::mutex g_ProfilesMutex; static std::mutex g_ProfilesMutex;
static std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > g_PostponedUpdates;
static std::mutex g_PostponedUpdatesMutex;
RouterProfile::RouterProfile (): RouterProfile::RouterProfile ():
m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0), m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0),
m_LastUpdateTime (i2p::util::GetSecondsSinceEpoch ()), m_LastAccessTime (0), m_LastUpdateTime (i2p::util::GetSecondsSinceEpoch ()),
m_LastPersistTime (0), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
m_NumTunnelsNonReplied (0),m_NumTimesTaken (0), m_NumTimesRejected (0), m_NumTimesTaken (0), m_NumTimesRejected (0), m_HasConnected (false),
m_HasConnected (false), m_IsDuplicated (false) m_IsDuplicated (false)
{ {
} }
@ -80,7 +78,6 @@ namespace data
void RouterProfile::Load (const IdentHash& identHash) void RouterProfile::Load (const IdentHash& identHash)
{ {
m_IsUpdated = false;
std::string ident = identHash.ToBase64 (); std::string ident = identHash.ToBase64 ();
std::string path = g_ProfilesStorage.Path(ident); std::string path = g_ProfilesStorage.Path(ident);
boost::property_tree::ptree pt; boost::property_tree::ptree pt;
@ -258,42 +255,30 @@ namespace data
std::unique_lock<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash); auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ()) if (it != g_Profiles.end ())
{
it->second->SetLastAccessTime (i2p::util::GetSecondsSinceEpoch ());
return it->second; return it->second;
}
} }
auto profile = netdb.NewRouterProfile (); auto profile = netdb.NewRouterProfile ();
profile->Load (identHash); // if possible profile->Load (identHash); // if possible
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
g_Profiles.emplace (identHash, profile); g_Profiles.emplace (identHash, profile);
return profile; return profile;
} }
bool IsRouterBanned (const IdentHash& identHash) bool IsRouterBanned (const IdentHash& identHash)
{ {
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash); auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ()) if (it != g_Profiles.end ())
return it->second->IsUnreachable (); return it->second->IsUnreachable ();
return false; return false;
} }
bool IsRouterDuplicated (const IdentHash& identHash)
{
std::lock_guard<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
return it->second->IsDuplicated ();
return false;
}
void InitProfilesStorage () void InitProfilesStorage ()
{ {
g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
} }
static void SaveProfilesToDisk (std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > >&& profiles) static void SaveProfilesToDisk (std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > >&& profiles)
{ {
for (auto& it: profiles) for (auto& it: profiles)
@ -305,17 +290,15 @@ namespace data
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > > tmp; std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > > tmp;
{ {
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
{ {
if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL) if (ts - it->second->GetLastUpdateTime () > PEER_PROFILE_PERSIST_INTERVAL)
{ {
tmp.push_back (*it); if (it->second->IsUpdated ())
it->second->SetLastPersistTime (ts); tmp.push_back (std::make_pair (it->first, it->second));
it->second->SetUpdated (false);
}
if (!it->second->IsUpdated () && ts > std::max (it->second->GetLastUpdateTime (), it->second->GetLastAccessTime ()) + PEER_PROFILE_PERSIST_INTERVAL)
it = g_Profiles.erase (it); it = g_Profiles.erase (it);
}
else else
it++; it++;
} }
@ -329,7 +312,7 @@ namespace data
{ {
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > tmp; std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > tmp;
{ {
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
std::swap (tmp, g_Profiles); std::swap (tmp, g_Profiles);
} }
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
@ -364,7 +347,7 @@ namespace data
{ {
{ {
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
{ {
if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT) if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT)
@ -376,47 +359,5 @@ namespace data
return std::async (std::launch::async, DeleteFilesFromDisk); return std::async (std::launch::async, DeleteFilesFromDisk);
} }
bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update)
{
if (!update) return true;
std::shared_ptr<RouterProfile> profile;
{
std::lock_guard<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
profile = it->second;
}
if (profile)
{
update (profile);
return true;
}
// postpone
std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
g_PostponedUpdates.emplace_back (identHash, update);
return false;
}
static void ApplyPostponedUpdates (std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > >&& updates)
{
for (const auto& [ident, update] : updates)
{
auto profile = GetRouterProfile (ident);
update (profile);
}
}
std::future<void> FlushPostponedRouterProfileUpdates ()
{
if (g_PostponedUpdates.empty ()) return std::future<void>();
std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > updates;
{
std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
g_PostponedUpdates.swap (updates);
}
return std::async (std::launch::async, ApplyPostponedUpdates, std::move (updates));
}
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,6 @@
#include <memory> #include <memory>
#include <future> #include <future>
#include <functional>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "Identity.h" #include "Identity.h"
@ -41,12 +40,10 @@ namespace data
const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes) const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes)
const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes) const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes)
const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours) const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours)
const int PEER_PROFILE_PERSIST_INTERVAL = 1320; // in seconds (22 minutes) const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes)
const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes) const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes)
const int PEER_PROFILE_USEFUL_THRESHOLD = 3; const int PEER_PROFILE_USEFUL_THRESHOLD = 3;
const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined
const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT = 2100; // in milliseconds
const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE = 500; // in milliseconds
class RouterProfile class RouterProfile
{ {
@ -70,11 +67,6 @@ namespace data
uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
bool IsUpdated () const { return m_IsUpdated; }; bool IsUpdated () const { return m_IsUpdated; };
void SetUpdated (bool updated) { m_IsUpdated = updated; }
uint64_t GetLastAccessTime () const { return m_LastAccessTime; };
void SetLastAccessTime (uint64_t ts) { m_LastAccessTime = ts; };
uint64_t GetLastPersistTime () const { return m_LastPersistTime; };
void SetLastPersistTime (uint64_t ts) { m_LastPersistTime = ts; };
bool IsUseful() const; bool IsUseful() const;
bool IsDuplicated () const { return m_IsDuplicated; }; bool IsDuplicated () const { return m_IsDuplicated; };
@ -96,8 +88,7 @@ namespace data
private: private:
bool m_IsUpdated; bool m_IsUpdated;
uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime, uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime; // in seconds
m_LastAccessTime, m_LastPersistTime; // in seconds
// participation // participation
uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsAgreed;
uint32_t m_NumTunnelsDeclined; uint32_t m_NumTunnelsDeclined;
@ -113,13 +104,10 @@ namespace data
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash); std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
bool IsRouterBanned (const IdentHash& identHash); // check only existing profiles bool IsRouterBanned (const IdentHash& identHash); // check only existing profiles
bool IsRouterDuplicated (const IdentHash& identHash); // check only existing profiles
void InitProfilesStorage (); void InitProfilesStorage ();
std::future<void> DeleteObsoleteProfiles (); std::future<void> DeleteObsoleteProfiles ();
void SaveProfiles (); void SaveProfiles ();
std::future<void> PersistProfiles (); std::future<void> PersistProfiles ();
bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update); // return true if updated immediately, and false if postponed
std::future<void> FlushPostponedRouterProfileUpdates ();
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -22,7 +22,6 @@
#include "ECIESX25519AEADRatchetSession.h" #include "ECIESX25519AEADRatchetSession.h"
#include "Transports.h" #include "Transports.h"
#include "Tunnel.h" #include "Tunnel.h"
#include "CryptoKey.h"
#include "RouterContext.h" #include "RouterContext.h"
namespace i2p namespace i2p
@ -34,14 +33,13 @@ namespace i2p
m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown), m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown),
m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone), m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone),
m_Testing (false), m_TestingV6 (false), m_NetID (I2PD_NET_ID), m_Testing (false), m_TestingV6 (false), m_NetID (I2PD_NET_ID),
m_PublishReplyToken (0), m_IsHiddenMode (false), m_PublishReplyToken (0), m_IsHiddenMode (false)
m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL), m_IsSaving (false)
{ {
} }
void RouterContext::Init () void RouterContext::Init ()
{ {
srand (m_Rng () % 1000); srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
m_StartupTime = i2p::util::GetMonotonicSeconds (); m_StartupTime = i2p::util::GetMonotonicSeconds ();
if (!Load ()) if (!Load ())
@ -78,7 +76,7 @@ namespace i2p
m_CongestionUpdateTimer->cancel (); m_CongestionUpdateTimer->cancel ();
m_Service->Stop (); m_Service->Stop ();
CleanUp (); // GarlicDestination CleanUp (); // GarlicDestination
} }
} }
std::shared_ptr<i2p::data::RouterInfo::Buffer> RouterContext::CopyRouterInfoBuffer () const std::shared_ptr<i2p::data::RouterInfo::Buffer> RouterContext::CopyRouterInfoBuffer () const
@ -255,36 +253,11 @@ namespace i2p
void RouterContext::UpdateRouterInfo () void RouterContext::UpdateRouterInfo ()
{ {
std::shared_ptr<i2p::data::RouterInfo::Buffer> buffer;
{ {
std::lock_guard<std::mutex> l(m_RouterInfoMutex); std::lock_guard<std::mutex> l(m_RouterInfoMutex);
m_RouterInfo.CreateBuffer (m_Keys); m_RouterInfo.CreateBuffer (m_Keys);
buffer = m_RouterInfo.CopyBuffer ();
} }
{ m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
// update save buffer to latest
std::lock_guard<std::mutex> l(m_SaveBufferMutex);
m_SaveBuffer = buffer;
}
bool isSaving = false;
if (m_IsSaving.compare_exchange_strong (isSaving, true)) // try to save only if not being saved
{
auto savingRouterInfo = std::async (std::launch::async, [this]()
{
std::shared_ptr<i2p::data::RouterInfo::Buffer> buffer;
while (m_SaveBuffer)
{
{
std::lock_guard<std::mutex> l(m_SaveBufferMutex);
buffer = m_SaveBuffer;
m_SaveBuffer = nullptr;
}
if (buffer)
i2p::data::RouterInfo::SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO), buffer);
}
m_IsSaving = false;
});
}
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
} }
@ -676,12 +649,12 @@ namespace i2p
void RouterContext::SetBandwidth (int limit) void RouterContext::SetBandwidth (int limit)
{ {
if (limit > (int)i2p::data::EXTRA_BANDWIDTH_LIMIT) { SetBandwidth('X'); } if (limit > 2000) { SetBandwidth('X'); }
else if (limit > (int)i2p::data::HIGH_BANDWIDTH_LIMIT) { SetBandwidth('P'); } else if (limit > 256) { SetBandwidth('P'); }
else if (limit > 128) { SetBandwidth('O'); } else if (limit > 128) { SetBandwidth('O'); }
else if (limit > 64) { SetBandwidth('N'); } else if (limit > 64) { SetBandwidth('N'); }
else if (limit > (int)i2p::data::LOW_BANDWIDTH_LIMIT) { SetBandwidth('M'); } else if (limit > 48) { SetBandwidth('M'); }
else if (limit > 12) { SetBandwidth('L'); } else if (limit > 12) { SetBandwidth('L'); }
else { SetBandwidth('K'); } else { SetBandwidth('K'); }
m_BandwidthLimit = limit; // set precise limit m_BandwidthLimit = limit; // set precise limit
} }
@ -1194,8 +1167,7 @@ namespace i2p
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len)));
} }
bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
{ {
if (typeID == eI2NPTunnelTest) if (typeID == eI2NPTunnelTest)
{ {
@ -1387,7 +1359,7 @@ namespace i2p
{ {
m_PublishTimer->cancel (); m_PublishTimer->cancel ();
m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_PUBLISH_INTERVAL + m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_PUBLISH_INTERVAL +
m_Rng () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE)); rand () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE));
m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishTimer, m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishTimer,
this, std::placeholders::_1)); this, std::placeholders::_1));
} }
@ -1462,7 +1434,7 @@ namespace i2p
i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ())); i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ()));
} }
else else
LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnels. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " milliseconds"); LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnles. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " seconds");
} }
m_PublishExcluded.insert (floodfill->GetIdentHash ()); m_PublishExcluded.insert (floodfill->GetIdentHash ());
m_PublishReplyToken = replyToken; m_PublishReplyToken = replyToken;
@ -1476,7 +1448,7 @@ namespace i2p
if (m_PublishTimer) if (m_PublishTimer)
{ {
m_PublishTimer->cancel (); m_PublishTimer->cancel ();
m_PublishTimer->expires_from_now (boost::posix_time::milliseconds(ROUTER_INFO_CONFIRMATION_TIMEOUT)); m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONFIRMATION_TIMEOUT));
m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishResendTimer, m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishResendTimer,
this, std::placeholders::_1)); this, std::placeholders::_1));
} }
@ -1499,8 +1471,7 @@ namespace i2p
if (m_CongestionUpdateTimer) if (m_CongestionUpdateTimer)
{ {
m_CongestionUpdateTimer->cancel (); m_CongestionUpdateTimer->cancel ();
m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds( m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONGESTION_UPDATE_INTERVAL));
ROUTER_INFO_CONGESTION_UPDATE_INTERVAL + m_Rng () % ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE));
m_CongestionUpdateTimer->async_wait (std::bind (&RouterContext::HandleCongestionUpdateTimer, m_CongestionUpdateTimer->async_wait (std::bind (&RouterContext::HandleCongestionUpdateTimer,
this, std::placeholders::_1)); this, std::placeholders::_1));
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -12,7 +12,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <memory> #include <memory>
#include <random>
#include <unordered_set> #include <unordered_set>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "Identity.h" #include "Identity.h"
@ -35,10 +34,9 @@ namespace garlic
const int ROUTER_INFO_PUBLISH_INTERVAL = 39*60; // in seconds const int ROUTER_INFO_PUBLISH_INTERVAL = 39*60; // in seconds
const int ROUTER_INFO_INITIAL_PUBLISH_INTERVAL = 10; // in seconds const int ROUTER_INFO_INITIAL_PUBLISH_INTERVAL = 10; // in seconds
const int ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE = 105;// in seconds const int ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE = 105;// in seconds
const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 1600; // in milliseconds const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 5; // in seconds
const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15;
const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 11*60; // in seconds const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 12*60; // in seconds
const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE = 130; // in seconds
const int ROUTER_INFO_CLEANUP_INTERVAL = 102; // in seconds const int ROUTER_INFO_CLEANUP_INTERVAL = 102; // in seconds
enum RouterStatus enum RouterStatus
@ -204,8 +202,7 @@ namespace garlic
// implements GarlicDestination // implements GarlicDestination
void HandleI2NPMessage (const uint8_t * buf, size_t len) override; void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) override;
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from) override;
private: private:
@ -266,10 +263,6 @@ namespace garlic
uint32_t m_PublishReplyToken; uint32_t m_PublishReplyToken;
bool m_IsHiddenMode; // not publish bool m_IsHiddenMode; // not publish
mutable std::mutex m_RouterInfoMutex; mutable std::mutex m_RouterInfoMutex;
std::mt19937 m_Rng;
std::shared_ptr<i2p::data::RouterInfo::Buffer> m_SaveBuffer;
std::mutex m_SaveBufferMutex; // TODO: make m_SaveBuffer atomic
std::atomic<bool> m_IsSaving;
}; };
extern RouterContext context; extern RouterContext context;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,7 +11,7 @@
#include "I2PEndian.h" #include "I2PEndian.h"
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <charconv> #include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp> // for boost::to_lower #include <boost/algorithm/string.hpp> // for boost::to_lower
#ifndef __cpp_lib_atomic_shared_ptr #ifndef __cpp_lib_atomic_shared_ptr
#include <boost/atomic.hpp> #include <boost/atomic.hpp>
@ -25,7 +25,6 @@
#include "Transports.h" #include "Transports.h"
#include "NetDb.hpp" #include "NetDb.hpp"
#include "RouterContext.h" #include "RouterContext.h"
#include "CryptoKey.h"
#include "RouterInfo.h" #include "RouterInfo.h"
namespace i2p namespace i2p
@ -107,7 +106,8 @@ namespace data
// skip identity // skip identity
size_t identityLen = m_RouterIdentity->GetFullLen (); size_t identityLen = m_RouterIdentity->GetFullLen ();
// read new RI // read new RI
ReadFromBuffer (buf + identityLen, len - identityLen); std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen));
ReadFromStream (str);
if (!m_IsUnreachable) if (!m_IsUnreachable)
UpdateBuffer (buf, len); // save buffer UpdateBuffer (buf, len); // save buffer
// don't delete buffer until saved to the file // don't delete buffer until saved to the file
@ -195,34 +195,39 @@ namespace data
} }
} }
// parse RI // parse RI
if (!ReadFromBuffer (m_Buffer->data () + identityLen, bufferLen - identityLen)) std::stringstream str;
str.write ((const char *)m_Buffer->data () + identityLen, bufferLen - identityLen);
ReadFromStream (str);
if (!str)
{ {
LogPrint (eLogError, "RouterInfo: Malformed message"); LogPrint (eLogError, "RouterInfo: Malformed message");
m_IsUnreachable = true; m_IsUnreachable = true;
} }
} }
bool RouterInfo::ReadFromBuffer (const uint8_t * buf, size_t len) void RouterInfo::ReadFromStream (std::istream& s)
{ {
if (len < 9) return false; if (!s) return;
m_Caps = 0; m_Congestion = eLowCongestion; m_Caps = 0; m_Congestion = eLowCongestion;
m_Timestamp = bufbe64toh (buf); s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
size_t offset = 8; // timestamp m_Timestamp = be64toh (m_Timestamp);
// read addresses // read addresses
auto addresses = NewAddresses (); auto addresses = NewAddresses ();
uint8_t numAddresses = buf[offset]; offset++; uint8_t numAddresses;
s.read ((char *)&numAddresses, sizeof (numAddresses));
for (int i = 0; i < numAddresses; i++) for (int i = 0; i < numAddresses; i++)
{ {
if (offset + 9 > len) return false; // 1 byte cost + 8 bytes date
uint8_t supportedTransports = 0; uint8_t supportedTransports = 0;
auto address = NewAddress (); auto address = NewAddress ();
offset++; // cost, ignore uint8_t cost; // ignore
address->date = bufbe64toh (buf + offset); offset += 8; // date s.read ((char *)&cost, sizeof (cost));
s.read ((char *)&address->date, sizeof (address->date));
bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false; bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false;
auto transportStyle = ExtractString (buf + offset, len - offset); offset += transportStyle.length () + 1; char transportStyle[6];
if (!transportStyle.compare (0, 4, "NTCP")) // NTCP or NTCP2 ReadString (transportStyle, 6, s);
if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2
address->transportStyle = eTransportNTCP2; address->transportStyle = eTransportNTCP2;
else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2 else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2
{ {
address->transportStyle = eTransportSSU2; address->transportStyle = eTransportSSU2;
address->ssu.reset (new SSUExt ()); address->ssu.reset (new SSUExt ());
@ -232,22 +237,24 @@ namespace data
address->transportStyle = eTransportUnknown; address->transportStyle = eTransportUnknown;
address->caps = 0; address->caps = 0;
address->port = 0; address->port = 0;
if (offset + 2 > len) return false; uint16_t size, r = 0;
uint16_t size = bufbe16toh (buf + offset); offset += 2; // size s.read ((char *)&size, sizeof (size)); if (!s) return;
if (offset + size >= len) return false; size = be16toh (size);
if (address->transportStyle == eTransportUnknown) if (address->transportStyle == eTransportUnknown)
{ {
// skip unknown address // skip unknown address
offset += size; s.seekg (size, std::ios_base::cur);
continue; if (s) continue; else return;
} }
size_t r = 0;
while (r < size) while (r < size)
{ {
auto [key, value, sz] = ExtractParam (buf + offset, len - offset); char key[255], value[255];
r += sz; offset += sz; r += ReadString (key, 255, s);
if (key.empty ()) continue; s.seekg (1, std::ios_base::cur); r++; // =
if (key == "host") r += ReadString (value, 255, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!s) return;
if (!strcmp (key, "host"))
{ {
boost::system::error_code ecode; boost::system::error_code ecode;
address->host = boost::asio::ip::make_address (value, ecode); address->host = boost::asio::ip::make_address (value, ecode);
@ -261,53 +268,63 @@ namespace data
address->transportStyle = eTransportUnknown; address->transportStyle = eTransportUnknown;
} }
} }
else if (key == "port") else if (!strcmp (key, "port"))
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), address->port); try
if (res.ec != std::errc()) {
LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ()); address->port = boost::lexical_cast<int>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'port' exception ", ex.what ());
}
} }
else if (key == "mtu") else if (!strcmp (key, "mtu"))
{ {
if (address->ssu) if (address->ssu)
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), address->ssu->mtu); try
if (res.ec != std::errc()) {
LogPrint (eLogWarning, "RouterInfo: 'mtu' conversion error: ", std::make_error_code (res.ec).message ()); address->ssu->mtu = boost::lexical_cast<int>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'mtu' exception ", ex.what ());
}
} }
else else
LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2");
} }
else if (key == "caps") else if (!strcmp (key, "caps"))
address->caps = ExtractAddressCaps (value); address->caps = ExtractAddressCaps (value);
else if (key == "s") // ntcp2 or ssu2 static key else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key
{ {
if (Base64ToByteStream (value, address->s, 32) == 32 && if (Base64ToByteStream (value, strlen (value), address->s, 32) == 32 &&
!(address->s[31] & 0x80)) // check if x25519 public key !(address->s[31] & 0x80)) // check if x25519 public key
isStaticKey = true; isStaticKey = true;
else else
address->transportStyle = eTransportUnknown; // invalid address address->transportStyle = eTransportUnknown; // invalid address
} }
else if (key == "i") // ntcp2 iv or ssu2 intro else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro
{ {
if (address->IsNTCP2 ()) if (address->IsNTCP2 ())
{ {
if (Base64ToByteStream (value, address->i, 16) == 16) if (Base64ToByteStream (value, strlen (value), address->i, 16) == 16)
address->published = true; // presence of "i" means "published" NTCP2 address->published = true; // presence of "i" means "published" NTCP2
else else
address->transportStyle = eTransportUnknown; // invalid address address->transportStyle = eTransportUnknown; // invalid address
} }
else if (address->IsSSU2 ()) else if (address->IsSSU2 ())
{ {
if (Base64ToByteStream (value, address->i, 32) == 32) if (Base64ToByteStream (value, strlen (value), address->i, 32) == 32)
isIntroKey = true; isIntroKey = true;
else else
address->transportStyle = eTransportUnknown; // invalid address address->transportStyle = eTransportUnknown; // invalid address
} }
} }
else if (key == "v") else if (!strcmp (key, "v"))
{ {
if (value == "2") if (!strcmp (value, "2"))
isV2 = true; isV2 = true;
else else
{ {
@ -323,11 +340,13 @@ namespace data
LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped");
continue; continue;
} }
unsigned char index = key[key.length () - 1] - '0'; // TODO: size_t l = strlen(key);
unsigned char index = key[l-1] - '0'; // TODO:
key[l-1] = 0;
if (index > 9) if (index > 9)
{ {
LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped");
continue; if (s) continue; else return;
} }
if (index >= address->ssu->introducers.size ()) if (index >= address->ssu->introducers.size ())
{ {
@ -336,23 +355,34 @@ namespace data
address->ssu->introducers.resize (index + 1); address->ssu->introducers.resize (index + 1);
} }
Introducer& introducer = address->ssu->introducers.at (index); Introducer& introducer = address->ssu->introducers.at (index);
auto key1 = key.substr(0, key.length () - 1); if (!strcmp (key, "itag"))
if (key1 == "itag")
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag); try
if (res.ec != std::errc()) {
LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ()); introducer.iTag = boost::lexical_cast<uint32_t>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ());
}
} }
else if (key1 == "ih") else if (!strcmp (key, "ih"))
Base64ToByteStream (value, introducer.iH, 32); Base64ToByteStream (value, strlen (value), introducer.iH, 32);
else if (key1 == "iexp") else if (!strcmp (key, "iexp"))
{ {
auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp); try
if (res.ec != std::errc()) {
LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ()); introducer.iExp = boost::lexical_cast<uint32_t>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ());
}
} }
} }
} if (!s) return;
}
if (address->transportStyle == eTransportNTCP2) if (address->transportStyle == eTransportNTCP2)
{ {
if (isStaticKey) if (isStaticKey)
@ -416,73 +446,66 @@ namespace data
boost::atomic_store (&m_Addresses, addresses); boost::atomic_store (&m_Addresses, addresses);
#endif #endif
// read peers // read peers
if (offset + 1 > len) return false; uint8_t numPeers;
uint8_t numPeers = buf[offset]; offset++; // num peers s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
offset += numPeers*32; // TODO: read peers s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
// read properties // read properties
if (offset + 2 > len) return false;
m_Version = 0; m_Version = 0;
bool isNetId = false; bool isNetId = false;
std::string family; std::string family;
uint16_t size = bufbe16toh (buf + offset); offset += 2; // size uint16_t size, r = 0;
if (offset + size > len) return false; s.read ((char *)&size, sizeof (size)); if (!s) return;
size_t r = 0; size = be16toh (size);
while (r < size) while (r < size)
{ {
auto [key, value, sz] = ExtractParam (buf + offset, len - offset); char key[255], value[255];
r += sz; offset += sz; r += ReadString (key, 255, s);
if (key.empty ()) continue; s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, 255, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!s) return;
SetProperty (key, value); SetProperty (key, value);
// extract caps // extract caps
if (key == "caps") if (!strcmp (key, "caps"))
{ {
ExtractCaps (value); ExtractCaps (value);
m_IsFloodfill = IsDeclaredFloodfill (); m_IsFloodfill = IsDeclaredFloodfill ();
} }
// extract version // extract version
else if (key == ROUTER_INFO_PROPERTY_VERSION) else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION))
{ {
m_Version = 0; m_Version = 0;
for (auto ch: value) char * ch = value;
while (*ch)
{ {
if (ch >= '0' && ch <= '9') if (*ch >= '0' && *ch <= '9')
{ {
m_Version *= 10; m_Version *= 10;
m_Version += (ch - '0'); m_Version += (*ch - '0');
} }
ch++;
} }
if (m_Version < NETDB_MIN_PEER_TEST_VERSION && (m_SupportedTransports & (eSSU2V4 | eSSU2V6)))
{
auto addresses = GetAddresses ();
if (addresses)
{
if ((*addresses)[eSSU2V4Idx]) (*addresses)[eSSU2V4Idx]->caps &= ~eSSUTesting;
if ((*addresses)[eSSU2V6Idx]) (*addresses)[eSSU2V6Idx]->caps &= ~eSSUTesting;
}
}
} }
// check netId // check netId
else if (key == ROUTER_INFO_PROPERTY_NETID) else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID))
{ {
isNetId = true; isNetId = true;
int netID; if (atoi (value) != i2p::context.GetNetID ())
auto res = std::from_chars(value.data(), value.data() + value.size(), netID);
if (res.ec != std::errc() || netID != i2p::context.GetNetID ())
{ {
LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value);
m_IsUnreachable = true; m_IsUnreachable = true;
} }
} }
// family // family
else if (key == ROUTER_INFO_PROPERTY_FAMILY) else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY))
{ {
family = value; family = value;
boost::to_lower (family); boost::to_lower (family);
} }
else if (key == ROUTER_INFO_PROPERTY_FAMILY_SIG) else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG))
{ {
if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) // TODO if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value))
m_FamilyID = netdb.GetFamilies ().GetFamilyID (family); m_FamilyID = netdb.GetFamilies ().GetFamilyID (family);
else else
{ {
@ -490,24 +513,25 @@ namespace data
SetUnreachable (true); SetUnreachable (true);
} }
} }
if (!s) return;
} }
if (!m_SupportedTransports || !isNetId || !m_Version) if (!m_SupportedTransports || !isNetId || !m_Version)
SetUnreachable (true); SetUnreachable (true);
}
return true;
}
bool RouterInfo::IsFamily (FamilyID famid) const bool RouterInfo::IsFamily (FamilyID famid) const
{ {
return m_FamilyID == famid; return m_FamilyID == famid;
} }
void RouterInfo::ExtractCaps (std::string_view value) void RouterInfo::ExtractCaps (const char * value)
{ {
for (auto cap: value) const char * cap = value;
while (*cap)
{ {
switch (cap) switch (*cap)
{ {
case CAPS_FLAG_FLOODFILL: case CAPS_FLAG_FLOODFILL:
m_Caps |= Caps::eFloodfill; m_Caps |= Caps::eFloodfill;
@ -516,16 +540,16 @@ namespace data
case CAPS_FLAG_LOW_BANDWIDTH2: case CAPS_FLAG_LOW_BANDWIDTH2:
case CAPS_FLAG_LOW_BANDWIDTH3: case CAPS_FLAG_LOW_BANDWIDTH3:
case CAPS_FLAG_LOW_BANDWIDTH4: case CAPS_FLAG_LOW_BANDWIDTH4:
m_BandwidthCap = cap; m_BandwidthCap = *cap;
break; break;
case CAPS_FLAG_HIGH_BANDWIDTH: case CAPS_FLAG_HIGH_BANDWIDTH:
m_Caps |= Caps::eHighBandwidth; m_Caps |= Caps::eHighBandwidth;
m_BandwidthCap = cap; m_BandwidthCap = *cap;
break; break;
case CAPS_FLAG_EXTRA_BANDWIDTH1: case CAPS_FLAG_EXTRA_BANDWIDTH1:
case CAPS_FLAG_EXTRA_BANDWIDTH2: case CAPS_FLAG_EXTRA_BANDWIDTH2:
m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth; m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth;
m_BandwidthCap = cap; m_BandwidthCap = *cap;
break; break;
case CAPS_FLAG_HIDDEN: case CAPS_FLAG_HIDDEN:
m_Caps |= Caps::eHidden; m_Caps |= Caps::eHidden;
@ -547,15 +571,17 @@ namespace data
break; break;
default: ; default: ;
} }
cap++;
} }
} }
uint8_t RouterInfo::ExtractAddressCaps (std::string_view value) const uint8_t RouterInfo::ExtractAddressCaps (const char * value) const
{ {
uint8_t caps = 0; uint8_t caps = 0;
for (auto cap: value) const char * cap = value;
while (*cap)
{ {
switch (cap) switch (*cap)
{ {
case CAPS_FLAG_V4: case CAPS_FLAG_V4:
caps |= AddressCaps::eV4; caps |= AddressCaps::eV4;
@ -571,10 +597,11 @@ namespace data
break; break;
default: ; default: ;
} }
cap++;
} }
return caps; return caps;
} }
void RouterInfo::UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts) void RouterInfo::UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts)
{ {
if (!address || !address->ssu) return; if (!address || !address->ssu) return;
@ -634,41 +661,25 @@ namespace data
return SaveToFile (fullPath, m_Buffer); return SaveToFile (fullPath, m_Buffer);
} }
std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const
{ {
uint8_t l = buf[0]; uint8_t l;
if (l > len) s.read ((char *)&l, 1);
if (l < len)
{
s.read (str, l);
if (!s) l = 0; // failed, return empty string
str[l] = 0;
}
else
{ {
LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len); LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len);
l = len; s.seekg (l, std::ios::cur); // skip
} str[0] = 0;
return { (const char *)(buf + 1), l }; }
return l+1;
} }
std::tuple<std::string_view, std::string_view, size_t> RouterInfo::ExtractParam (const uint8_t * buf, size_t len) const
{
auto key = ExtractString (buf, len);
size_t offset = key.length () + 1;
if (offset >= len) return { std::string_view(), std::string_view(), len };
if (buf[offset] != '=')
{
LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead '=' after ", key);
key = std::string_view();
}
offset++;
if (offset >= len) return { key, std::string_view(), len };
auto value = ExtractString (buf + offset, len - offset);
offset += value.length () + 1;
if (offset >= len) return { key, std::string_view(), len };
if (buf[offset] != ';')
{
LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead ';' after ", value);
value = std::string_view();
}
offset++;
return { key, value, offset };
}
void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps) void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps)
{ {
auto addr = std::make_shared<Address>(); auto addr = std::make_shared<Address>();
@ -1125,12 +1136,12 @@ namespace data
void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len) void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len)
{ {
m_IsBufferScheduledToDelete = false;
if (!m_Buffer) if (!m_Buffer)
m_Buffer = NewBuffer (); m_Buffer = NewBuffer ();
if (len > m_Buffer->size ()) len = m_Buffer->size (); if (len > m_Buffer->size ()) len = m_Buffer->size ();
memcpy (m_Buffer->data (), buf, len); memcpy (m_Buffer->data (), buf, len);
m_Buffer->SetBufferLen (len); m_Buffer->SetBufferLen (len);
m_IsBufferScheduledToDelete = false;
} }
std::shared_ptr<RouterInfo::Buffer> RouterInfo::CopyBuffer () const std::shared_ptr<RouterInfo::Buffer> RouterInfo::CopyBuffer () const
@ -1382,9 +1393,9 @@ namespace data
if (!introducer.iTag) continue; if (!introducer.iTag) continue;
if (introducer.iExp) // expiration is specified if (introducer.iExp) // expiration is specified
{ {
WriteString ("iexp" + std::to_string(i), properties); WriteString ("iexp" + boost::lexical_cast<std::string>(i), properties);
properties << '='; properties << '=';
WriteString (std::to_string(introducer.iExp), properties); WriteString (boost::lexical_cast<std::string>(introducer.iExp), properties);
properties << ';'; properties << ';';
} }
i++; i++;
@ -1393,9 +1404,11 @@ namespace data
for (const auto& introducer: address.ssu->introducers) for (const auto& introducer: address.ssu->introducers)
{ {
if (!introducer.iTag) continue; if (!introducer.iTag) continue;
WriteString ("ih" + std::to_string(i), properties); WriteString ("ih" + boost::lexical_cast<std::string>(i), properties);
properties << '='; properties << '=';
auto value = ByteStreamToBase64 (introducer.iH, 32); char value[64];
size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64);
value[l] = 0;
WriteString (value, properties); WriteString (value, properties);
properties << ';'; properties << ';';
i++; i++;
@ -1404,9 +1417,9 @@ namespace data
for (const auto& introducer: address.ssu->introducers) for (const auto& introducer: address.ssu->introducers)
{ {
if (!introducer.iTag) continue; if (!introducer.iTag) continue;
WriteString ("itag" + std::to_string(i), properties); WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
properties << '='; properties << '=';
WriteString (std::to_string(introducer.iTag), properties); WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
properties << ';'; properties << ';';
i++; i++;
} }
@ -1420,7 +1433,7 @@ namespace data
{ {
WriteString ("mtu", properties); WriteString ("mtu", properties);
properties << '='; properties << '=';
WriteString (std::to_string(address.ssu->mtu), properties); WriteString (boost::lexical_cast<std::string>(address.ssu->mtu), properties);
properties << ';'; properties << ';';
} }
} }
@ -1428,7 +1441,7 @@ namespace data
{ {
WriteString ("port", properties); WriteString ("port", properties);
properties << '='; properties << '=';
WriteString (std::to_string(address.port), properties); WriteString (boost::lexical_cast<std::string>(address.port), properties);
properties << ';'; properties << ';';
} }
if (address.IsNTCP2 () || address.IsSSU2 ()) if (address.IsNTCP2 () || address.IsSSU2 ())
@ -1463,11 +1476,9 @@ namespace data
s.write (properties.str ().c_str (), properties.str ().size ()); s.write (properties.str ().c_str (), properties.str ().size ());
} }
void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value) void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value)
{ {
auto [it, inserted] = m_Properties.emplace (key, value); m_Properties[key] = value;
if (!inserted)
it->second = value;
} }
void LocalRouterInfo::DeleteProperty (const std::string& key) void LocalRouterInfo::DeleteProperty (const std::string& key)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -11,8 +11,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <string_view>
#include <tuple>
#include <map> #include <map>
#include <vector> #include <vector>
#include <array> #include <array>
@ -210,8 +208,8 @@ namespace data
typedef boost::shared_ptr<Addresses> AddressesPtr; typedef boost::shared_ptr<Addresses> AddressesPtr;
#endif #endif
RouterInfo (const std::string& fullPath); RouterInfo (const std::string& fullPath);
RouterInfo (const RouterInfo& ) = delete; RouterInfo (const RouterInfo& ) = default;
RouterInfo& operator=(const RouterInfo& ) = delete; RouterInfo& operator=(const RouterInfo& ) = default;
RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len); RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len);
RouterInfo (const uint8_t * buf, size_t len); RouterInfo (const uint8_t * buf, size_t len);
virtual ~RouterInfo (); virtual ~RouterInfo ();
@ -221,7 +219,7 @@ namespace data
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
uint64_t GetTimestamp () const { return m_Timestamp; }; uint64_t GetTimestamp () const { return m_Timestamp; };
int GetVersion () const { return m_Version; }; int GetVersion () const { return m_Version; };
virtual void SetProperty (std::string_view key, std::string_view value) {}; virtual void SetProperty (const std::string& key, const std::string& value) {};
virtual void ClearProperties () {}; virtual void ClearProperties () {};
AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr
std::shared_ptr<const Address> GetNTCP2V4Address () const; std::shared_ptr<const Address> GetNTCP2V4Address () const;
@ -296,7 +294,6 @@ namespace data
std::shared_ptr<Buffer> GetSharedBuffer () const { return m_Buffer; }; std::shared_ptr<Buffer> GetSharedBuffer () const { return m_Buffer; };
std::shared_ptr<Buffer> CopyBuffer () const; std::shared_ptr<Buffer> CopyBuffer () const;
void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; }; void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; };
void CancelBufferToDelete () { m_IsBufferScheduledToDelete = false; };
bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; }; bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; };
bool IsUpdated () const { return m_IsUpdated; }; bool IsUpdated () const { return m_IsUpdated; };
@ -335,12 +332,11 @@ namespace data
bool LoadFile (const std::string& fullPath); bool LoadFile (const std::string& fullPath);
void ReadFromFile (const std::string& fullPath); void ReadFromFile (const std::string& fullPath);
bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed void ReadFromStream (std::istream& s);
void ReadFromBuffer (bool verifySignature); void ReadFromBuffer (bool verifySignature);
std::string_view ExtractString (const uint8_t * buf, size_t len) const; size_t ReadString (char* str, size_t len, std::istream& s) const;
std::tuple<std::string_view, std::string_view, size_t> ExtractParam (const uint8_t * buf, size_t len) const; void ExtractCaps (const char * value);
void ExtractCaps (std::string_view value); uint8_t ExtractAddressCaps (const char * value) const;
uint8_t ExtractAddressCaps (std::string_view value) const;
void UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts); void UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts);
template<typename Filter> template<typename Filter>
std::shared_ptr<const Address> GetAddress (Filter filter) const; std::shared_ptr<const Address> GetAddress (Filter filter) const;
@ -382,7 +378,7 @@ namespace data
void UpdateCaps (uint8_t caps); void UpdateCaps (uint8_t caps);
bool UpdateCongestion (Congestion c); // returns true if updated bool UpdateCongestion (Congestion c); // returns true if updated
void SetProperty (std::string_view key, std::string_view value) override; void SetProperty (const std::string& key, const std::string& value) override;
void DeleteProperty (const std::string& key); void DeleteProperty (const std::string& key);
std::string GetProperty (const std::string& key) const; std::string GetProperty (const std::string& key) const;
void ClearProperties () override { m_Properties.clear (); }; void ClearProperties () override { m_Properties.clear (); };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -890,7 +890,7 @@ namespace transport
} }
auto session = std::make_shared<SSU2Session> (*this, router, address); auto session = std::make_shared<SSU2Session> (*this, router, address);
if (!isValidEndpoint && router->HasProfile () && router->GetProfile ()->HasLastEndpoint (address->IsV4 ())) if (!isValidEndpoint && router->GetProfile ()->HasLastEndpoint (address->IsV4 ()))
{ {
// router doesn't publish endpoint, but we connected before and hole punch might be alive // router doesn't publish endpoint, but we connected before and hole punch might be alive
auto ep = router->GetProfile ()->GetLastEndpoint (); auto ep = router->GetProfile ()->GetLastEndpoint ();
@ -1251,21 +1251,18 @@ namespace transport
} }
uint64_t token; uint64_t token;
RAND_bytes ((uint8_t *)&token, 8); RAND_bytes ((uint8_t *)&token, 8);
if (!token) token = 1; // token can't be zero m_IncomingTokens.emplace (ep, std::make_pair (token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)));
m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT));
return token; return token;
} }
std::pair<uint64_t, uint32_t> SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep) std::pair<uint64_t, uint32_t> SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep)
{ {
m_IncomingTokens.erase (ep); // drop previous
uint64_t token; uint64_t token;
RAND_bytes ((uint8_t *)&token, 8); RAND_bytes ((uint8_t *)&token, 8);
if (!token) token = 1; // token can't be zero auto ret = std::make_pair (token, uint32_t(i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT));
uint32_t expires = i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT; m_IncomingTokens.emplace (ep, ret);
auto [it, inserted] = m_IncomingTokens.try_emplace (ep, token, expires); return ret;
if (!inserted)
it->second = { token, expires }; // override
return it->second;
} }
std::vector<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers, std::vector<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,
@ -1530,11 +1527,6 @@ namespace transport
{ {
return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
} }
void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
m_ChaCha20 (msg, msgLen, key, nonce, out);
}
void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -86,7 +86,6 @@ namespace transport
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; } bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; }
bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; };
void AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from); void AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from);
@ -207,7 +206,6 @@ namespace transport
mutable std::mutex m_ReceivedPacketsQueueMutex; mutable std::mutex m_ReceivedPacketsQueueMutex;
i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
i2p::crypto::ChaCha20Context m_ChaCha20;
// proxy // proxy
bool m_IsThroughProxy; bool m_IsThroughProxy;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024-2025, The PurpleI2P Project * Copyright (c) 2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -46,7 +46,7 @@ namespace transport
} }
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token uint64_t headerX[2]; // sourceConnID, token
GetServer ().ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
SetDestConnID (headerX[0]); SetDestConnID (headerX[0]);
// decrypt and handle payload // decrypt and handle payload
uint8_t * payload = buf + 32; uint8_t * payload = buf + 32;
@ -183,7 +183,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
memset (n, 0, 12); memset (n, 0, 12);
GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16);
// send // send
GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ()); GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ());
UpdateNumSentBytes (payloadSize + 32); UpdateNumSentBytes (payloadSize + 32);
@ -191,7 +191,12 @@ namespace transport
void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed) void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed)
{ {
#if __cplusplus >= 202002L // C++20
m_SignedData.assign (signedData, signedData + signedDataLen); m_SignedData.assign (signedData, signedData + signedDataLen);
#else
m_SignedData.resize (signedDataLen);
memcpy (m_SignedData.data (), signedData, signedDataLen);
#endif
if (!delayed) if (!delayed)
SendPeerTest (msg); SendPeerTest (msg);
// schedule resend for msgs 5 or 6 // schedule resend for msgs 5 or 6
@ -252,7 +257,7 @@ namespace transport
{ {
// we are Charlie // we are Charlie
uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id
uint64_t sourceConnID = ~destConnID; uint32_t sourceConnID = ~destConnID;
SetSourceConnID (sourceConnID); SetSourceConnID (sourceConnID);
SetDestConnID (destConnID); SetDestConnID (destConnID);
SetState (eSSU2SessionStateHolePunch); SetState (eSSU2SessionStateHolePunch);
@ -300,7 +305,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
memset (n, 0, 12); memset (n, 0, 12);
GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16);
// send // send
GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep); GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep);
UpdateNumSentBytes (payloadSize + 32); UpdateNumSentBytes (payloadSize + 32);
@ -308,7 +313,12 @@ namespace transport
void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen) void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen)
{ {
#if __cplusplus >= 202002L // C++20
m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen); m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen);
#else
m_RelayResponseBlock.resize (relayResponseBlockLen);
memcpy (m_RelayResponseBlock.data (), relayResponseBlock, relayResponseBlockLen);
#endif
SendHolePunch (); SendHolePunch ();
ScheduleResend (); ScheduleResend ();
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -92,7 +92,7 @@ namespace transport
m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()), m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()),
m_TerminationReason (eSSU2TerminationReasonNormalClose), m_TerminationReason (eSSU2TerminationReasonNormalClose),
m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size
m_LastResendTime (0), m_LastResendAttemptTime (0), m_NumRanges (0) m_LastResendTime (0), m_LastResendAttemptTime (0)
{ {
if (noise) if (noise)
m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState);
@ -189,7 +189,7 @@ namespace transport
if (!asz) return false; if (!asz) return false;
payload[17] = asz; payload[17] = asz;
packet->payloadSize = asz + 18; packet->payloadSize = asz + 18;
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash
@ -623,8 +623,7 @@ namespace transport
} }
else else
{ {
uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize, uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize);
it->second->numResends > 1 ? SSU2_FLAG_IMMEDIATE_ACK_REQUESTED : 0);
it->second->numResends++; it->second->numResends++;
it->second->sendTime = ts; it->second->sendTime = ts;
resentPackets.emplace (packetNum, it->second); resentPackets.emplace (packetNum, it->second);
@ -683,7 +682,7 @@ namespace transport
} }
const uint8_t nonce[12] = {0}; const uint8_t nonce[12] = {0};
uint64_t headerX[2]; uint64_t headerX[2];
m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]); LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]);
break; break;
} }
@ -749,7 +748,7 @@ namespace transport
payloadSize += 16; payloadSize += 16;
header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12));
m_Server.ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); i2p::crypto::ChaCha20 (headerX, 48, m_Address->i, nonce, headerX);
m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated
m_SentHandshakePacket->payloadSize = payloadSize; m_SentHandshakePacket->payloadSize = payloadSize;
// send // send
@ -776,7 +775,7 @@ namespace transport
} }
const uint8_t nonce[12] = {0}; const uint8_t nonce[12] = {0};
uint8_t headerX[48]; uint8_t headerX[48];
m_Server.ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX);
memcpy (&m_DestConnID, headerX, 8); memcpy (&m_DestConnID, headerX, 8);
uint64_t token; uint64_t token;
memcpy (&token, headerX + 8, 8); memcpy (&token, headerX + 8, 8);
@ -875,7 +874,7 @@ namespace transport
m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted Noise payload from Session Created) m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted Noise payload from Session Created)
header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12));
m_Server.ChaCha20 (headerX, 48, kh2, nonce, headerX); i2p::crypto::ChaCha20 (headerX, 48, kh2, nonce, headerX);
m_State = eSSU2SessionStateSessionCreatedSent; m_State = eSSU2SessionStateSessionCreatedSent;
m_SentHandshakePacket->payloadSize = payloadSize; m_SentHandshakePacket->payloadSize = payloadSize;
// send // send
@ -903,7 +902,7 @@ namespace transport
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
const uint8_t nonce[12] = {0}; const uint8_t nonce[12] = {0};
uint8_t headerX[48]; uint8_t headerX[48];
m_Server.ChaCha20 (buf + 16, 48, kh2, nonce, headerX); i2p::crypto::ChaCha20 (buf + 16, 48, kh2, nonce, headerX);
// KDF for SessionCreated // KDF for SessionCreated
m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header)
m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk); m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk);
@ -1179,18 +1178,13 @@ namespace transport
LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb"); LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb");
return false; return false;
} }
std::shared_ptr<i2p::data::RouterProfile> profile; // not null if older
bool isOlder = false;
if (ri->GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) if (ri->GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
{ {
// received RouterInfo is older than one in netdb // received RouterInfo is older than one in netdb
isOlder = true; profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile
if (ri->HasProfile ()) if (profile && profile->IsDuplicated ())
{ return false;
auto profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile
if (profile && profile->IsDuplicated ())
return false;
}
} }
ri = ri1; ri = ri1;
@ -1204,28 +1198,15 @@ namespace transport
(!m_RemoteEndpoint.address ().is_v6 () || (!m_RemoteEndpoint.address ().is_v6 () ||
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address
{ {
if (isOlder) // older router? if (profile) // older router?
i2p::data::UpdateRouterProfile (ri->GetIdentHash (), profile->Duplicated (); // mark router as duplicated in profile
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Duplicated (); // mark router as duplicated in profile
});
else else
LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host, LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host,
" and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ()));
return false; return false;
} }
if (!m_Address->published) if (!m_Address->published)
{ ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint);
if (ri->HasProfile ())
ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint);
else
i2p::data::UpdateRouterProfile (ri->GetIdentHash (),
[ep = m_RemoteEndpoint](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->SetLastEndpoint (ep);
});
}
SetRemoteIdentity (ri->GetRouterIdentity ()); SetRemoteIdentity (ri->GetRouterIdentity ());
AdjustMaxPayloadSize (); AdjustMaxPayloadSize ();
m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now
@ -1283,7 +1264,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12));
memset (nonce, 0, 12); memset (nonce, 0, 12);
m_Server.ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16);
// send // send
if (m_Server.AddPendingOutgoingSession (shared_from_this ())) if (m_Server.AddPendingOutgoingSession (shared_from_this ()))
m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint);
@ -1305,7 +1286,7 @@ namespace transport
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint8_t h[32]; uint8_t h[32];
memcpy (h, header.buf, 16); memcpy (h, header.buf, 16);
m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
memcpy (&m_DestConnID, h + 16, 8); memcpy (&m_DestConnID, h + 16, 8);
// decrypt // decrypt
CreateNonce (be32toh (header.h.packetNum), nonce); CreateNonce (be32toh (header.h.packetNum), nonce);
@ -1357,7 +1338,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12));
memset (nonce, 0, 12); memset (nonce, 0, 12);
m_Server.ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
// send // send
m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint);
} }
@ -1381,7 +1362,7 @@ namespace transport
} }
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token uint64_t headerX[2]; // sourceConnID, token
m_Server.ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX);
uint64_t token = headerX[1]; uint64_t token = headerX[1];
if (token) if (token)
m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT);
@ -1430,7 +1411,7 @@ namespace transport
} }
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token uint64_t headerX[2]; // sourceConnID, token
m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
m_DestConnID = headerX[0]; m_DestConnID = headerX[0];
// decrypt and handle payload // decrypt and handle payload
uint8_t * payload = buf + 32; uint8_t * payload = buf + 32;
@ -1498,11 +1479,11 @@ namespace transport
ResendHandshakePacket (); // assume we receive ResendHandshakePacket (); // assume we receive
return; return;
} }
if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()) && if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()))
(!m_PathChallenge || from != m_PathChallenge->second)) // path challenge was not sent to this endpoint yet
{ {
LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from); LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from);
SendPathChallenge (from); m_RemoteEndpoint = from;
SendPathChallenge ();
} }
if (len < 32) if (len < 32)
{ {
@ -1660,11 +1641,10 @@ namespace transport
LogPrint (eLogDebug, "SSU2: Path response"); LogPrint (eLogDebug, "SSU2: Path response");
if (m_PathChallenge) if (m_PathChallenge)
{ {
if (buf64toh (buf + offset) == m_PathChallenge->first) i2p::data::IdentHash hash;
{ SHA256 (buf + offset, size, hash);
m_RemoteEndpoint = m_PathChallenge->second; if (hash == *m_PathChallenge)
m_PathChallenge.reset (nullptr); m_PathChallenge.reset (nullptr);
}
} }
break; break;
} }
@ -1764,7 +1744,6 @@ namespace transport
HandleAckRange (firstPacketNum, ackThrough, i2p::util::GetMillisecondsSinceEpoch ()); // acnt HandleAckRange (firstPacketNum, ackThrough, i2p::util::GetMillisecondsSinceEpoch ()); // acnt
// ranges // ranges
len -= 5; len -= 5;
if (!len || m_SentPackets.empty ()) return; // don't handle ranges if nothing to acknowledge
const uint8_t * ranges = buf + 5; const uint8_t * ranges = buf + 5;
while (len > 0 && firstPacketNum && ackThrough - firstPacketNum < SSU2_MAX_NUM_ACK_PACKETS) while (len > 0 && firstPacketNum && ackThrough - firstPacketNum < SSU2_MAX_NUM_ACK_PACKETS)
{ {
@ -1966,7 +1945,6 @@ namespace transport
void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len)
{ {
// we are Bob // we are Bob
if (len < 9) return;
auto mts = i2p::util::GetMillisecondsSinceEpoch (); auto mts = i2p::util::GetMillisecondsSinceEpoch ();
uint32_t nonce = bufbe32toh (buf + 1); // nonce uint32_t nonce = bufbe32toh (buf + 1); // nonce
uint32_t relayTag = bufbe32toh (buf + 5); // relay tag uint32_t relayTag = bufbe32toh (buf + 5); // relay tag
@ -2000,7 +1978,7 @@ namespace transport
packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0;
if (!packet->payloadSize && r) if (!packet->payloadSize && r)
session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); session->SendFragmentedMessage (CreateDatabaseStoreMsg (r));
packet->payloadSize += CreateRelayIntroBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, buf + 1, len - 1); packet->payloadSize += CreateRelayIntroBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, buf + 1, len -1);
if (packet->payloadSize < m_MaxPayloadSize) if (packet->payloadSize < m_MaxPayloadSize)
packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize); uint32_t packetNum = session->SendData (packet->payload, packet->payloadSize);
@ -2015,24 +1993,18 @@ namespace transport
void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts) void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts)
{ {
// we are Charlie // we are Charlie
if (len < 47) return;
SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept; SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept;
boost::asio::ip::udp::endpoint ep; boost::asio::ip::udp::endpoint ep;
std::shared_ptr<const i2p::data::RouterInfo::Address> addr; std::shared_ptr<const i2p::data::RouterInfo::Address> addr;
auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice auto r = i2p::data::netdb.FindRouter (buf + 1); // Alice
if (r) if (r)
{ {
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (i2p::context.GetIdentHash (), 32); // chash s.Insert (i2p::context.GetIdentHash (), 32); // chash
s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz
uint8_t asz = buf[46]; uint8_t asz = buf[46];
if (asz + 47 + r->GetIdentity ()->GetSignatureLen () > len)
{
LogPrint (eLogWarning, "SSU2: Malformed RelayIntro len=", len);
return;
}
s.Insert (buf + 47, asz); // Alice Port, Alice IP s.Insert (buf + 47, asz); // Alice Port, Alice IP
if (s.Verify (r->GetIdentity (), buf + 47 + asz)) if (s.Verify (r->GetIdentity (), buf + 47 + asz))
{ {
@ -2121,7 +2093,6 @@ namespace transport
void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len) void SSU2Session::HandleRelayResponse (const uint8_t * buf, size_t len)
{ {
if (len < 6) return;
uint32_t nonce = bufbe32toh (buf + 2); uint32_t nonce = bufbe32toh (buf + 2);
if (m_State == eSSU2SessionStateIntroduced) if (m_State == eSSU2SessionStateIntroduced)
{ {
@ -2142,9 +2113,7 @@ namespace transport
auto it = m_RelaySessions.find (nonce); auto it = m_RelaySessions.find (nonce);
if (it != m_RelaySessions.end ()) if (it != m_RelaySessions.end ())
{ {
auto relaySession = it->second.first; if (it->second.first && it->second.first->IsEstablished ())
m_RelaySessions.erase (it);
if (relaySession && relaySession->IsEstablished ())
{ {
// we are Bob, message from Charlie // we are Bob, message from Charlie
auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
@ -2154,12 +2123,12 @@ namespace transport
memcpy (payload + 3, buf, len); // forward to Alice as is memcpy (payload + 3, buf, len); // forward to Alice as is
packet->payloadSize = len + 3; packet->payloadSize = len + 3;
packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); packet->payloadSize += CreatePaddingBlock (payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize);
uint32_t packetNum = relaySession->SendData (packet->payload, packet->payloadSize); uint32_t packetNum = it->second.first->SendData (packet->payload, packet->payloadSize);
if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION)
{ {
// sometimes Alice doesn't ack this RelayResponse in older versions // sometimes Alice doesn't ack this RelayResponse in older versions
packet->sendTime = i2p::util::GetMillisecondsSinceEpoch (); packet->sendTime = i2p::util::GetMillisecondsSinceEpoch ();
relaySession->m_SentPackets.emplace (packetNum, packet); it->second.first->m_SentPackets.emplace (packetNum, packet);
} }
} }
else else
@ -2168,31 +2137,25 @@ namespace transport
if (!buf[1]) // status code accepted? if (!buf[1]) // status code accepted?
{ {
// verify signature // verify signature
uint8_t csz = (len >= 12) ? buf[11] : 0; uint8_t csz = buf[11];
if (csz + 12 + relaySession->GetRemoteIdentity ()->GetSignatureLen () > len) SignedData s;
{
LogPrint (eLogWarning, "SSU2: Malformed RelayResponse len=", len);
relaySession->Done ();
return;
}
SignedData<128> s;
s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint
if (s.Verify (relaySession->GetRemoteIdentity (), buf + 12 + csz)) if (s.Verify (it->second.first->GetRemoteIdentity (), buf + 12 + csz))
{ {
if (relaySession->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet if (it->second.first->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet
{ {
// update Charlie's endpoint // update Charlie's endpoint
if (ExtractEndpoint (buf + 12, csz, relaySession->m_RemoteEndpoint)) if (ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint))
{ {
// update token // update token
uint64_t token; uint64_t token;
memcpy (&token, buf + len - 8, 8); memcpy (&token, buf + len - 8, 8);
m_Server.UpdateOutgoingToken (relaySession->m_RemoteEndpoint, m_Server.UpdateOutgoingToken (it->second.first->m_RemoteEndpoint,
token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT);
// connect to Charlie, HolePunch will be ignored // connect to Charlie, HolePunch will be ignored
relaySession->ConnectAfterIntroduction (); it->second.first->ConnectAfterIntroduction ();
} }
else else
LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint"); LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint");
@ -2201,15 +2164,16 @@ namespace transport
else else
{ {
LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed");
relaySession->Done (); it->second.first->Done ();
} }
} }
else else
{ {
LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2)); LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2));
relaySession->Done (); it->second.first->Done ();
} }
} }
m_RelaySessions.erase (it);
} }
else else
LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); LogPrint (eLogDebug, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2));
@ -2278,13 +2242,10 @@ namespace transport
case 2: // Charlie from Bob case 2: // Charlie from Bob
{ {
// sign with Charlie's key // sign with Charlie's key
if (len < offset + 9) return;
uint8_t asz = buf[offset + 9]; uint8_t asz = buf[offset + 9];
size_t l = asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen (); std::vector<uint8_t> newSignedData (asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen ());
if (len < offset + l) return;
std::vector<uint8_t> newSignedData (l);
memcpy (newSignedData.data (), buf + offset, asz + 10); memcpy (newSignedData.data (), buf + offset, asz + 10);
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (buf + 3, 32); // ahash s.Insert (buf + 3, 32); // ahash
@ -2392,16 +2353,10 @@ namespace transport
if (GetRouterStatus () == eRouterStatusUnknown) if (GetRouterStatus () == eRouterStatusUnknown)
SetTestingState (true); SetTestingState (true);
auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie
if (r && len >= offset + 9) if (r)
{ {
uint8_t asz = buf[offset + 9]; uint8_t asz = buf[offset + 9];
if (len < offset + asz + 10 + r->GetIdentity ()->GetSignatureLen ()) SignedData s;
{
LogPrint (eLogWarning, "Malformed PeerTest 4 len=", len);
session->Done ();
return;
}
SignedData<128> s;
s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // ahash s.Insert (i2p::context.GetIdentity ()->GetIdentHash (), 32); // ahash
@ -2560,19 +2515,17 @@ namespace transport
return nullptr; return nullptr;
} }
void SSU2Session::AdjustMaxPayloadSize (size_t maxMtu) void SSU2Session::AdjustMaxPayloadSize ()
{ {
auto addr = FindLocalAddress (); auto addr = FindLocalAddress ();
if (addr && addr->ssu) if (addr && addr->ssu)
{ {
int mtu = addr->ssu->mtu; int mtu = addr->ssu->mtu;
if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE; if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE;
if (mtu > (int)maxMtu) mtu = maxMtu;
if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu)) if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu))
mtu = m_Address->ssu->mtu; mtu = m_Address->ssu->mtu;
if (mtu) if (mtu)
{ {
if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE;
if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE;
m_MaxPayloadSize = mtu - (addr->IsV6 () ? IPV6_HEADER_SIZE: IPV4_HEADER_SIZE) - UDP_HEADER_SIZE - 32; m_MaxPayloadSize = mtu - (addr->IsV6 () ? IPV6_HEADER_SIZE: IPV4_HEADER_SIZE) - UDP_HEADER_SIZE - 32;
LogPrint (eLogDebug, "SSU2: Session MTU=", mtu, ", max payload size=", m_MaxPayloadSize); LogPrint (eLogDebug, "SSU2: Session MTU=", mtu, ", max payload size=", m_MaxPayloadSize);
@ -2671,17 +2624,17 @@ namespace transport
size_t SSU2Session::CreateAckBlock (uint8_t * buf, size_t len) size_t SSU2Session::CreateAckBlock (uint8_t * buf, size_t len)
{ {
if (len < 8) return 0; if (len < 8) return 0;
int maxNumRanges = (len - 8) >> 1;
if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES;
buf[0] = eSSU2BlkAck; buf[0] = eSSU2BlkAck;
uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin (); uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin ();
htobe32buf (buf + 3, ackThrough); // Ack Through htobe32buf (buf + 3, ackThrough); // Ack Through
uint16_t acnt = 0; uint16_t acnt = 0;
int numRanges = 0;
if (ackThrough) if (ackThrough)
{ {
if (m_OutOfSequencePackets.empty ()) if (m_OutOfSequencePackets.empty ())
{
acnt = std::min ((int)ackThrough, SSU2_MAX_NUM_ACNT); // no gaps acnt = std::min ((int)ackThrough, SSU2_MAX_NUM_ACNT); // no gaps
m_NumRanges = 0;
}
else else
{ {
auto it = m_OutOfSequencePackets.rbegin (); it++; // prev packet num auto it = m_OutOfSequencePackets.rbegin (); it++; // prev packet num
@ -2694,102 +2647,93 @@ namespace transport
it++; it++;
} }
// ranges // ranges
if (!m_NumRanges) uint32_t lastNum = ackThrough - acnt;
{ if (acnt > SSU2_MAX_NUM_ACNT)
int maxNumRanges = (len - 8) >> 1; {
if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES; auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT);
int numRanges = 0; acnt = SSU2_MAX_NUM_ACNT;
uint32_t lastNum = ackThrough - acnt; if (d.quot > maxNumRanges)
if (acnt > SSU2_MAX_NUM_ACNT)
{ {
auto d = std::div (acnt - SSU2_MAX_NUM_ACNT, SSU2_MAX_NUM_ACNT); d.quot = maxNumRanges;
acnt = SSU2_MAX_NUM_ACNT; d.rem = 0;
if (d.quot > maxNumRanges)
{
d.quot = maxNumRanges;
d.rem = 0;
}
// Acks only ranges for acnt
for (int i = 0; i < d.quot; i++)
{
m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255
numRanges++;
}
if (d.rem > 0)
{
m_Ranges[numRanges*2] = 0; m_Ranges[numRanges*2 + 1] = d.rem;
numRanges++;
}
} }
int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; // Acks only ranges for acnt
while (it != m_OutOfSequencePackets.rend () && for (int i = 0; i < d.quot; i++)
numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
{ {
if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255
{
// NACKs only ranges
if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs
while (lastNum - (*it) > SSU2_MAX_NUM_ACNT)
{
m_Ranges[numRanges*2] = SSU2_MAX_NUM_ACNT; m_Ranges[numRanges*2 + 1] = 0; // NACKs 255, Acks 0
lastNum -= SSU2_MAX_NUM_ACNT;
numRanges++;
numPackets += SSU2_MAX_NUM_ACNT;
}
}
// NACKs and Acks ranges
m_Ranges[numRanges*2] = lastNum - (*it) - 1; // NACKs
numPackets += m_Ranges[numRanges*2];
lastNum = *it; it++;
int numAcks = 1;
while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1)
{
numAcks++; lastNum--;
it++;
}
while (numAcks > SSU2_MAX_NUM_ACNT)
{
// Acks only ranges
m_Ranges[numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255
numAcks -= SSU2_MAX_NUM_ACNT;
numRanges++;
numPackets += SSU2_MAX_NUM_ACNT;
m_Ranges[numRanges*2] = 0; // NACKs 0
if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break;
}
if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT;
m_Ranges[numRanges*2 + 1] = (uint8_t)numAcks; // Acks
numPackets += numAcks;
numRanges++; numRanges++;
} }
if (it == m_OutOfSequencePackets.rend () && if (d.rem > 0)
numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
{ {
// add range between out-of-sequence and received buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = d.rem;
int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; numRanges++;
if (nacks > 0) }
}
int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT;
while (it != m_OutOfSequencePackets.rend () &&
numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
{
if (lastNum - (*it) > SSU2_MAX_NUM_ACNT)
{
// NACKs only ranges
if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs
while (lastNum - (*it) > SSU2_MAX_NUM_ACNT)
{ {
if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2] = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0
m_Ranges[numRanges*2] = nacks; lastNum -= SSU2_MAX_NUM_ACNT;
m_Ranges[numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT);
numRanges++; numRanges++;
numPackets += SSU2_MAX_NUM_ACNT;
} }
} }
m_NumRanges = numRanges; // NACKs and Acks ranges
} buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs
if (m_NumRanges) numPackets += buf[8 + numRanges*2];
memcpy (buf + 8, m_Ranges, m_NumRanges*2); lastNum = *it; it++;
int numAcks = 1;
while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1)
{
numAcks++; lastNum--;
it++;
}
while (numAcks > SSU2_MAX_NUM_ACNT)
{
// Acks only ranges
buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255
numAcks -= SSU2_MAX_NUM_ACNT;
numRanges++;
numPackets += SSU2_MAX_NUM_ACNT;
buf[8 + numRanges*2] = 0; // NACKs 0
if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break;
}
if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT;
buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks
numPackets += numAcks;
numRanges++;
}
if (it == m_OutOfSequencePackets.rend () &&
numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS)
{
// add range between out-of-sequence and received
int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1;
if (nacks > 0)
{
if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT;
buf[8 + numRanges*2] = nacks;
buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT);
numRanges++;
}
}
} }
} }
buf[7] = (uint8_t)acnt; // acnt buf[7] = (uint8_t)acnt; // acnt
htobe16buf (buf + 1, 5 + m_NumRanges*2); htobe16buf (buf + 1, 5 + numRanges*2);
return 8 + m_NumRanges*2; return 8 + numRanges*2;
} }
size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize)
{ {
if (len < 3 || len < minSize) return 0; if (len < 3 || len < minSize) return 0;
size_t paddingSize = m_Server.GetRng ()() & 0x1F; // 0 - 31 size_t paddingSize = m_Server.GetRng ()() & 0x0F; // 0 - 15
if (paddingSize + 3 > len) paddingSize = len - 3; if (paddingSize + 3 > len) paddingSize = len - 3;
else if (paddingSize + 3 < minSize) paddingSize = minSize - 3; else if (paddingSize + 3 < minSize) paddingSize = minSize - 3;
buf[0] = eSSU2BlkPadding; buf[0] = eSSU2BlkPadding;
@ -2891,7 +2835,7 @@ namespace transport
LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len); LogPrint (eLogError, "SSU2: Buffer for RelayResponse signature is too small ", len);
return 0; return 0;
} }
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue
if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie if (code == eSSU2RelayResponseCodeAccept || code >= 64) // Charlie
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
@ -2953,7 +2897,7 @@ namespace transport
size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); size_t asz = CreateEndpoint (signedData + 10, 86, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port));
signedData[9] = asz; signedData[9] = asz;
// signature // signature
SignedData<128> s; SignedData s;
s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue s.Insert ((const uint8_t *)"PeerTestValidate", 16); // prologue
s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash
s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint s.Insert (signedData, 10 + asz); // ver, nonce, ts, asz, Alice's endpoint
@ -3017,17 +2961,11 @@ namespace transport
} }
m_OutOfSequencePackets.erase (m_OutOfSequencePackets.begin (), it); m_OutOfSequencePackets.erase (m_OutOfSequencePackets.begin (), it);
} }
m_NumRanges = 0; // recalculate ranges when create next Ack
} }
m_ReceivePacketNum = packetNum; m_ReceivePacketNum = packetNum;
} }
else else
{
if (m_NumRanges && (m_OutOfSequencePackets.empty () ||
packetNum != (*m_OutOfSequencePackets.rbegin ()) + 1))
m_NumRanges = 0; // reset ranges if received packet is not next
m_OutOfSequencePackets.insert (packetNum); m_OutOfSequencePackets.insert (packetNum);
}
return true; return true;
} }
@ -3058,67 +2996,38 @@ namespace transport
void SSU2Session::SendPathResponse (const uint8_t * data, size_t len) void SSU2Session::SendPathResponse (const uint8_t * data, size_t len)
{ {
uint8_t payload[SSU2_MAX_PACKET_SIZE]; if (len > m_MaxPayloadSize - 3)
size_t payloadSize = 0;
// datetime block
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
payloadSize += 7;
// address block
payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint);
// path response
if (payloadSize + len > m_MaxPayloadSize)
{ {
LogPrint (eLogWarning, "SSU2: Incorrect data size for path response ", len); LogPrint (eLogWarning, "SSU2: Incorrect data size for path response ", len);
return; return;
} }
payload[payloadSize] = eSSU2BlkPathResponse; uint8_t payload[SSU2_MAX_PACKET_SIZE];
htobe16buf (payload + payloadSize + 1, len); payload[0] = eSSU2BlkPathResponse;
memcpy (payload + payloadSize + 3, data, len); htobe16buf (payload + 1, len);
payloadSize += len + 3; memcpy (payload + 3, data, len);
// ack block size_t payloadSize = len + 3;
if (payloadSize < m_MaxPayloadSize) if (payloadSize < m_MaxPayloadSize)
payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, payloadSize < 8 ? 8 : 0);
// padding
if (payloadSize < m_MaxPayloadSize)
payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
SendData (payload, payloadSize); SendData (payload, payloadSize);
} }
void SSU2Session::SendPathChallenge (const boost::asio::ip::udp::endpoint& to) void SSU2Session::SendPathChallenge ()
{ {
AdjustMaxPayloadSize (SSU2_MIN_PACKET_SIZE); // reduce to minimum
m_WindowSize = SSU2_MIN_WINDOW_SIZE; // reduce window to minimum
uint8_t payload[SSU2_MAX_PACKET_SIZE]; uint8_t payload[SSU2_MAX_PACKET_SIZE];
size_t payloadSize = 0; payload[0] = eSSU2BlkPathChallenge;
// datetime block size_t len = m_Server.GetRng ()() % (m_MaxPayloadSize - 3);
payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, len);
htobe16buf (payload + 1, 4); if (len > 0)
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); {
payloadSize += 7; RAND_bytes (payload + 3, len);
// address block with new address i2p::data::IdentHash * hash = new i2p::data::IdentHash ();
payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, to); SHA256 (payload + 3, len, *hash);
// path challenge block m_PathChallenge.reset (hash);
payload[payloadSize] = eSSU2BlkPathChallenge; }
uint64_t challenge; len += 3;
RAND_bytes ((uint8_t *)&challenge, 8); if (len < m_MaxPayloadSize)
htobe16buf (payload + payloadSize + 1, 8); // always 8 bytes len += CreatePaddingBlock (payload + len, m_MaxPayloadSize - len, len < 8 ? 8 : 0);
htobuf64 (payload + payloadSize + 3, challenge); SendData (payload, len);
payloadSize += 11;
m_PathChallenge = std::make_unique<std::pair<uint64_t, boost::asio::ip::udp::endpoint> >(challenge, to);
// ack block
if (payloadSize < m_MaxPayloadSize)
payloadSize += CreateAckBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
// padding block
if (payloadSize < m_MaxPayloadSize)
payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize);
// send to new endpoint
auto existing = m_RemoteEndpoint;
m_RemoteEndpoint = to; // send path challenge to new endpoint
SendData (payload, payloadSize);
m_RemoteEndpoint = existing; // restore endpoint back until path response received
} }
void SSU2Session::CleanUp (uint64_t ts) void SSU2Session::CleanUp (uint64_t ts)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -327,7 +327,7 @@ namespace transport
void SendQuickAck (); void SendQuickAck ();
void SendTermination (); void SendTermination ();
void SendPathResponse (const uint8_t * data, size_t len); void SendPathResponse (const uint8_t * data, size_t len);
void SendPathChallenge (const boost::asio::ip::udp::endpoint& to); void SendPathChallenge ();
void HandleDateTime (const uint8_t * buf, size_t len); void HandleDateTime (const uint8_t * buf, size_t len);
void HandleRouterInfo (const uint8_t * buf, size_t len); void HandleRouterInfo (const uint8_t * buf, size_t len);
@ -336,7 +336,7 @@ namespace transport
virtual void HandleAddress (const uint8_t * buf, size_t len); virtual void HandleAddress (const uint8_t * buf, size_t len);
size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
std::shared_ptr<const i2p::data::RouterInfo::Address> FindLocalAddress () const; std::shared_ptr<const i2p::data::RouterInfo::Address> FindLocalAddress () const;
void AdjustMaxPayloadSize (size_t maxMtu = SSU2_MAX_PACKET_SIZE); void AdjustMaxPayloadSize ();
bool GetTestingState () const; bool GetTestingState () const;
void SetTestingState(bool testing) const; void SetTestingState(bool testing) const;
std::shared_ptr<const i2p::data::RouterInfo> ExtractRouterInfo (const uint8_t * buf, size_t size); std::shared_ptr<const i2p::data::RouterInfo> ExtractRouterInfo (const uint8_t * buf, size_t size);
@ -394,11 +394,9 @@ namespace transport
boost::asio::deadline_timer m_ConnectTimer; boost::asio::deadline_timer m_ConnectTimer;
SSU2TerminationReason m_TerminationReason; SSU2TerminationReason m_TerminationReason;
size_t m_MaxPayloadSize; size_t m_MaxPayloadSize;
std::unique_ptr<std::pair<uint64_t, boost::asio::ip::udp::endpoint> > m_PathChallenge; std::unique_ptr<i2p::data::IdentHash> m_PathChallenge;
std::unordered_map<uint32_t, uint32_t> m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds std::unordered_map<uint32_t, uint32_t> m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds
uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds
int m_NumRanges;
uint8_t m_Ranges[SSU2_MAX_NUM_ACK_RANGES*2]; // ranges sent with previous Ack if any
}; };
inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce) inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -7,10 +7,6 @@
*/ */
#include <memory> #include <memory>
#include <openssl/evp.h>
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
#include <openssl/core_names.h>
#endif
#include "Log.h" #include "Log.h"
#include "Signature.h" #include "Signature.h"
@ -18,163 +14,6 @@ namespace i2p
{ {
namespace crypto namespace crypto
{ {
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
DSAVerifier::DSAVerifier ():
m_PublicKey (nullptr)
{
}
DSAVerifier::~DSAVerifier ()
{
if (m_PublicKey)
EVP_PKEY_free (m_PublicKey);
}
void DSAVerifier::SetPublicKey (const uint8_t * signingKey)
{
if (m_PublicKey)
EVP_PKEY_free (m_PublicKey);
BIGNUM * pub = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL);
m_PublicKey = CreateDSA (pub);
BN_free (pub);
}
bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20], sign[48];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
// to DER format
uint8_t * s = sign;
auto l = i2d_DSA_SIG (sig, &s);
DSA_SIG_free(sig);
// verify
auto ctx = EVP_PKEY_CTX_new (m_PublicKey, NULL);
EVP_PKEY_verify_init(ctx);
EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1());
bool ret = EVP_PKEY_verify(ctx, sign, l, digest, 20);
EVP_PKEY_CTX_free(ctx);
return ret;
}
DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
{
BIGNUM * priv = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL);
m_PrivateKey = CreateDSA (nullptr, priv);
BN_free (priv);
}
DSASigner::~DSASigner ()
{
if (m_PrivateKey)
EVP_PKEY_free (m_PrivateKey);
}
void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[20], sign[48];
SHA1 (buf, len, digest);
auto ctx = EVP_PKEY_CTX_new (m_PrivateKey, NULL);
EVP_PKEY_sign_init(ctx);
EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1());
size_t l = 48;
EVP_PKEY_sign(ctx, sign, &l, digest, 20);
const uint8_t * s1 = sign;
DSA_SIG * sig = d2i_DSA_SIG (NULL, &s1, l);
const BIGNUM * r, * s;
DSA_SIG_get0 (sig, &r, &s);
bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
DSA_SIG_free(sig);
EVP_PKEY_CTX_free(ctx);
}
void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
EVP_PKEY * paramskey = CreateDSA();
EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_from_pkey(NULL, paramskey, NULL);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY * pkey = nullptr;
EVP_PKEY_keygen(ctx, &pkey);
BIGNUM * pub = NULL, * priv = NULL;
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub);
bn2buf (pub, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv);
bn2buf (priv, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
BN_free (pub); BN_free (priv);
EVP_PKEY_free (pkey);
EVP_PKEY_free (paramskey);
EVP_PKEY_CTX_free (ctx);
}
#else
DSAVerifier::DSAVerifier ()
{
m_PublicKey = CreateDSA ();
}
DSAVerifier::~DSAVerifier ()
{
DSA_free (m_PublicKey);
}
void DSAVerifier::SetPublicKey (const uint8_t * signingKey)
{
DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL);
}
bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
// DSA verification
int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
DSA_SIG_free(sig);
return ret;
}
DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
{
m_PrivateKey = CreateDSA ();
DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL));
}
DSASigner::~DSASigner ()
{
DSA_free (m_PrivateKey);
}
void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[20];
SHA1 (buf, len, digest);
DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
const BIGNUM * r, * s;
DSA_SIG_get0 (sig, &r, &s);
bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
DSA_SIG_free(sig);
}
void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
DSA * dsa = CreateDSA ();
DSA_generate_key (dsa);
const BIGNUM * pub_key, * priv_key;
DSA_get0_key(dsa, &pub_key, &priv_key);
bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
DSA_free (dsa);
}
#endif
#if OPENSSL_EDDSA #if OPENSSL_EDDSA
EDDSA25519Verifier::EDDSA25519Verifier (): EDDSA25519Verifier::EDDSA25519Verifier ():
m_Pkey (nullptr) m_Pkey (nullptr)
@ -310,178 +149,5 @@ namespace crypto
LogPrint (eLogError, "EdDSA signing key is not set"); LogPrint (eLogError, "EdDSA signing key is not set");
} }
#endif #endif
#if (OPENSSL_VERSION_NUMBER >= 0x030000000)
static const OSSL_PARAM EDDSA25519phParams[] =
{
OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9),
OSSL_PARAM_END
};
bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
auto pkey = GetPkey ();
if (pkey)
{
uint8_t digest[64];
SHA512 (buf, len, digest);
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams);
auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64);
EVP_MD_CTX_destroy (ctx);
return ret;
}
else
LogPrint (eLogError, "EdDSA verification key is not set");
return false;
}
EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey):
EDDSA25519Signer (signingPrivateKey)
{
}
void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
auto pkey = GetPkey ();
if (pkey)
{
uint8_t digest[64];
SHA512 (buf, len, digest);
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
size_t l = 64;
uint8_t sig[64];
EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams);
if (!EVP_DigestSign (ctx, sig, &l, digest, 64))
LogPrint (eLogError, "EdDSA signing failed");
memcpy (signature, sig, 64);
EVP_MD_CTX_destroy (ctx);
}
else
LogPrint (eLogError, "EdDSA signing key is not set");
}
#endif
#if OPENSSL_PQ
MLDSA44Verifier::MLDSA44Verifier ():
m_Pkey (nullptr)
{
}
MLDSA44Verifier::~MLDSA44Verifier ()
{
EVP_PKEY_free (m_Pkey);
}
void MLDSA44Verifier::SetPublicKey (const uint8_t * signingKey)
{
if (m_Pkey)
{
EVP_PKEY_free (m_Pkey);
m_Pkey = nullptr;
}
OSSL_PARAM params[] =
{
OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)signingKey, GetPublicKeyLen ()),
OSSL_PARAM_END
};
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL);
if (ctx)
{
EVP_PKEY_fromdata_init (ctx);
EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLDSA44 can't create PKEY context");
}
bool MLDSA44Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
bool ret = false;
if (m_Pkey)
{
EVP_PKEY_CTX * vctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (vctx)
{
EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL);
if (sig)
{
int encode = 1;
OSSL_PARAM params[] =
{
OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode),
OSSL_PARAM_END
};
EVP_PKEY_verify_message_init (vctx, sig, params);
ret = EVP_PKEY_verify (vctx, signature, GetSignatureLen (), buf, len);
EVP_SIGNATURE_free (sig);
}
EVP_PKEY_CTX_free (vctx);
}
else
LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY");
}
else
LogPrint (eLogError, "MLDSA44 verification key is not set");
return ret;
}
MLDSA44Signer::MLDSA44Signer (const uint8_t * signingPrivateKey):
m_Pkey (nullptr)
{
OSSL_PARAM params[] =
{
OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PRIV_KEY, (uint8_t *)signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH),
OSSL_PARAM_END
};
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL);
if (ctx)
{
EVP_PKEY_fromdata_init (ctx);
EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params);
EVP_PKEY_CTX_free (ctx);
}
else
LogPrint (eLogError, "MLDSA44 can't create PKEY context");
}
MLDSA44Signer::~MLDSA44Signer ()
{
if (m_Pkey) EVP_PKEY_free (m_Pkey);
}
void MLDSA44Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
if (m_Pkey)
{
EVP_PKEY_CTX * sctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
if (sctx)
{
EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL);
if (sig)
{
int encode = 1;
OSSL_PARAM params[] =
{
OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode),
OSSL_PARAM_END
};
EVP_PKEY_sign_message_init (sctx, sig, params);
size_t siglen = MLDSA44_SIGNATURE_LENGTH;
EVP_PKEY_sign (sctx, signature, &siglen, buf, len);
EVP_SIGNATURE_free (sig);
}
EVP_PKEY_CTX_free (sctx);
}
else
LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY");
}
else
LogPrint (eLogError, "MLDSA44 signing key is not set");
}
#endif
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -43,55 +43,94 @@ namespace crypto
virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0;
}; };
// DSA
const size_t DSA_PUBLIC_KEY_LENGTH = 128; const size_t DSA_PUBLIC_KEY_LENGTH = 128;
const size_t DSA_SIGNATURE_LENGTH = 40; const size_t DSA_SIGNATURE_LENGTH = 40;
const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2; const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2;
class DSAVerifier: public Verifier class DSAVerifier: public Verifier
{ {
public: public:
DSAVerifier ();
~DSAVerifier ();
// implements Verifier DSAVerifier ()
void SetPublicKey (const uint8_t * signingKey) override; {
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const override; m_PublicKey = CreateDSA ();
size_t GetPublicKeyLen () const override { return DSA_PUBLIC_KEY_LENGTH; }; }
size_t GetSignatureLen () const override { return DSA_SIGNATURE_LENGTH; };
void SetPublicKey (const uint8_t * signingKey)
{
DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL);
}
~DSAVerifier ()
{
DSA_free (m_PublicKey);
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
// DSA verification
int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
DSA_SIG_free(sig);
return ret;
}
size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
private: private:
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * m_PublicKey;
#else
DSA * m_PublicKey; DSA * m_PublicKey;
#endif
}; };
class DSASigner: public Signer class DSASigner: public Signer
{ {
public: public:
DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey); DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
// openssl 1.1 always requires DSA public key even for signing // openssl 1.1 always requires DSA public key even for signing
~DSASigner (); {
m_PrivateKey = CreateDSA ();
DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL));
}
// implements Signer ~DSASigner ()
void Sign (const uint8_t * buf, int len, uint8_t * signature) const override; {
DSA_free (m_PrivateKey);
}
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
uint8_t digest[20];
SHA1 (buf, len, digest);
DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey);
const BIGNUM * r, * s;
DSA_SIG_get0 (sig, &r, &s);
bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2);
bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2);
DSA_SIG_free(sig);
}
private: private:
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
EVP_PKEY * m_PrivateKey;
#else
DSA * m_PrivateKey; DSA * m_PrivateKey;
#endif
}; };
void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey); inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
DSA * dsa = CreateDSA ();
DSA_generate_key (dsa);
const BIGNUM * pub_key, * priv_key;
DSA_get0_key(dsa, &pub_key, &priv_key);
bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH);
bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH);
DSA_free (dsa);
}
// ECDSA
struct SHA256Hash struct SHA256Hash
{ {
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
@ -122,6 +161,7 @@ namespace crypto
enum { hashLen = 64 }; enum { hashLen = 64 };
}; };
// EcDSA
template<typename Hash, int curve, size_t keyLen> template<typename Hash, int curve, size_t keyLen>
class ECDSAVerifier: public Verifier class ECDSAVerifier: public Verifier
{ {
@ -263,28 +303,14 @@ namespace crypto
private: private:
#if OPENSSL_EDDSA #if OPENSSL_EDDSA
EVP_PKEY * m_Pkey; EVP_PKEY * m_Pkey;
protected:
EVP_PKEY * GetPkey () const { return m_Pkey; };
#else #else
EDDSAPoint m_PublicKey; EDDSAPoint m_PublicKey;
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
#endif #endif
}; };
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
class EDDSA25519phVerifier: public EDDSA25519Verifier
{
public:
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
};
#endif
class EDDSA25519SignerCompat: public Signer class EDDSA25519SignerCompat: public Signer
{ {
public: public:
@ -313,10 +339,6 @@ namespace crypto
void Sign (const uint8_t * buf, int len, uint8_t * signature) const; void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
protected:
EVP_PKEY * GetPkey () const { return m_Pkey; };
private: private:
EVP_PKEY * m_Pkey; EVP_PKEY * m_Pkey;
@ -328,18 +350,6 @@ namespace crypto
#endif #endif
#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
class EDDSA25519phSigner: public EDDSA25519Signer
{
public:
EDDSA25519phSigner (const uint8_t * signingPrivateKey);
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
};
#endif
inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{ {
#if OPENSSL_EDDSA #if OPENSSL_EDDSA
@ -520,57 +530,6 @@ namespace crypto
RedDSA25519Signer signer (signingPrivateKey); RedDSA25519Signer signer (signingPrivateKey);
memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
} }
#if OPENSSL_PQ
#include <openssl/core_names.h>
// Post-Quantum
const size_t MLDSA44_PUBLIC_KEY_LENGTH = 1312;
const size_t MLDSA44_SIGNATURE_LENGTH = 2420;
const size_t MLDSA44_PRIVATE_KEY_LENGTH = 2560;
class MLDSA44Verifier: public Verifier
{
public:
MLDSA44Verifier ();
void SetPublicKey (const uint8_t * signingKey);
~MLDSA44Verifier ();
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
size_t GetPublicKeyLen () const { return MLDSA44_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return MLDSA44_SIGNATURE_LENGTH; };
size_t GetPrivateKeyLen () const { return MLDSA44_PRIVATE_KEY_LENGTH; };
private:
EVP_PKEY * m_Pkey;
};
class MLDSA44Signer: public Signer
{
public:
MLDSA44Signer (const uint8_t * signingPrivateKey);
~MLDSA44Signer ();
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
private:
EVP_PKEY * m_Pkey;
};
inline void CreateMLDSA44RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
EVP_PKEY * pkey = EVP_PKEY_Q_keygen (NULL, NULL, "ML-DSA-44");
size_t len = MLDSA44_PUBLIC_KEY_LENGTH;
EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PUB_KEY, signingPublicKey, MLDSA44_PUBLIC_KEY_LENGTH, &len);
len = MLDSA44_PRIVATE_KEY_LENGTH;
EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH, &len);
EVP_PKEY_free (pkey);
}
#endif
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -19,55 +19,39 @@ namespace i2p
{ {
namespace stream namespace stream
{ {
void SendBufferQueue::Add (std::shared_ptr<SendBuffer>&& buf) void SendBufferQueue::Add (std::shared_ptr<SendBuffer> buf)
{ {
if (buf) if (buf)
{ {
m_Buffers.push_back (buf);
m_Size += buf->len; m_Size += buf->len;
m_Buffers.push_back (std::move (buf));
} }
} }
size_t SendBufferQueue::Get (uint8_t * buf, size_t len) size_t SendBufferQueue::Get (uint8_t * buf, size_t len)
{ {
if (!m_Size) return 0;
size_t offset = 0; size_t offset = 0;
if (len >= m_Size) while (!m_Buffers.empty () && offset < len)
{ {
for (auto& it: m_Buffers) auto nextBuffer = m_Buffers.front ();
auto rem = nextBuffer->GetRemainingSize ();
if (offset + rem <= len)
{ {
auto rem = it->GetRemainingSize (); // whole buffer
memcpy (buf + offset, it->GetRemaningBuffer (), rem); memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
offset += rem; offset += rem;
m_Buffers.pop_front (); // delete it
} }
m_Buffers.clear (); else
m_Size = 0;
return offset;
}
else
{
while (!m_Buffers.empty () && offset < len)
{ {
auto nextBuffer = m_Buffers.front (); // partially
auto rem = nextBuffer->GetRemainingSize (); rem = len - offset;
if (offset + rem <= len) memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
{ nextBuffer->offset += rem;
// whole buffer offset = len; // break
memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
offset += rem;
m_Buffers.pop_front (); // delete it
}
else
{
// partially
rem = len - offset;
memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem);
nextBuffer->offset += rem;
offset = len; // break
}
} }
m_Size -= offset; }
} m_Size -= offset;
return offset; return offset;
} }
@ -75,7 +59,7 @@ namespace stream
{ {
if (!m_Buffers.empty ()) if (!m_Buffers.empty ())
{ {
for (auto& it: m_Buffers) for (auto it: m_Buffers)
it->Cancel (); it->Cancel ();
m_Buffers.clear (); m_Buffers.clear ();
m_Size = 0; m_Size = 0;
@ -96,7 +80,7 @@ namespace stream
m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO), m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_RTO (INITIAL_RTO),
m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0),
m_Jitter (0), m_MinPacingTime (0), m_Jitter (0), m_MinPacingTime (0),
m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0),
m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed
m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU)
{ {
@ -123,7 +107,7 @@ namespace stream
m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0), m_WindowSize (INITIAL_WINDOW_SIZE), m_LastWindowDropSize (0), m_WindowDropTargetSize (0), m_WindowIncCounter (0),
m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0), m_PrevRTTSample (INITIAL_RTT), m_WindowSizeTail (0), m_Jitter (0), m_MinPacingTime (0),
m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0), m_LastWindowIncTime (0), m_PacingTime (INITIAL_PACING_TIME), m_PacingTimeRem (0), m_LastSendTime (0), m_LastACKRecieveTime (0), m_ACKRecieveInterval (local.GetOwner ()->GetStreamingAckDelay ()), m_RemoteLeaseChangeTime (0),
m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed
m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU)
{ {
@ -629,8 +613,10 @@ namespace stream
if (wasInitial) if (wasInitial)
ScheduleResend (); ScheduleResend ();
} }
if (m_IsClientChoked && ackThrough >= m_DropWindowDelaySequenceNumber) if (m_IsClientChoked && ackThrough > m_DropWindowDelaySequenceNumber)
{
m_IsClientChoked = false; m_IsClientChoked = false;
}
if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber) if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber)
{ {
m_IsFirstRttSample = true; m_IsFirstRttSample = true;
@ -733,10 +719,10 @@ namespace stream
else if (handler) else if (handler)
handler(boost::system::error_code ()); handler(boost::system::error_code ());
auto s = shared_from_this (); auto s = shared_from_this ();
boost::asio::post (m_Service, [s, buffer = std::move(buffer)]() mutable boost::asio::post (m_Service, [s, buffer]()
{ {
if (buffer) if (buffer)
s->m_SendBuffer.Add (std::move(buffer)); s->m_SendBuffer.Add (buffer);
s->SendBuffer (); s->SendBuffer ();
}); });
} }
@ -1311,14 +1297,8 @@ namespace stream
m_NumPacketsToSend = 1; m_PacingTimeRem = 0; m_NumPacketsToSend = 1; m_PacingTimeRem = 0;
} }
m_IsSendTime = true; m_IsSendTime = true;
if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime && m_RTT <= m_SlowRTT) if (m_WindowIncCounter && (m_WindowSize < MAX_WINDOW_SIZE || m_WindowDropTargetSize) && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime)
{ {
float winSize = m_WindowSize;
if (m_WindowDropTargetSize)
winSize = m_WindowDropTargetSize;
float maxWinSize = MAX_WINDOW_SIZE;
if (m_LastWindowIncTime)
maxWinSize = (ts - m_LastWindowIncTime) / (m_RTT / MAX_WINDOW_SIZE_INC_PER_RTT) + winSize;
for (int i = 0; i < m_NumPacketsToSend; i++) for (int i = 0; i < m_NumPacketsToSend; i++)
{ {
if (m_WindowIncCounter) if (m_WindowIncCounter)
@ -1327,17 +1307,12 @@ namespace stream
{ {
if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize)) if (m_LastWindowDropSize && (m_LastWindowDropSize >= m_WindowDropTargetSize))
m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here m_WindowDropTargetSize += 1 - (1 / ((m_LastWindowDropSize + PREV_SPEED_KEEP_TIME_COEFF) / m_WindowDropTargetSize)); // some magic here
else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowDropTargetSize)) else if (m_LastWindowDropSize && (m_LastWindowDropSize < m_WindowSize))
m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here m_WindowDropTargetSize += (m_WindowDropTargetSize - (m_LastWindowDropSize - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; // some magic here
else else
m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize; m_WindowDropTargetSize += (m_WindowDropTargetSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowDropTargetSize;
if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE; if (m_WindowDropTargetSize > MAX_WINDOW_SIZE) m_WindowDropTargetSize = MAX_WINDOW_SIZE;
m_WindowIncCounter--; m_WindowIncCounter--;
if (m_WindowDropTargetSize >= maxWinSize)
{
m_WindowDropTargetSize = maxWinSize;
break;
}
} }
else else
{ {
@ -1349,17 +1324,11 @@ namespace stream
m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize; m_WindowSize += (m_WindowSize - (1 - PREV_SPEED_KEEP_TIME_COEFF)) / m_WindowSize;
if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
m_WindowIncCounter--; m_WindowIncCounter--;
if (m_WindowSize >= maxWinSize)
{
m_WindowSize = maxWinSize;
break;
}
} }
} }
else else
break; break;
} }
m_LastWindowIncTime = ts;
UpdatePacingTime (); UpdatePacingTime ();
} }
else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime) else if (m_WindowIncCounter && m_WindowSize == MAX_WINDOW_SIZE && !m_SendBuffer.IsEmpty () && m_PacingTime > m_MinPacingTime)
@ -1677,22 +1646,14 @@ namespace stream
void Stream::ProcessWindowDrop () void Stream::ProcessWindowDrop ()
{ {
if (m_WindowDropTargetSize) if (m_WindowSize > m_LastWindowDropSize)
m_WindowDropTargetSize = (m_WindowDropTargetSize / 2) * 0.75; // congestion window size and -25% to drain queue
else
{ {
if (m_WindowSize < m_LastWindowDropSize) m_LastWindowDropSize = (m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2;
{ if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE;
m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), (m_WindowSize - (m_LastWindowDropSize - m_WindowSize)));
if (m_LastWindowDropSize < MIN_WINDOW_SIZE) m_LastWindowDropSize = MIN_WINDOW_SIZE;
}
else
{
m_LastWindowDropSize = std::max ((m_WindowSize - MAX_WINDOW_SIZE_INC_PER_RTT), ((m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2));
if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE;
}
m_WindowDropTargetSize = m_LastWindowDropSize * 0.75; // -25% to drain queue
} }
else
m_LastWindowDropSize = m_WindowSize;
m_WindowDropTargetSize = m_LastWindowDropSize - (m_LastWindowDropSize / 4); // -25%;
if (m_WindowDropTargetSize < MIN_WINDOW_SIZE) if (m_WindowDropTargetSize < MIN_WINDOW_SIZE)
m_WindowDropTargetSize = MIN_WINDOW_SIZE; m_WindowDropTargetSize = MIN_WINDOW_SIZE;
m_WindowIncCounter = 0; // disable window growth m_WindowIncCounter = 0; // disable window growth

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -57,7 +57,6 @@ namespace stream
const int INITIAL_WINDOW_SIZE = 10; const int INITIAL_WINDOW_SIZE = 10;
const int MIN_WINDOW_SIZE = 3; const int MIN_WINDOW_SIZE = 3;
const int MAX_WINDOW_SIZE = 512; const int MAX_WINDOW_SIZE = 512;
const int MAX_WINDOW_SIZE_INC_PER_RTT = 16;
const double RTT_EWMA_ALPHA = 0.25; const double RTT_EWMA_ALPHA = 0.25;
const double SLOWRTT_EWMA_ALPHA = 0.05; const double SLOWRTT_EWMA_ALPHA = 0.05;
const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer
@ -150,7 +149,7 @@ namespace stream
SendBufferQueue (): m_Size (0) {}; SendBufferQueue (): m_Size (0) {};
~SendBufferQueue () { CleanUp (); }; ~SendBufferQueue () { CleanUp (); };
void Add (std::shared_ptr<SendBuffer>&& buf); void Add (std::shared_ptr<SendBuffer> buf);
size_t Get (uint8_t * buf, size_t len); size_t Get (uint8_t * buf, size_t len);
size_t GetSize () const { return m_Size; }; size_t GetSize () const { return m_Size; };
bool IsEmpty () const { return m_Buffers.empty (); }; bool IsEmpty () const { return m_Buffers.empty (); };
@ -300,7 +299,7 @@ namespace stream
int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail; int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail;
double m_Jitter; double m_Jitter;
uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds
m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime, m_LastWindowIncTime; // milliseconds m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime; // milliseconds
uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed
int m_NumResendAttempts, m_NumPacketsToSend; int m_NumResendAttempts, m_NumPacketsToSend;
size_t m_MTU; size_t m_MTU;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2023, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -12,14 +12,10 @@
#include <boost/static_assert.hpp> #include <boost/static_assert.hpp>
#include <string.h> #include <string.h>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <string>
#include <string_view>
#include "Base.h" #include "Base.h"
namespace i2p namespace i2p {
{ namespace data {
namespace data
{
template<size_t sz> template<size_t sz>
class Tag class Tag
{ {
@ -62,22 +58,26 @@ namespace data
std::string ToBase64 (size_t len = sz) const std::string ToBase64 (size_t len = sz) const
{ {
return i2p::data::ByteStreamToBase64 (m_Buf, len); char str[sz*2];
size_t l = i2p::data::ByteStreamToBase64 (m_Buf, len, str, sz*2);
return std::string (str, str + l);
} }
std::string ToBase32 (size_t len = sz) const std::string ToBase32 (size_t len = sz) const
{ {
return i2p::data::ByteStreamToBase32 (m_Buf, len); char str[sz*2];
size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2);
return std::string (str, str + l);
} }
size_t FromBase32 (std::string_view s) size_t FromBase32 (const std::string& s)
{ {
return i2p::data::Base32ToByteStream (s, m_Buf, sz); return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
} }
size_t FromBase64 (std::string_view s) size_t FromBase64 (const std::string& s)
{ {
return i2p::data::Base64ToByteStream (s, m_Buf, sz); return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
} }
uint8_t GetBit (int i) const uint8_t GetBit (int i) const

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -101,13 +101,13 @@ namespace tunnel
TunnelMessageBlock block; TunnelMessageBlock block;
block.deliveryType = eDeliveryTypeLocal; block.deliveryType = eDeliveryTypeLocal;
block.data = msg; block.data = msg;
std::lock_guard<std::mutex> l(m_SendMutex); std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.PutTunnelDataMsg (block); m_Gateway.PutTunnelDataMsg (block);
} }
void TransitTunnelGateway::FlushTunnelDataMsgs () void TransitTunnelGateway::FlushTunnelDataMsgs ()
{ {
std::lock_guard<std::mutex> l(m_SendMutex); std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.SendBuffer (); m_Gateway.SendBuffer ();
} }
@ -130,36 +130,20 @@ namespace tunnel
EncryptTunnelMsg (tunnelMsg, newMsg); EncryptTunnelMsg (tunnelMsg, newMsg);
LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
std::lock_guard<std::mutex> l(m_HandleMutex); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
if (!m_Endpoint) m_Endpoint = std::make_unique<TunnelEndpoint>(false); // transit endpoint is always outbound
m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg);
} }
void TransitTunnelEndpoint::FlushTunnelDataMsgs () void TransitTunnelEndpoint::FlushTunnelDataMsgs ()
{ {
if (m_Endpoint) m_Endpoint.FlushI2NPMsgs ();
{
std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint->FlushI2NPMsgs ();
}
} }
void TransitTunnelEndpoint::Cleanup ()
{
if (m_Endpoint)
{
std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint->Cleanup ();
}
}
std::string TransitTunnelEndpoint::GetNextPeerName () const std::string TransitTunnelEndpoint::GetNextPeerName () const
{ {
if (!m_Endpoint) return ""; auto hash = m_Endpoint.GetCurrentHash ();
auto hash = m_Endpoint->GetCurrentHash ();
if (hash) if (hash)
{ {
const auto& sender = m_Endpoint->GetSender (); const auto& sender = m_Endpoint.GetSender ();
if (sender) if (sender)
{ {
auto transport = sender->GetCurrentTransport (); auto transport = sender->GetCurrentTransport ();
@ -196,7 +180,7 @@ namespace tunnel
} }
TransitTunnels::TransitTunnels (): TransitTunnels::TransitTunnels ():
m_IsRunning (false), m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) m_IsRunning (false)
{ {
} }
@ -336,44 +320,19 @@ namespace tunnel
// check if we accept this tunnel // check if we accept this tunnel
std::shared_ptr<i2p::tunnel::TransitTunnel> transitTunnel; std::shared_ptr<i2p::tunnel::TransitTunnel> transitTunnel;
uint8_t retCode = 0; uint8_t retCode = 0;
if (i2p::context.AcceptsTunnels ()) if (!i2p::context.AcceptsTunnels () || i2p::context.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL)
{
auto congestionLevel = i2p::context.GetCongestionLevel (false);
if (congestionLevel < CONGESTION_LEVEL_FULL)
{
if (congestionLevel >= CONGESTION_LEVEL_MEDIUM)
{
// random reject depending on congestion level
int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM;
if (congestionLevel > level)
retCode = 30;
}
}
else
retCode = 30;
}
else
retCode = 30; retCode = 30;
if (!retCode) if (!retCode)
{ {
i2p::data::IdentHash nextIdent(clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET); // create new transit tunnel
bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; transitTunnel = i2p::tunnel::CreateTransitTunnel (
if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent)) bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
{ clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
// create new transit tunnel bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
transitTunnel = CreateTransitTunnel ( layerKey, ivKey,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
nextIdent, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), if (!AddTransitTunnel (transitTunnel))
layerKey, ivKey,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
isEndpoint);
if (!AddTransitTunnel (transitTunnel))
retCode = 30;
}
else
// decline tunnel going to duplicated router
retCode = 30; retCode = 30;
} }
@ -485,7 +444,7 @@ namespace tunnel
if (congestionLevel < CONGESTION_LEVEL_FULL) if (congestionLevel < CONGESTION_LEVEL_FULL)
{ {
// random reject depending on congestion level // random reject depending on congestion level
int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; int level = i2p::tunnel::tunnels.GetRng ()() % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM;
if (congestionLevel > level) if (congestionLevel > level)
accept = false; accept = false;
} }
@ -493,32 +452,23 @@ namespace tunnel
accept = false; accept = false;
} }
} }
// replace record to reply
if (accept) if (accept)
{ {
i2p::data::IdentHash nextIdent(clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET); auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
bool isEndpoint = clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent)) clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
{ bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
auto transitTunnel = CreateTransitTunnel ( clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
nextIdent, clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, if (!AddTransitTunnel (transitTunnel))
clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
isEndpoint);
if (!AddTransitTunnel (transitTunnel))
retCode = 30;
}
else
// decline tunnel going to duplicated router
retCode = 30; retCode = 30;
} }
else else
retCode = 30; // always reject with bandwidth reason (30) retCode = 30; // always reject with bandwidth reason (30)
// replace record to reply
memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode;
// encrypt reply // encrypt reply

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -97,19 +97,19 @@ namespace tunnel
TransitTunnelEndpoint (uint32_t receiveTunnelID, TransitTunnelEndpoint (uint32_t receiveTunnelID,
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey) {}; TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
m_Endpoint (false) {}; // transit endpoint is always outbound
void Cleanup () override; void Cleanup () override { m_Endpoint.Cleanup (); }
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override; void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
void FlushTunnelDataMsgs () override; void FlushTunnelDataMsgs () override;
size_t GetNumTransmittedBytes () const override { return m_Endpoint ? m_Endpoint->GetNumReceivedBytes () : 0; } size_t GetNumTransmittedBytes () const override { return m_Endpoint.GetNumReceivedBytes (); }
std::string GetNextPeerName () const override; std::string GetNextPeerName () const override;
private: private:
std::mutex m_HandleMutex; TunnelEndpoint m_Endpoint;
std::unique_ptr<TunnelEndpoint> m_Endpoint;
}; };
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID, std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
@ -151,7 +151,6 @@ namespace tunnel
std::unique_ptr<std::thread> m_Thread; std::unique_ptr<std::thread> m_Thread;
std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels; std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_TunnelBuildMsgQueue; i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_TunnelBuildMsgQueue;
std::mt19937 m_Rng;
public: public:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -10,7 +10,7 @@
#define TRANSPORT_SESSION_H__ #define TRANSPORT_SESSION_H__
#include <inttypes.h> #include <inttypes.h>
#include <string.h> #include <iostream>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <mutex> #include <mutex>
@ -28,51 +28,45 @@ namespace transport
const size_t IPV6_HEADER_SIZE = 40; const size_t IPV6_HEADER_SIZE = 40;
const size_t UDP_HEADER_SIZE = 8; const size_t UDP_HEADER_SIZE = 8;
template<size_t sz>
class SignedData class SignedData
{ {
public: public:
SignedData (): m_Size(0) {} SignedData () {}
SignedData (const SignedData& other) SignedData (const SignedData& other)
{ {
m_Size = other.m_Size; m_Stream << other.m_Stream.rdbuf ();
memcpy (m_Buf, other.m_Buf, m_Size);
} }
void Reset () void Reset ()
{ {
m_Size = 0; m_Stream.str("");
} }
size_t Insert (const uint8_t * buf, size_t len) void Insert (const uint8_t * buf, size_t len)
{ {
if (m_Size + len > sz) len = sz - m_Size; m_Stream.write ((char *)buf, len);
memcpy (m_Buf + m_Size, buf, len);
m_Size += len;
return len;
} }
template<typename T> template<typename T>
void Insert (T t) void Insert (T t)
{ {
Insert ((const uint8_t *)&t, sizeof (T)); m_Stream.write ((char *)&t, sizeof (T));
} }
bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const
{ {
return ident->Verify (m_Buf, m_Size, signature); return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
} }
void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const
{ {
keys.Sign (m_Buf, m_Size, signature); keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
} }
private: private:
uint8_t m_Buf[sz]; std::stringstream m_Stream;
size_t m_Size;
}; };
const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -160,8 +160,7 @@ namespace transport
m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0), m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0),
m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0),
m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0), m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0)
m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
{ {
} }
@ -339,7 +338,7 @@ namespace transport
if (m_IsNAT) if (m_IsNAT)
{ {
m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
} }
} }
@ -561,14 +560,7 @@ namespace transport
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer) bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer)
{ {
if (!peer->router) // reconnect if (!peer->router) // reconnect
{ peer->SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb
auto r = netdb.FindRouter (ident); // try to get new one from netdb
if (r)
{
peer->SetRouter (r);
r->CancelBufferToDelete ();
}
}
if (peer->router) // we have RI already if (peer->router) // we have RI already
{ {
if (peer->priority.empty ()) if (peer->priority.empty ())
@ -655,7 +647,7 @@ namespace transport
return true; return true;
} }
void Transports::SetPriority (std::shared_ptr<Peer> peer) void Transports::SetPriority (std::shared_ptr<Peer> peer) const
{ {
static const std::vector<i2p::data::RouterInfo::SupportedTransports> static const std::vector<i2p::data::RouterInfo::SupportedTransports>
ntcp2Priority = ntcp2Priority =
@ -680,21 +672,8 @@ namespace transport
auto directTransports = compatibleTransports & peer->router->GetPublishedTransports (); auto directTransports = compatibleTransports & peer->router->GetPublishedTransports ();
peer->numAttempts = 0; peer->numAttempts = 0;
peer->priority.clear (); peer->priority.clear ();
bool isReal = peer->router->GetProfile ()->IsReal ();
std::shared_ptr<RouterProfile> profile; bool ssu2 = isReal ? (rand () & 1) : false; // try NTCP2 if router is not confirmed real
if (peer->router->HasProfile ()) profile = peer->router->GetProfile (); // only if in memory
bool ssu2 = false; // NTCP2 by default
bool isReal = profile ? profile->IsReal () : true;
if (isReal)
{
ssu2 = m_Rng () & 1; // 1/2
if (ssu2 && !profile)
{
profile = peer->router->GetProfile (); // load profile if necessary
isReal = profile->IsReal ();
if (!isReal) ssu2 = false; // try NTCP2 if router is not confirmed real
}
}
const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority; const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority;
if (directTransports) if (directTransports)
{ {
@ -722,8 +701,7 @@ namespace transport
// try recently connected SSU2 if any // try recently connected SSU2 if any
auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) &
peer->router->GetCompatibleTransports (false); peer->router->GetCompatibleTransports (false);
if ((supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) && if (supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6))
peer->router->HasProfile ())
{ {
auto ep = peer->router->GetProfile ()->GetLastEndpoint (); auto ep = peer->router->GetProfile ()->GetLastEndpoint ();
if (!ep.address ().is_unspecified () && ep.port ()) if (!ep.address ().is_unspecified () && ep.port ())
@ -813,7 +791,7 @@ namespace transport
} }
else else
{ {
testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
if (m_Service) if (m_Service)
{ {
auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service); auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@ -851,7 +829,7 @@ namespace transport
} }
else else
{ {
testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
if (m_Service) if (m_Service)
{ {
auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service); auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@ -908,11 +886,7 @@ namespace transport
auto transport = peer->priority[peer->numAttempts-1]; auto transport = peer->priority[peer->numAttempts-1];
if (transport == i2p::data::RouterInfo::eNTCP2V4 || if (transport == i2p::data::RouterInfo::eNTCP2V4 ||
transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh) transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh)
i2p::data::UpdateRouterProfile (ident, peer->router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Connected (); // outgoing NTCP2 connection if always real
});
i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable
} }
peer->numAttempts = 0; peer->numAttempts = 0;
@ -947,11 +921,7 @@ namespace transport
session->SendI2NPMessages (msgs); // send DatabaseStore session->SendI2NPMessages (msgs); // send DatabaseStore
} }
auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed
i2p::data::UpdateRouterProfile (ident, if (r) r->GetProfile ()->Connected ();
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Connected ();
});
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
auto peer = std::make_shared<Peer>(r, ts); auto peer = std::make_shared<Peer>(r, ts);
peer->sessions.push_back (session); peer->sessions.push_back (session);
@ -985,13 +955,8 @@ namespace transport
} }
else else
{ {
{ std::lock_guard<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex); m_Peers.erase (it);
m_Peers.erase (it);
}
// delete buffer of just disconnected router
auto r = i2p::data::netdb.FindRouter (ident);
if (r && !r->IsUpdated ()) r->ScheduleBufferToDelete ();
} }
} }
} }
@ -1041,7 +1006,7 @@ namespace transport
if (session) if (session)
session->SendLocalRouterInfo (true); session->SendLocalRouterInfo (true);
it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL +
m_Rng() % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE;
} }
++it; ++it;
} }
@ -1055,7 +1020,7 @@ namespace transport
// if still testing or unknown, repeat peer test // if still testing or unknown, repeat peer test
if (ipv4Testing || ipv6Testing) if (ipv4Testing || ipv6Testing)
PeerTest (ipv4Testing, ipv6Testing); PeerTest (ipv4Testing, ipv6Testing);
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(2 * SESSION_CREATION_TIMEOUT + m_Rng() % SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3 * SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
} }
} }
@ -1065,7 +1030,7 @@ namespace transport
if (ecode != boost::asio::error::operation_aborted) if (ecode != boost::asio::error::operation_aborted)
{ {
PeerTest (); PeerTest ();
m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
} }
} }
@ -1195,7 +1160,7 @@ namespace transport
std::lock_guard<std::mutex> lock(m_TrustedRoutersMutex); std::lock_guard<std::mutex> lock(m_TrustedRoutersMutex);
m_TrustedRouters.clear(); m_TrustedRouters.clear();
for (const auto & ri : routers ) for (const auto & ri : routers )
m_TrustedRouters.insert(ri); m_TrustedRouters.push_back(ri);
} }
bool Transports::RoutesRestricted() const bool Transports::RoutesRestricted() const
@ -1212,7 +1177,7 @@ namespace transport
} }
/** XXX: if routes are not restricted this dies */ /** XXX: if routes are not restricted this dies */
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() const
{ {
{ {
std::lock_guard<std::mutex> l(m_FamilyMutex); std::lock_guard<std::mutex> l(m_FamilyMutex);
@ -1221,7 +1186,7 @@ namespace transport
if(sz > 1) if(sz > 1)
{ {
auto it = m_TrustedFamilies.begin (); auto it = m_TrustedFamilies.begin ();
std::advance(it, m_Rng() % sz); std::advance(it, rand() % sz);
fam = *it; fam = *it;
} }
else if (sz == 1) else if (sz == 1)
@ -1236,32 +1201,23 @@ namespace transport
auto sz = m_TrustedRouters.size(); auto sz = m_TrustedRouters.size();
if (sz) if (sz)
{ {
if(sz == 1)
return i2p::data::netdb.FindRouter(m_TrustedRouters[0]);
auto it = m_TrustedRouters.begin(); auto it = m_TrustedRouters.begin();
if(sz > 1) std::advance(it, rand() % sz);
std::advance(it, m_Rng() % sz);
return i2p::data::netdb.FindRouter(*it); return i2p::data::netdb.FindRouter(*it);
} }
} }
return nullptr; return nullptr;
} }
bool Transports::IsTrustedRouter (const i2p::data::IdentHash& ih) const bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const
{ {
if (m_TrustedRouters.empty ()) return false; {
std::lock_guard<std::mutex> l(m_TrustedRoutersMutex); std::lock_guard<std::mutex> l(m_TrustedRoutersMutex);
#if __cplusplus >= 202002L // C++20 for (const auto & r : m_TrustedRouters )
if (m_TrustedRouters.contains (ih)) if ( r == ih ) return true;
#else }
if (m_TrustedRouters.count (ih) > 0)
#endif
return true;
return false;
}
bool Transports::IsRestrictedPeer(const i2p::data::IdentHash& ih) const
{
if (IsTrustedRouter (ih)) return true;
{ {
std::lock_guard<std::mutex> l(m_FamilyMutex); std::lock_guard<std::mutex> l(m_FamilyMutex);
auto ri = i2p::data::netdb.FindRouter(ih); auto ri = i2p::data::netdb.FindRouter(ih);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -15,13 +15,11 @@
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
#include <queue> #include <queue>
#include <string> #include <string>
#include <memory> #include <memory>
#include <atomic> #include <atomic>
#include <random>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "TransportSession.h" #include "TransportSession.h"
#include "SSU2.h" #include "SSU2.h"
@ -108,8 +106,7 @@ namespace transport
}; };
const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds
const int PEER_TEST_INTERVAL = 68*60; // in seconds const int PEER_TEST_INTERVAL = 71; // in minutes
const int PEER_TEST_INTERVAL_VARIANCE = 3*60; // in seconds
const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds
const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds
const int MAX_NUM_DELAYED_MESSAGES = 150; const int MAX_NUM_DELAYED_MESSAGES = 150;
@ -171,7 +168,7 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer (bool isHighBandwidth) const; std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer (bool isHighBandwidth) const;
/** get a trusted first hop for restricted routes */ /** get a trusted first hop for restricted routes */
std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer(); std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer() const;
/** do we want to use restricted routes? */ /** do we want to use restricted routes? */
bool RoutesRestricted() const; bool RoutesRestricted() const;
/** restrict routes to use only these router families for first hops */ /** restrict routes to use only these router families for first hops */
@ -179,8 +176,7 @@ namespace transport
/** restrict routes to use only these routers for first hops */ /** restrict routes to use only these routers for first hops */
void RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers); void RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers);
bool IsTrustedRouter (const i2p::data::IdentHash& ih) const; bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const;
bool IsRestrictedPeer(const i2p::data::IdentHash& ih) const;
void PeerTest (bool ipv4 = true, bool ipv6 = true); void PeerTest (bool ipv4 = true, bool ipv6 = true);
@ -195,7 +191,7 @@ namespace transport
void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident); void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident);
std::shared_ptr<TransportSession> PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs); std::shared_ptr<TransportSession> PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs);
bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer); bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer);
void SetPriority (std::shared_ptr<Peer> peer); void SetPriority (std::shared_ptr<Peer> peer) const;
void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
void HandlePeerTestTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode);
void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode); void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode);
@ -239,11 +235,10 @@ namespace transport
mutable std::mutex m_FamilyMutex; mutable std::mutex m_FamilyMutex;
/** which routers for first hop to trust */ /** which routers for first hop to trust */
std::unordered_set<i2p::data::IdentHash> m_TrustedRouters; std::vector<i2p::data::IdentHash> m_TrustedRouters;
mutable std::mutex m_TrustedRoutersMutex; mutable std::mutex m_TrustedRoutersMutex;
i2p::I2NPMessagesHandler m_LoopbackHandler; i2p::I2NPMessagesHandler m_LoopbackHandler;
std::mt19937 m_Rng;
public: public:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -103,7 +103,7 @@ namespace tunnel
if (m_Config->IsShort ()) if (m_Config->IsShort ())
{ {
auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr;
if (ident && ident->GetIdentHash () != outboundTunnel->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP
{ {
auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ());
if (msg1) msg = msg1; if (msg1) msg = msg1;
@ -179,12 +179,9 @@ namespace tunnel
{ {
uint8_t ret = hop->GetRetCode (msg + 1); uint8_t ret = hop->GetRetCode (msg + 1);
LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret);
if (hop->ident) auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), if (profile)
[ret](std::shared_ptr<i2p::data::RouterProfile> profile) profile->TunnelBuildResponse (ret);
{
if (profile) profile->TunnelBuildResponse (ret);
});
if (ret) if (ret)
// if any of participants declined the tunnel is not established // if any of participants declined the tunnel is not established
established = false; established = false;
@ -281,21 +278,6 @@ namespace tunnel
m_Endpoint.HandleDecryptedTunnelDataMsg (msg); m_Endpoint.HandleDecryptedTunnelDataMsg (msg);
} }
bool InboundTunnel::Recreate ()
{
if (!IsRecreated ())
{
auto pool = GetTunnelPool ();
if (pool)
{
SetRecreated (true);
pool->RecreateInboundTunnel (std::static_pointer_cast<InboundTunnel>(shared_from_this ()));
return true;
}
}
return false;
}
ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): ZeroHopsInboundTunnel::ZeroHopsInboundTunnel ():
InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()), InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
m_NumReceivedBytes (0) m_NumReceivedBytes (0)
@ -315,28 +297,22 @@ namespace tunnel
void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg) void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg)
{ {
TunnelMessageBlock block; TunnelMessageBlock block;
block.tunnelID = 0; // Initialize tunnelID to a default value
if (gwHash) if (gwHash)
{ {
block.hash = gwHash; block.hash = gwHash;
if (gwTunnel) if (gwTunnel)
{ {
block.deliveryType = eDeliveryTypeTunnel; block.deliveryType = eDeliveryTypeTunnel;
block.tunnelID = gwTunnel; // Set tunnelID only if gwTunnel is non-zero block.tunnelID = gwTunnel;
} }
else else
{
block.deliveryType = eDeliveryTypeRouter; block.deliveryType = eDeliveryTypeRouter;
}
} }
else else
{
block.deliveryType = eDeliveryTypeLocal; block.deliveryType = eDeliveryTypeLocal;
}
block.data = msg; block.data = msg;
SendTunnelDataMsgs({block});
SendTunnelDataMsgs ({block});
} }
void OutboundTunnel::SendTunnelDataMsgs (const std::vector<TunnelMessageBlock>& msgs) void OutboundTunnel::SendTunnelDataMsgs (const std::vector<TunnelMessageBlock>& msgs)
@ -352,21 +328,6 @@ namespace tunnel
LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ()); LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ());
} }
bool OutboundTunnel::Recreate ()
{
if (!IsRecreated ())
{
auto pool = GetTunnelPool ();
if (pool)
{
SetRecreated (true);
pool->RecreateOutboundTunnel (std::static_pointer_cast<OutboundTunnel>(shared_from_this ()));
return true;
}
}
return false;
}
ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel ():
OutboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()), OutboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
m_NumSentBytes (0) m_NumSentBytes (0)
@ -473,7 +434,7 @@ namespace tunnel
std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel () std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel ()
{ {
if (m_OutboundTunnels.empty ()) return nullptr; if (m_OutboundTunnels.empty ()) return nullptr;
uint32_t ind = m_Rng () % m_OutboundTunnels.size (), i = 0; uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0;
std::shared_ptr<OutboundTunnel> tunnel; std::shared_ptr<OutboundTunnel> tunnel;
for (const auto& it: m_OutboundTunnels) for (const auto& it: m_OutboundTunnels)
{ {
@ -750,17 +711,8 @@ namespace tunnel
void Tunnels::ManageTunnels (uint64_t ts) void Tunnels::ManageTunnels (uint64_t ts)
{ {
ManagePendingTunnels (ts); ManagePendingTunnels (ts);
std::vector<std::shared_ptr<Tunnel> > tunnelsToRecreate; ManageInboundTunnels (ts);
ManageInboundTunnels (ts, tunnelsToRecreate); ManageOutboundTunnels (ts);
ManageOutboundTunnels (ts, tunnelsToRecreate);
// rec-create in random order
if (!tunnelsToRecreate.empty ())
{
if (tunnelsToRecreate.size () > 1)
std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng);
for (auto& it: tunnelsToRecreate)
it->Recreate ();
}
} }
void Tunnels::ManagePendingTunnels (uint64_t ts) void Tunnels::ManagePendingTunnels (uint64_t ts)
@ -791,11 +743,11 @@ namespace tunnel
while (hop) while (hop)
{ {
if (hop->ident) if (hop->ident)
i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), {
[](std::shared_ptr<i2p::data::RouterProfile> profile) auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
{ if (profile)
if (profile) profile->TunnelNonReplied (); profile->TunnelNonReplied ();
}); }
hop = hop->next; hop = hop->next;
} }
} }
@ -823,7 +775,7 @@ namespace tunnel
} }
} }
void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate) void Tunnels::ManageOutboundTunnels (uint64_t ts)
{ {
for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
{ {
@ -847,7 +799,10 @@ namespace tunnel
auto pool = tunnel->GetTunnelPool (); auto pool = tunnel->GetTunnelPool ();
// let it die if the tunnel pool has been reconfigured and this is old // let it die if the tunnel pool has been reconfigured and this is old
if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops())
toRecreate.push_back (tunnel); {
tunnel->SetRecreated (true);
pool->RecreateOutboundTunnel (tunnel);
}
} }
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
tunnel->SetState (eTunnelStateExpiring); tunnel->SetState (eTunnelStateExpiring);
@ -872,7 +827,7 @@ namespace tunnel
} }
} }
void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate) void Tunnels::ManageInboundTunnels (uint64_t ts)
{ {
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
{ {
@ -896,7 +851,10 @@ namespace tunnel
auto pool = tunnel->GetTunnelPool (); auto pool = tunnel->GetTunnelPool ();
// let it die if the tunnel pool was reconfigured and has different number of hops // let it die if the tunnel pool was reconfigured and has different number of hops
if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops())
toRecreate.push_back (tunnel); {
tunnel->SetRecreated (true);
pool->RecreateInboundTunnel (tunnel);
}
} }
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -99,7 +99,6 @@ namespace tunnel
void SetRecreated (bool recreated) { m_IsRecreated = recreated; }; void SetRecreated (bool recreated) { m_IsRecreated = recreated; };
int GetNumHops () const { return m_Hops.size (); }; int GetNumHops () const { return m_Hops.size (); };
virtual bool IsInbound() const = 0; virtual bool IsInbound() const = 0;
virtual bool Recreate () = 0;
std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; }; std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; }; void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; };
@ -151,7 +150,6 @@ namespace tunnel
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override; void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
bool IsInbound() const override { return false; } bool IsInbound() const override { return false; }
bool Recreate () override;
private: private:
@ -168,7 +166,6 @@ namespace tunnel
void HandleTunnelDataMsg (std::shared_ptr<I2NPMessage>&& msg) override; void HandleTunnelDataMsg (std::shared_ptr<I2NPMessage>&& msg) override;
virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
bool IsInbound() const override { return true; } bool IsInbound() const override { return true; }
bool Recreate () override;
// override TunnelBase // override TunnelBase
void Cleanup () override { m_Endpoint.Cleanup (); }; void Cleanup () override { m_Endpoint.Cleanup (); };
@ -248,6 +245,8 @@ namespace tunnel
void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels); void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels);
uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; }; uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; };
int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; } int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; }
std::mt19937& GetRng () { return m_Rng; };
private: private:
@ -265,8 +264,8 @@ namespace tunnel
void Run (); void Run ();
void ManageTunnels (uint64_t ts); void ManageTunnels (uint64_t ts);
void ManageOutboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate); void ManageOutboundTunnels (uint64_t ts);
void ManageInboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate); void ManageInboundTunnels (uint64_t ts);
void ManagePendingTunnels (uint64_t ts); void ManagePendingTunnels (uint64_t ts);
template<class PendingTunnels> template<class PendingTunnels>
void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -258,8 +258,9 @@ namespace tunnel
void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum,
bool isLastFragment, const uint8_t * fragment, size_t size) bool isLastFragment, const uint8_t * fragment, size_t size)
{ {
if (!m_OutOfSequenceFragments.try_emplace ((uint64_t)msgID << 32 | fragmentNum, std::unique_ptr<Fragment> f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size));
isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), fragment, size).second) memcpy (f->data.data (), fragment, size);
if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second)
LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID);
} }
@ -289,7 +290,7 @@ namespace tunnel
if (it != m_OutOfSequenceFragments.end ()) if (it != m_OutOfSequenceFragments.end ())
{ {
LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found");
size_t size = it->second.data.size (); size_t size = it->second->data.size ();
if (msg.data->len + size > msg.data->maxLen) if (msg.data->len + size > msg.data->maxLen)
{ {
LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough");
@ -297,9 +298,9 @@ namespace tunnel
*newMsg = *(msg.data); *newMsg = *(msg.data);
msg.data = newMsg; msg.data = newMsg;
} }
if (msg.data->Concat (it->second.data.data (), size) < size) // concatenate out-of-sync fragment if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment
LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen);
if (it->second.isLastFragment) if (it->second->isLastFragment)
// message complete // message complete
msg.nextFragmentNum = 0; msg.nextFragmentNum = 0;
else else
@ -348,7 +349,7 @@ namespace tunnel
// out-of-sequence fragments // out-of-sequence fragments
for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();)
{ {
if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT)
it = m_OutOfSequenceFragments.erase (it); it = m_OutOfSequenceFragments.erase (it);
else else
++it; ++it;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -32,8 +32,7 @@ namespace tunnel
struct Fragment struct Fragment
{ {
Fragment (bool last, uint64_t t, const uint8_t * buf, size_t size): Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {};
isLastFragment (last), receiveTime (t), data (size) { memcpy (data.data(), buf, size); };
bool isLastFragment; bool isLastFragment;
uint64_t receiveTime; // milliseconds since epoch uint64_t receiveTime; // milliseconds since epoch
std::vector<uint8_t> data; std::vector<uint8_t> data;
@ -49,7 +48,7 @@ namespace tunnel
void HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg); void HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg);
void FlushI2NPMsgs (); void FlushI2NPMsgs ();
const i2p::data::IdentHash * GetCurrentHash () const; // return null if not available const i2p::data::IdentHash * GetCurrentHash () const; // return null if not avaiable
const std::unique_ptr<TunnelTransportSender>& GetSender () const { return m_Sender; }; const std::unique_ptr<TunnelTransportSender>& GetSender () const { return m_Sender; };
private: private:
@ -68,7 +67,7 @@ namespace tunnel
private: private:
std::unordered_map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages; std::unordered_map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages;
std::unordered_map<uint64_t, Fragment> m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment std::unordered_map<uint64_t, std::unique_ptr<Fragment> > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment
bool m_IsInbound; bool m_IsInbound;
size_t m_NumReceivedBytes; size_t m_NumReceivedBytes;
TunnelMessageBlockEx m_CurrentMessage; TunnelMessageBlockEx m_CurrentMessage;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@ -351,13 +351,10 @@ namespace tunnel
{ {
it.second.first->SetState (eTunnelStateFailed); it.second.first->SetState (eTunnelStateFailed);
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex); std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
if (m_OutboundTunnels.size () > 1) // don't fail last tunnel if (m_OutboundTunnels.size () > 1 || m_NumOutboundTunnels <= 1) // don't fail last tunnel
m_OutboundTunnels.erase (it.second.first); m_OutboundTunnels.erase (it.second.first);
else else
{
it.second.first->SetState (eTunnelStateTestFailed); it.second.first->SetState (eTunnelStateTestFailed);
CreateOutboundTunnel (); // create new tunnel immediately because last one failed
}
} }
else if (it.second.first->GetState () != eTunnelStateExpiring) else if (it.second.first->GetState () != eTunnelStateExpiring)
it.second.first->SetState (eTunnelStateTestFailed); it.second.first->SetState (eTunnelStateTestFailed);
@ -371,16 +368,13 @@ namespace tunnel
bool failed = false; bool failed = false;
{ {
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex); std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
if (m_InboundTunnels.size () > 1) // don't fail last tunnel if (m_InboundTunnels.size () > 1 || m_NumInboundTunnels <= 1) // don't fail last tunnel
{ {
m_InboundTunnels.erase (it.second.second); m_InboundTunnels.erase (it.second.second);
failed = true; failed = true;
} }
else else
{
it.second.second->SetState (eTunnelStateTestFailed); it.second.second->SetState (eTunnelStateTestFailed);
CreateInboundTunnel (); // create new tunnel immediately because last one failed
}
} }
if (failed && m_LocalDestination) if (failed && m_LocalDestination)
m_LocalDestination->SetLeaseSetUpdated (true); m_LocalDestination->SetLeaseSetUpdated (true);
@ -566,7 +560,7 @@ namespace tunnel
i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false); i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false);
if (hop) if (hop)
{ {
if (!hop->HasProfile () || !hop->GetProfile ()->IsBad ()) if (!hop->GetProfile ()->IsBad ())
break; break;
} }
else if (tryClient) else if (tryClient)
@ -594,7 +588,7 @@ namespace tunnel
(inbound && i2p::transport::transports.GetNumPeers () > 25)) (inbound && i2p::transport::transports.GetNumPeers () > 25))
{ {
auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ()); auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ());
if (r && r->IsECIES () && (!r->HasProfile () || !r->GetProfile ()->IsBad ()) && if (r && r->IsECIES () && !r->GetProfile ()->IsBad () &&
(numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4 (numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4
{ {
prevHop = r; prevHop = r;

Some files were not shown because too many files have changed in this diff Show more