Browse Source

don't select ElGamal routers for tunnels

pull/1688/head
orignal 3 years ago
parent
commit
349022ae42
  1. 47
      libi2pd/Crypto.cpp
  2. 4
      libi2pd/Crypto.h
  3. 14
      libi2pd/CryptoKey.cpp
  4. 10
      libi2pd/CryptoKey.h
  5. 4
      libi2pd/LeaseSet.cpp
  6. 10
      libi2pd/NetDb.cpp
  7. 2
      libi2pd/RouterInfo.cpp
  8. 1
      libi2pd/RouterInfo.h
  9. 69
      libi2pd/TunnelConfig.cpp
  10. 34
      libi2pd/TunnelConfig.h
  11. 13
      libi2pd/TunnelPool.cpp

47
libi2pd/Crypto.cpp

@ -398,7 +398,7 @@ namespace crypto
} }
// ElGamal // ElGamal
void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding) void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted)
{ {
BN_CTX * ctx = BN_CTX_new (); BN_CTX * ctx = BN_CTX_new ();
BN_CTX_start (ctx); BN_CTX_start (ctx);
@ -436,18 +436,11 @@ namespace crypto
BN_bin2bn (m, 255, b); BN_bin2bn (m, 255, b);
BN_mod_mul (b, b1, b, elgp, ctx); BN_mod_mul (b, b1, b, elgp, ctx);
// copy a and b // copy a and b
if (zeroPadding) encrypted[0] = 0;
{ bn2buf (a, encrypted + 1, 256);
encrypted[0] = 0; encrypted[257] = 0;
bn2buf (a, encrypted + 1, 256); bn2buf (b, encrypted + 258, 256);
encrypted[257] = 0;
bn2buf (b, encrypted + 258, 256);
}
else
{
bn2buf (a, encrypted, 256);
bn2buf (b, encrypted + 256, 256);
}
BN_free (a); BN_free (a);
BN_CTX_end (ctx); BN_CTX_end (ctx);
BN_CTX_free (ctx); BN_CTX_free (ctx);
@ -502,7 +495,7 @@ namespace crypto
} }
// ECIES // ECIES
void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding) void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted)
{ {
BN_CTX * ctx = BN_CTX_new (); BN_CTX * ctx = BN_CTX_new ();
BN_CTX_start (ctx); BN_CTX_start (ctx);
@ -516,19 +509,10 @@ namespace crypto
EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx); EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx);
BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx);
EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr);
if (zeroPadding) encrypted[0] = 0;
{ bn2buf (x, encrypted + 1, len);
encrypted[0] = 0; bn2buf (y, encrypted + 1 + len, len);
bn2buf (x, encrypted + 1, len); RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len);
bn2buf (y, encrypted + 1 + len, len);
RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len);
}
else
{
bn2buf (x, encrypted, len);
bn2buf (y, encrypted + len, len);
RAND_bytes (encrypted + 2*len, 256 - 2*len);
}
// encryption key and iv // encryption key and iv
EC_POINT_mul (curve, p, nullptr, key, k, ctx); EC_POINT_mul (curve, p, nullptr, key, k, ctx);
EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr);
@ -545,13 +529,8 @@ namespace crypto
CBCEncryption encryption; CBCEncryption encryption;
encryption.SetKey (shared); encryption.SetKey (shared);
encryption.SetIV (iv); encryption.SetIV (iv);
if (zeroPadding) encrypted[257] = 0;
{ encryption.Encrypt (m, 256, encrypted + 258);
encrypted[257] = 0;
encryption.Encrypt (m, 256, encrypted + 258);
}
else
encryption.Encrypt (m, 256, encrypted + 256);
EC_POINT_free (p); EC_POINT_free (p);
BN_CTX_end (ctx); BN_CTX_end (ctx);
BN_CTX_free (ctx); BN_CTX_free (ctx);

4
libi2pd/Crypto.h

@ -108,12 +108,12 @@ namespace crypto
}; };
// ElGamal // ElGamal
void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted
bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data
void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub);
// ECIES // ECIES
void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted
bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data
void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub);

14
libi2pd/CryptoKey.cpp

@ -20,9 +20,9 @@ namespace crypto
memcpy (m_PublicKey, pub, 256); memcpy (m_PublicKey, pub, 256);
} }
void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted)
{ {
ElGamalEncrypt (m_PublicKey, data, encrypted, zeroPadding); ElGamalEncrypt (m_PublicKey, data, encrypted);
} }
ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv) ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv)
@ -52,10 +52,10 @@ namespace crypto
if (m_PublicKey) EC_POINT_free (m_PublicKey); if (m_PublicKey) EC_POINT_free (m_PublicKey);
} }
void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted)
{ {
if (m_Curve && m_PublicKey) if (m_Curve && m_PublicKey)
ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, zeroPadding); ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted);
} }
ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv) ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv)
@ -112,10 +112,10 @@ namespace crypto
if (m_PublicKey) EC_POINT_free (m_PublicKey); if (m_PublicKey) EC_POINT_free (m_PublicKey);
} }
void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted)
{ {
if (m_PublicKey) if (m_PublicKey)
ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, zeroPadding); ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted);
} }
ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv) ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv)
@ -159,7 +159,7 @@ namespace crypto
memcpy (m_PublicKey, pub, 32); memcpy (m_PublicKey, pub, 32);
} }
void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, bool) void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub)
{ {
memcpy (pub, m_PublicKey, 32); memcpy (pub, m_PublicKey, 32);
} }

10
libi2pd/CryptoKey.h

@ -21,7 +21,7 @@ namespace crypto
public: public:
virtual ~CryptoKeyEncryptor () {}; virtual ~CryptoKeyEncryptor () {};
virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) = 0; virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) = 0;
}; };
class CryptoKeyDecryptor class CryptoKeyDecryptor
@ -39,7 +39,7 @@ namespace crypto
public: public:
ElGamalEncryptor (const uint8_t * pub); ElGamalEncryptor (const uint8_t * pub);
void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; // 222 bytes data, 512/514 bytes encrypted void Encrypt (const uint8_t * data, uint8_t * encrypted) override; // 222 bytes data, 514 bytes encrypted
private: private:
@ -67,7 +67,7 @@ namespace crypto
ECIESP256Encryptor (const uint8_t * pub); ECIESP256Encryptor (const uint8_t * pub);
~ECIESP256Encryptor (); ~ECIESP256Encryptor ();
void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; void Encrypt (const uint8_t * data, uint8_t * encrypted) override;
private: private:
@ -101,7 +101,7 @@ namespace crypto
ECIESGOSTR3410Encryptor (const uint8_t * pub); ECIESGOSTR3410Encryptor (const uint8_t * pub);
~ECIESGOSTR3410Encryptor (); ~ECIESGOSTR3410Encryptor ();
void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; void Encrypt (const uint8_t * data, uint8_t * encrypted) override;
private: private:
@ -133,7 +133,7 @@ namespace crypto
ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ECIESX25519AEADRatchetEncryptor (const uint8_t * pub);
~ECIESX25519AEADRatchetEncryptor () {}; ~ECIESX25519AEADRatchetEncryptor () {};
void Encrypt (const uint8_t *, uint8_t * pub, bool) override; void Encrypt (const uint8_t *, uint8_t * pub) override;
// copies m_PublicKey to pub // copies m_PublicKey to pub
private: private:

4
libi2pd/LeaseSet.cpp

@ -259,7 +259,7 @@ namespace data
if (!m_EncryptionKey) return; if (!m_EncryptionKey) return;
auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey); auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey);
if (encryptor) if (encryptor)
encryptor->Encrypt (data, encrypted, true); encryptor->Encrypt (data, encrypted);
} }
void LeaseSet::SetBuffer (const uint8_t * buf, size_t len) void LeaseSet::SetBuffer (const uint8_t * buf, size_t len)
@ -662,7 +662,7 @@ namespace data
{ {
auto encryptor = m_Encryptor; // TODO: atomic auto encryptor = m_Encryptor; // TODO: atomic
if (encryptor) if (encryptor)
encryptor->Encrypt (data, encrypted, true); encryptor->Encrypt (data, encrypted);
} }
uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const

10
libi2pd/NetDb.cpp

@ -1171,7 +1171,8 @@ namespace data
{ {
return !router->IsHidden () && router != compatibleWith && return !router->IsHidden () && router != compatibleWith &&
(reverse ? compatibleWith->IsReachableFrom (*router) : (reverse ? compatibleWith->IsReachableFrom (*router) :
router->IsReachableFrom (*compatibleWith)); router->IsReachableFrom (*compatibleWith)) &&
router->IsECIES ();
}); });
} }
@ -1213,11 +1214,8 @@ namespace data
(reverse ? compatibleWith->IsReachableFrom (*router) : (reverse ? compatibleWith->IsReachableFrom (*router) :
router->IsReachableFrom (*compatibleWith)) && router->IsReachableFrom (*compatibleWith)) &&
(router->GetCaps () & RouterInfo::eHighBandwidth) && (router->GetCaps () & RouterInfo::eHighBandwidth) &&
#if defined(__x86_64__) router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION &&
router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; router->IsECIES ();
#else
router->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
#endif
}); });
} }

2
libi2pd/RouterInfo.cpp

@ -1159,7 +1159,7 @@ namespace data
{ {
auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr);
if (encryptor) if (encryptor)
encryptor->Encrypt (data, encrypted, true); encryptor->Encrypt (data, encrypted);
} }
bool RouterInfo::IsEligibleFloodfill () const bool RouterInfo::IsEligibleFloodfill () const

1
libi2pd/RouterInfo.h

@ -191,6 +191,7 @@ namespace data
void UpdateSupportedTransports (); void UpdateSupportedTransports ();
bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
bool IsReachable () const { return m_Caps & Caps::eReachable; }; bool IsReachable () const { return m_Caps & Caps::eReachable; };
bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; };
bool IsSSU (bool v4only = true) const; bool IsSSU (bool v4only = true) const;
bool IsSSUV6 () const; bool IsSSUV6 () const;
bool IsNTCP2 (bool v4only = true) const; bool IsNTCP2 (bool v4only = true) const;

69
libi2pd/TunnelConfig.cpp

@ -83,48 +83,6 @@ namespace tunnel
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
} }
void ElGamalTunnelHopConfig::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[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID);
memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32);
htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID);
memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32);
memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16);
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag;
htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ());
htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID);
RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET);
// encrypt
uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
auto encryptor = ident->CreateEncryptor (nullptr);
if (encryptor)
encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, false);
memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
}
bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const
{
uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE;
i2p::crypto::CBCDecryption decryption;
decryption.SetKey (replyKey);
decryption.SetIV (replyIV);
decryption.Decrypt (record, TUNNEL_BUILD_RECORD_SIZE, record);
return true;
}
void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted)
{ {
if (!ident) return; if (!ident) return;
@ -261,5 +219,32 @@ namespace tunnel
memcpy (key, m_CK + 32, 32); memcpy (key, m_CK + 32, 32);
return tag; return tag;
} }
void TunnelConfig::CreatePeers (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers)
{
TunnelHopConfig * prev = nullptr;
for (const auto& it: peers)
{
TunnelHopConfig * hop = nullptr;
if (m_IsShort)
hop = new ShortECIESTunnelHopConfig (it);
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_LastHop = prev;
}
} }
} }

34
libi2pd/TunnelConfig.h

@ -47,16 +47,6 @@ namespace tunnel
virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag
}; };
struct ElGamalTunnelHopConfig: public TunnelHopConfig
{
ElGamalTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r):
TunnelHopConfig (r) {};
uint8_t GetRetCode (const uint8_t * records) const
{ return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; };
void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID);
bool DecryptBuildResponseRecord (uint8_t * records) const;
};
struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState
{ {
ECIESTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r): ECIESTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r):
@ -194,29 +184,7 @@ namespace tunnel
private: private:
void CreatePeers (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers) void CreatePeers (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers);
{
TunnelHopConfig * prev = nullptr;
for (const auto& it: peers)
{
TunnelHopConfig * hop;
if (m_IsShort)
hop = new ShortECIESTunnelHopConfig (it);
else
{
if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
hop = new LongECIESTunnelHopConfig (it);
else
hop = new ElGamalTunnelHopConfig (it);
}
if (prev)
prev->SetNext (hop);
else
m_FirstHop = hop;
prev = hop;
}
m_LastHop = prev;
}
private: private:

13
libi2pd/TunnelPool.cpp

@ -453,7 +453,7 @@ namespace tunnel
(inbound && i2p::transport::transports.GetNumPeers () > 25)) (inbound && i2p::transport::transports.GetNumPeers () > 25))
{ {
auto r = i2p::transport::transports.GetRandomPeer (); auto r = i2p::transport::transports.GetRandomPeer ();
if (r && !r->GetProfile ()->IsBad () && if (r && r->IsECIES () && !r->GetProfile ()->IsBad () &&
(numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable
{ {
prevHop = r; prevHop = r;
@ -469,6 +469,7 @@ namespace tunnel
{ {
LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected");
hop = i2p::transport::transports.GetRandomPeer (); hop = i2p::transport::transports.GetRandomPeer ();
if (!hop->IsECIES ()) hop = nullptr;
} }
if (!hop) if (!hop)
{ {
@ -513,7 +514,15 @@ namespace tunnel
auto& ident = (*m_ExplicitPeers)[i]; auto& ident = (*m_ExplicitPeers)[i];
auto r = i2p::data::netdb.FindRouter (ident); auto r = i2p::data::netdb.FindRouter (ident);
if (r) if (r)
path.Add (r); {
if (r->IsECIES ())
path.Add (r);
else
{
LogPrint (eLogError, "Tunnels: ElGamal router ", ident.ToBase64 (), " is not supported");
return false;
}
}
else else
{ {
LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ());

Loading…
Cancel
Save