mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2026-07-28 07:59:22 +00:00
always send short tunnel build message
This commit is contained in:
+8
-13
@@ -31,7 +31,7 @@ namespace tunnel
|
||||
{
|
||||
Tunnel::Tunnel (std::shared_ptr<TunnelConfig> config):
|
||||
TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()),
|
||||
m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr),
|
||||
m_Config (config), m_Pool (nullptr),
|
||||
m_State (eTunnelStatePending), m_FarEndTransports (i2p::data::RouterInfo::eAllTransports),
|
||||
m_IsRecreated (false), m_Latency (UNKNOWN_LATENCY)
|
||||
{
|
||||
@@ -53,7 +53,7 @@ namespace tunnel
|
||||
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS;
|
||||
auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage ();
|
||||
*msg->GetPayload () = numRecords;
|
||||
const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE;
|
||||
const size_t recordSize = SHORT_TUNNEL_BUILD_RECORD_SIZE;
|
||||
msg->len += numRecords*recordSize + 1;
|
||||
// shuffle records
|
||||
std::vector<int> recordIndicies;
|
||||
@@ -97,7 +97,7 @@ namespace tunnel
|
||||
// delete phony hop after encryption
|
||||
if (insertPhonyRecord) m_Config->DeletePhonyHop ();
|
||||
|
||||
msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild);
|
||||
msg->FillI2NPMessageHeader (eI2NPShortTunnelBuild);
|
||||
auto s = shared_from_this ();
|
||||
msg->onDrop = [s]()
|
||||
{
|
||||
@@ -108,21 +108,17 @@ namespace tunnel
|
||||
// send message
|
||||
if (outboundTunnel)
|
||||
{
|
||||
if (m_Config->IsShort ())
|
||||
auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr;
|
||||
if (ident && ident->GetIdentHash () != outboundTunnel->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP
|
||||
{
|
||||
auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr;
|
||||
if (ident && ident->GetIdentHash () != outboundTunnel->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP
|
||||
{
|
||||
auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ());
|
||||
if (msg1) msg = msg1;
|
||||
}
|
||||
auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ());
|
||||
if (msg1) msg = msg1;
|
||||
}
|
||||
outboundTunnel->SendTunnelDataMsgTo (GetNextIdentHash (), 0, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Config->IsShort () && m_Config->GetLastHop () &&
|
||||
m_Config->GetLastHop ()->ident->GetIdentHash () != m_Config->GetLastHop ()->nextIdent)
|
||||
if (m_Config->GetLastHop () && m_Config->GetLastHop ()->ident->GetIdentHash () != m_Config->GetLastHop ()->nextIdent)
|
||||
{
|
||||
// add garlic key/tag for reply
|
||||
uint8_t key[32];
|
||||
@@ -212,7 +208,6 @@ namespace tunnel
|
||||
hop = hop->prev;
|
||||
i++;
|
||||
}
|
||||
m_IsShortBuildMessage = m_Config->IsShort ();
|
||||
m_FarEndTransports = m_Config->GetFarEndTransports ();
|
||||
m_Config = nullptr;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ namespace tunnel
|
||||
std::shared_ptr<TunnelConfig> GetTunnelConfig () const { return m_Config; }
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetPeers () const;
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetInvertedPeers () const;
|
||||
bool IsShortBuildMessage () const { return m_IsShortBuildMessage; };
|
||||
i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; };
|
||||
TunnelState GetState () const { return m_State; };
|
||||
void SetState (TunnelState state);
|
||||
@@ -127,7 +126,6 @@ namespace tunnel
|
||||
|
||||
std::shared_ptr<TunnelConfig> m_Config;
|
||||
std::vector<TunnelHop> m_Hops;
|
||||
bool m_IsShortBuildMessage;
|
||||
std::shared_ptr<TunnelPool> m_Pool; // pool, tunnel belongs to, or null
|
||||
std::atomic<TunnelState> m_State;
|
||||
i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports;
|
||||
|
||||
+13
-84
@@ -109,50 +109,6 @@ namespace tunnel
|
||||
return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt
|
||||
}
|
||||
|
||||
void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
|
||||
{
|
||||
// generate keys
|
||||
RAND_bytes (layerKey, 32);
|
||||
RAND_bytes (ivKey, 32);
|
||||
RAND_bytes (replyKey, 32);
|
||||
RAND_bytes (replyIV, 16);
|
||||
// fill clear text
|
||||
uint8_t flag = 0;
|
||||
if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG;
|
||||
if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
|
||||
uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
|
||||
htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID);
|
||||
htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID);
|
||||
memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32);
|
||||
memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32);
|
||||
memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32);
|
||||
memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32);
|
||||
memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16);
|
||||
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag;
|
||||
memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility
|
||||
htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ());
|
||||
htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes
|
||||
htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID);
|
||||
memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET);
|
||||
// encrypt
|
||||
uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
|
||||
EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET);
|
||||
memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
|
||||
}
|
||||
|
||||
bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const
|
||||
{
|
||||
uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
|
||||
uint8_t nonce[12];
|
||||
memset (nonce, 0, 12);
|
||||
if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record))
|
||||
{
|
||||
LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
|
||||
{
|
||||
// fill clear text
|
||||
@@ -225,14 +181,6 @@ namespace tunnel
|
||||
return tag;
|
||||
}
|
||||
|
||||
void LongPhonyTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
|
||||
{
|
||||
uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
|
||||
memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetIdentHash (), 16);
|
||||
memcpy (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, i2p::transport::transports.GetNextX25519KeysPair ()->GetPublicKey (), 32);
|
||||
RAND_bytes (record + 48, TUNNEL_BUILD_RECORD_SIZE - 48);
|
||||
}
|
||||
|
||||
void ShortPhonyTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID)
|
||||
{
|
||||
uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE;
|
||||
@@ -242,8 +190,8 @@ namespace tunnel
|
||||
}
|
||||
|
||||
TunnelConfig::TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers,
|
||||
bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports):
|
||||
m_IsShort (isShort), m_FarEndTransports (farEndTransports)
|
||||
i2p::data::RouterInfo::CompatibleTransports farEndTransports):
|
||||
m_FarEndTransports (farEndTransports)
|
||||
{
|
||||
// inbound
|
||||
CreatePeers (peers);
|
||||
@@ -251,9 +199,9 @@ namespace tunnel
|
||||
}
|
||||
|
||||
TunnelConfig::TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers,
|
||||
uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort,
|
||||
uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent,
|
||||
i2p::data::RouterInfo::CompatibleTransports farEndTransports):
|
||||
m_IsShort (isShort), m_FarEndTransports (farEndTransports)
|
||||
m_FarEndTransports (farEndTransports)
|
||||
{
|
||||
// outbound
|
||||
CreatePeers (peers);
|
||||
@@ -266,24 +214,12 @@ namespace tunnel
|
||||
TunnelHopConfig * prev = nullptr;
|
||||
for (const auto& it: peers)
|
||||
{
|
||||
TunnelHopConfig * hop = nullptr;
|
||||
if (m_IsShort)
|
||||
hop = new ShortECIESTunnelHopConfig (it);
|
||||
TunnelHopConfig * hop = new ShortECIESTunnelHopConfig (it);
|
||||
if (prev)
|
||||
prev->SetNext (hop);
|
||||
else
|
||||
{
|
||||
if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
|
||||
hop = new LongECIESTunnelHopConfig (it);
|
||||
else
|
||||
LogPrint (eLogError, "Tunnel: ElGamal router is not supported");
|
||||
}
|
||||
if (hop)
|
||||
{
|
||||
if (prev)
|
||||
prev->SetNext (hop);
|
||||
else
|
||||
m_FirstHop = hop;
|
||||
prev = hop;
|
||||
}
|
||||
m_FirstHop = hop;
|
||||
prev = hop;
|
||||
}
|
||||
m_LastHop = prev;
|
||||
}
|
||||
@@ -292,17 +228,10 @@ namespace tunnel
|
||||
{
|
||||
if (m_LastHop && m_LastHop->ident)
|
||||
{
|
||||
TunnelHopConfig * hop = nullptr;
|
||||
if (m_IsShort)
|
||||
hop = new ShortPhonyTunnelHopConfig ();
|
||||
else
|
||||
hop = new LongPhonyTunnelHopConfig ();
|
||||
if (hop)
|
||||
{
|
||||
hop->prev = m_LastHop;
|
||||
m_LastHop->next = hop;
|
||||
m_LastHop = hop;
|
||||
}
|
||||
TunnelHopConfig * hop = new ShortPhonyTunnelHopConfig ();
|
||||
hop->prev = m_LastHop;
|
||||
m_LastHop->next = hop;
|
||||
m_LastHop = hop;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2025, The PurpleI2P Project
|
||||
* Copyright (c) 2013-2026, The PurpleI2P Project
|
||||
*
|
||||
* This file is part of Purple i2pd project and licensed under BSD3
|
||||
*
|
||||
@@ -55,16 +55,6 @@ namespace tunnel
|
||||
bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const;
|
||||
};
|
||||
|
||||
struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig
|
||||
{
|
||||
LongECIESTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r):
|
||||
ECIESTunnelHopConfig (r) {};
|
||||
uint8_t GetRetCode (const uint8_t * records) const override
|
||||
{ return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; };
|
||||
void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override;
|
||||
bool DecryptBuildResponseRecord (uint8_t * records) const override;
|
||||
};
|
||||
|
||||
struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig
|
||||
{
|
||||
ShortECIESTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r):
|
||||
@@ -85,27 +75,22 @@ namespace tunnel
|
||||
void DecryptRecord (uint8_t * records, int index) const override {} // do nothing
|
||||
};
|
||||
|
||||
struct LongPhonyTunnelHopConfig: public PhonyTunnelHopConfig
|
||||
{
|
||||
void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override;
|
||||
};
|
||||
|
||||
struct ShortPhonyTunnelHopConfig: public PhonyTunnelHopConfig
|
||||
{
|
||||
void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override;
|
||||
};
|
||||
|
||||
|
||||
class TunnelConfig
|
||||
{
|
||||
public:
|
||||
|
||||
TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers,
|
||||
bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports); // inbound
|
||||
i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports); // inbound
|
||||
|
||||
TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers,
|
||||
uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort,
|
||||
uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent,
|
||||
i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports); // outbound
|
||||
|
||||
|
||||
virtual ~TunnelConfig ()
|
||||
{
|
||||
TunnelHopConfig * hop = m_FirstHop;
|
||||
@@ -118,8 +103,6 @@ namespace tunnel
|
||||
}
|
||||
}
|
||||
|
||||
bool IsShort () const { return m_IsShort; }
|
||||
|
||||
i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const
|
||||
{
|
||||
return m_FarEndTransports;
|
||||
@@ -188,15 +171,15 @@ namespace tunnel
|
||||
return peers;
|
||||
}
|
||||
|
||||
size_t GetRecordSize () const { return m_IsShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; };
|
||||
size_t GetRecordSize () const { return SHORT_TUNNEL_BUILD_RECORD_SIZE; };
|
||||
|
||||
void CreatePhonyHop ();
|
||||
void DeletePhonyHop ();
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
// this constructor can't be called from outside
|
||||
TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false),
|
||||
TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr),
|
||||
m_FarEndTransports (i2p::data::RouterInfo::eAllTransports)
|
||||
{
|
||||
}
|
||||
@@ -208,7 +191,6 @@ namespace tunnel
|
||||
private:
|
||||
|
||||
TunnelHopConfig * m_FirstHop, * m_LastHop;
|
||||
bool m_IsShort;
|
||||
i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports;
|
||||
};
|
||||
|
||||
|
||||
+10
-19
@@ -27,12 +27,7 @@ namespace tunnel
|
||||
void Path::Add (std::shared_ptr<const i2p::data::RouterInfo> r)
|
||||
{
|
||||
if (r)
|
||||
{
|
||||
peers.push_back (r->GetRouterIdentity ());
|
||||
if (r->GetVersion () < i2p::data::NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION ||
|
||||
r->GetRouterIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
|
||||
isShort = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Path::Reverse ()
|
||||
@@ -751,7 +746,7 @@ namespace tunnel
|
||||
if (m_NumInboundHops > 0)
|
||||
{
|
||||
path.Reverse ();
|
||||
config = std::make_shared<TunnelConfig> (path.peers, path.isShort, path.farEndTransports);
|
||||
config = std::make_shared<TunnelConfig> (path.peers, path.farEndTransports);
|
||||
}
|
||||
auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel);
|
||||
if (tunnel->IsEstablished ()) // zero hops
|
||||
@@ -777,8 +772,7 @@ namespace tunnel
|
||||
{
|
||||
auto peers = tunnel->GetPeers();
|
||||
if (peers.size ()&& ValidatePeers (peers))
|
||||
config = std::make_shared<TunnelConfig>(tunnel->GetPeers (),
|
||||
tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ());
|
||||
config = std::make_shared<TunnelConfig>(tunnel->GetPeers (), tunnel->GetFarEndTransports ());
|
||||
}
|
||||
if (!m_NumInboundHops || config)
|
||||
{
|
||||
@@ -811,19 +805,17 @@ namespace tunnel
|
||||
std::shared_ptr<TunnelConfig> config;
|
||||
if (m_NumOutboundHops > 0)
|
||||
config = std::make_shared<TunnelConfig>(path.peers, inboundTunnel->GetNextTunnelID (),
|
||||
inboundTunnel->GetNextIdentHash (), path.isShort, path.farEndTransports);
|
||||
inboundTunnel->GetNextIdentHash (), path.farEndTransports);
|
||||
|
||||
std::shared_ptr<OutboundTunnel> tunnel;
|
||||
if (path.isShort)
|
||||
// TODO: implement it better
|
||||
tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ());
|
||||
if (tunnel)
|
||||
{
|
||||
// TODO: implement it better
|
||||
tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ());
|
||||
tunnel->SetTunnelPool (shared_from_this ());
|
||||
if (tunnel->IsEstablished ()) // zero hops
|
||||
TunnelCreated (tunnel);
|
||||
}
|
||||
else
|
||||
tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ());
|
||||
if (tunnel && tunnel->IsEstablished ()) // zero hops
|
||||
TunnelCreated (tunnel);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available");
|
||||
@@ -848,7 +840,7 @@ namespace tunnel
|
||||
auto peers = tunnel->GetPeers();
|
||||
if (peers.size () && ValidatePeers (peers))
|
||||
config = std::make_shared<TunnelConfig>(peers, inboundTunnel->GetNextTunnelID (),
|
||||
inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ());
|
||||
inboundTunnel->GetNextIdentHash (), tunnel->GetFarEndTransports ());
|
||||
}
|
||||
if (!m_NumOutboundHops || config)
|
||||
{
|
||||
@@ -865,8 +857,7 @@ namespace tunnel
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel...");
|
||||
auto tunnel = tunnels.CreateInboundTunnel (
|
||||
m_NumOutboundHops > 0 ? std::make_shared<TunnelConfig>(outboundTunnel->GetInvertedPeers (),
|
||||
outboundTunnel->IsShortBuildMessage ()) : nullptr,
|
||||
m_NumOutboundHops > 0 ? std::make_shared<TunnelConfig>(outboundTunnel->GetInvertedPeers ()) : nullptr,
|
||||
shared_from_this (), outboundTunnel);
|
||||
if (tunnel->IsEstablished ()) // zero hops
|
||||
TunnelCreated (tunnel);
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace tunnel
|
||||
struct Path
|
||||
{
|
||||
std::vector<Peer> peers;
|
||||
bool isShort = true;
|
||||
i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports;
|
||||
|
||||
void Add (std::shared_ptr<const i2p::data::RouterInfo> r);
|
||||
|
||||
Reference in New Issue
Block a user