mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-04-27 11:17:49 +02:00
restructure build to separate the 3 main components into 3 subdirectories
libi2pd for core libs libi2pd_client for i2pd client libs daemon for i2pd daemon libs
This commit is contained in:
parent
b3161dde93
commit
4cc3b7f9fb
140 changed files with 209 additions and 206 deletions
290
libi2pd/Base.cpp
Normal file
290
libi2pd/Base.cpp
Normal file
|
@ -0,0 +1,290 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Base.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
static const char T32[32] = {
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||
'y', 'z', '2', '3', '4', '5', '6', '7',
|
||||
};
|
||||
|
||||
const char * GetBase32SubstitutionTable ()
|
||||
{
|
||||
return T32;
|
||||
}
|
||||
|
||||
static void iT64Build(void);
|
||||
|
||||
/*
|
||||
*
|
||||
* BASE64 Substitution Table
|
||||
* -------------------------
|
||||
*
|
||||
* Direct Substitution Table
|
||||
*/
|
||||
|
||||
static const char T64[64] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '-', '~'
|
||||
};
|
||||
|
||||
const char * GetBase64SubstitutionTable ()
|
||||
{
|
||||
return T64;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reverse Substitution Table (built in run time)
|
||||
*/
|
||||
|
||||
static char iT64[256];
|
||||
static int isFirstTime = 1;
|
||||
|
||||
/*
|
||||
* Padding
|
||||
*/
|
||||
|
||||
static char P64 = '=';
|
||||
|
||||
/*
|
||||
*
|
||||
* ByteStreamToBase64
|
||||
* ------------------
|
||||
*
|
||||
* Converts binary encoded data to BASE64 format.
|
||||
*
|
||||
*/
|
||||
|
||||
size_t /* Number of bytes in the encoded buffer */
|
||||
ByteStreamToBase64 (
|
||||
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 * pd;
|
||||
unsigned char acc_1;
|
||||
unsigned char acc_2;
|
||||
int i;
|
||||
int n;
|
||||
int m;
|
||||
size_t outCount;
|
||||
|
||||
ps = (unsigned char *)InBuffer;
|
||||
n = InCount/3;
|
||||
m = InCount%3;
|
||||
if (!m)
|
||||
outCount = 4*n;
|
||||
else
|
||||
outCount = 4*(n+1);
|
||||
if (outCount > len) return 0;
|
||||
pd = (unsigned char *)OutBuffer;
|
||||
for ( i = 0; i<n; i++ ){
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1<<4)&0x30;
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_1 = *ps++;
|
||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||
*pd++ = T64[acc_2];
|
||||
acc_1 &= 0x0f;
|
||||
acc_1 <<=2;
|
||||
acc_2 = *ps++;
|
||||
acc_1 |= acc_2>>6; /* base64 digit #3 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_2 &= 0x3f; /* base64 digit #4 */
|
||||
*pd++ = T64[acc_2];
|
||||
}
|
||||
if ( m == 1 ){
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
*pd++ = T64[acc_2];
|
||||
*pd++ = P64;
|
||||
*pd++ = P64;
|
||||
|
||||
}
|
||||
else if ( m == 2 ){
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1<<4)&0x3f;
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_1 = *ps++;
|
||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||
*pd++ = T64[acc_2];
|
||||
acc_1 &= 0x0f;
|
||||
acc_1 <<=2; /* base64 digit #3 */
|
||||
*pd++ = T64[acc_1];
|
||||
*pd++ = P64;
|
||||
}
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Base64ToByteStream
|
||||
* ------------------
|
||||
*
|
||||
* Converts BASE64 encoded data to binary format. If input buffer is
|
||||
* not properly padded, buffer of negative length is returned
|
||||
*
|
||||
*/
|
||||
|
||||
size_t /* Number of output bytes */
|
||||
Base64ToByteStream (
|
||||
const char * InBuffer, /* BASE64 encoded 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 acc_1;
|
||||
unsigned char acc_2;
|
||||
int i;
|
||||
int n;
|
||||
int m;
|
||||
size_t outCount;
|
||||
|
||||
if (isFirstTime) iT64Build();
|
||||
n = InCount/4;
|
||||
m = InCount%4;
|
||||
if (InCount && !m)
|
||||
outCount = 3*n;
|
||||
else {
|
||||
outCount = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ps = (unsigned char *)(InBuffer + InCount - 1);
|
||||
while ( *ps-- == P64 ) outCount--;
|
||||
ps = (unsigned char *)InBuffer;
|
||||
|
||||
if (outCount > len) return -1;
|
||||
pd = OutBuffer;
|
||||
auto endOfOutBuffer = OutBuffer + outCount;
|
||||
for ( i = 0; i < n; i++ ){
|
||||
acc_1 = iT64[*ps++];
|
||||
acc_2 = iT64[*ps++];
|
||||
acc_1 <<= 2;
|
||||
acc_1 |= acc_2>>4;
|
||||
*pd++ = acc_1;
|
||||
if (pd >= endOfOutBuffer) break;
|
||||
|
||||
acc_2 <<= 4;
|
||||
acc_1 = iT64[*ps++];
|
||||
acc_2 |= acc_1 >> 2;
|
||||
*pd++ = acc_2;
|
||||
if (pd >= endOfOutBuffer) break;
|
||||
|
||||
acc_2 = iT64[*ps++];
|
||||
acc_2 |= acc_1 << 6;
|
||||
*pd++ = acc_2;
|
||||
}
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
size_t Base64EncodingBufferSize (const size_t input_size)
|
||||
{
|
||||
auto d = div (input_size, 3);
|
||||
if (d.rem) d.quot++;
|
||||
return 4*d.quot;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* iT64
|
||||
* ----
|
||||
* Reverse table builder. P64 character is replaced with 0
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
static void iT64Build()
|
||||
{
|
||||
int i;
|
||||
isFirstTime = 0;
|
||||
for ( i=0; i<256; i++ ) iT64[i] = -1;
|
||||
for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i;
|
||||
iT64[(int)P64] = 0;
|
||||
}
|
||||
|
||||
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen)
|
||||
{
|
||||
int tmp = 0, bits = 0;
|
||||
size_t ret = 0;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
char ch = inBuf[i];
|
||||
if (ch >= '2' && ch <= '7') // digit
|
||||
ch = (ch - '2') + 26; // 26 means a-z
|
||||
else if (ch >= 'a' && ch <= 'z')
|
||||
ch = ch - 'a'; // a = 0
|
||||
else
|
||||
return 0; // unexpected character
|
||||
|
||||
tmp |= ch;
|
||||
bits += 5;
|
||||
if (bits >= 8)
|
||||
{
|
||||
if (ret >= outLen) return ret;
|
||||
outBuf[ret] = tmp >> (bits - 8);
|
||||
bits -= 8;
|
||||
ret++;
|
||||
}
|
||||
tmp <<= 5;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen)
|
||||
{
|
||||
size_t ret = 0, pos = 1;
|
||||
int bits = 8, tmp = inBuf[0];
|
||||
while (ret < outLen && (bits > 0 || pos < len))
|
||||
{
|
||||
if (bits < 5)
|
||||
{
|
||||
if (pos < len)
|
||||
{
|
||||
tmp <<= 8;
|
||||
tmp |= inBuf[pos] & 0xFF;
|
||||
pos++;
|
||||
bits += 8;
|
||||
}
|
||||
else // last byte
|
||||
{
|
||||
tmp <<= (5 - bits);
|
||||
bits = 5;
|
||||
}
|
||||
}
|
||||
|
||||
bits -= 5;
|
||||
int ind = (tmp >> bits) & 0x1F;
|
||||
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
|
||||
ret++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
25
libi2pd/Base.h
Normal file
25
libi2pd/Base.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef BASE_H__
|
||||
#define BASE_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
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 * GetBase64SubstitutionTable ();
|
||||
|
||||
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);
|
||||
size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen);
|
||||
|
||||
/**
|
||||
Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes
|
||||
*/
|
||||
size_t Base64EncodingBufferSize(const size_t input_size);
|
||||
} // data
|
||||
} // i2p
|
||||
|
||||
#endif
|
69
libi2pd/BloomFilter.cpp
Normal file
69
libi2pd/BloomFilter.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "BloomFilter.h"
|
||||
#include "I2PEndian.h"
|
||||
#include <array>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
|
||||
/** @brief decaying bloom filter implementation */
|
||||
class DecayingBloomFilter : public IBloomFilter
|
||||
{
|
||||
public:
|
||||
|
||||
DecayingBloomFilter(const std::size_t size)
|
||||
{
|
||||
m_Size = size;
|
||||
m_Data = new uint8_t[size];
|
||||
}
|
||||
|
||||
/** @brief implements IBloomFilter::~IBloomFilter */
|
||||
~DecayingBloomFilter()
|
||||
{
|
||||
delete [] m_Data;
|
||||
}
|
||||
|
||||
/** @brief implements IBloomFilter::Add */
|
||||
bool Add(const uint8_t * data, std::size_t len)
|
||||
{
|
||||
std::size_t idx;
|
||||
uint8_t mask;
|
||||
Get(data, len, idx, mask);
|
||||
if(m_Data[idx] & mask) return false; // filter hit
|
||||
m_Data[idx] |= mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @brief implements IBloomFilter::Decay */
|
||||
void Decay()
|
||||
{
|
||||
// reset bloom filter buffer
|
||||
memset(m_Data, 0, m_Size);
|
||||
}
|
||||
|
||||
private:
|
||||
/** @brief get bit index for for data */
|
||||
void Get(const uint8_t * data, std::size_t len, std::size_t & idx, uint8_t & bm)
|
||||
{
|
||||
bm = 1;
|
||||
uint8_t digest[32];
|
||||
// TODO: use blake2 because it's faster
|
||||
SHA256(data, len, digest);
|
||||
uint64_t i = buf64toh(digest);
|
||||
idx = i % m_Size;
|
||||
bm <<= (i % 8);
|
||||
}
|
||||
|
||||
uint8_t * m_Data;
|
||||
std::size_t m_Size;
|
||||
};
|
||||
|
||||
|
||||
BloomFilterPtr BloomFilter(std::size_t capacity)
|
||||
{
|
||||
return std::make_shared<DecayingBloomFilter>(capacity);
|
||||
}
|
||||
}
|
||||
}
|
31
libi2pd/BloomFilter.h
Normal file
31
libi2pd/BloomFilter.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef BLOOM_FILTER_H_
|
||||
#define BLOOM_FILTER_H_
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
|
||||
/** @brief interface for bloom filter */
|
||||
struct IBloomFilter
|
||||
{
|
||||
|
||||
/** @brief destructor */
|
||||
virtual ~IBloomFilter() {};
|
||||
/** @brief add entry to bloom filter, return false if filter hit otherwise return true */
|
||||
virtual bool Add(const uint8_t * data, std::size_t len) = 0;
|
||||
/** @brief optionally decay old entries */
|
||||
virtual void Decay() = 0;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<IBloomFilter> BloomFilterPtr;
|
||||
|
||||
/** @brief create bloom filter */
|
||||
BloomFilterPtr BloomFilter(std::size_t capacity = 1024 * 8);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
303
libi2pd/Config.cpp
Normal file
303
libi2pd/Config.cpp
Normal file
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016, 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 <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/program_options/cmdline.hpp>
|
||||
#include <boost/program_options/options_description.hpp>
|
||||
#include <boost/program_options/parsers.hpp>
|
||||
#include <boost/program_options/variables_map.hpp>
|
||||
|
||||
#include "Identity.h"
|
||||
#include "Config.h"
|
||||
#include "version.h"
|
||||
|
||||
using namespace boost::program_options;
|
||||
|
||||
namespace i2p {
|
||||
namespace config {
|
||||
options_description m_OptionsDesc;
|
||||
variables_map m_Options;
|
||||
|
||||
void Init() {
|
||||
|
||||
options_description general("General options");
|
||||
general.add_options()
|
||||
("help", "Show this message")
|
||||
("conf", value<std::string>()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)")
|
||||
("tunconf", value<std::string>()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)")
|
||||
("pidfile", value<std::string>()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)")
|
||||
("log", value<std::string>()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)")
|
||||
("logfile", value<std::string>()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)")
|
||||
("loglevel", value<std::string>()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)")
|
||||
("family", value<std::string>()->default_value(""), "Specify a family, router belongs to")
|
||||
("datadir", value<std::string>()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)")
|
||||
("host", value<std::string>()->default_value("0.0.0.0"), "External IP")
|
||||
("ifname", value<std::string>()->default_value(""), "Network interface to bind to")
|
||||
("ifname4", value<std::string>()->default_value(""), "Network interface to bind to for ipv4")
|
||||
("ifname6", value<std::string>()->default_value(""), "Network interface to bind to for ipv6")
|
||||
("nat", value<bool>()->zero_tokens()->default_value(true), "Should we assume we are behind NAT?")
|
||||
("port", value<uint16_t>()->default_value(0), "Port to listen for incoming connections (default: auto)")
|
||||
("ipv4", value<bool>()->zero_tokens()->default_value(true), "Enable communication through ipv4")
|
||||
("ipv6", value<bool>()->zero_tokens()->default_value(false), "Enable communication through ipv6")
|
||||
("netid", value<int>()->default_value(I2PD_NET_ID), "Specify NetID. Main I2P is 2")
|
||||
("daemon", value<bool>()->zero_tokens()->default_value(false), "Router will go to background after start")
|
||||
("service", value<bool>()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'")
|
||||
("notransit", value<bool>()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup")
|
||||
("floodfill", value<bool>()->zero_tokens()->default_value(false), "Router will be floodfill")
|
||||
("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)")
|
||||
("ntcp", value<bool>()->zero_tokens()->default_value(true), "Enable NTCP transport")
|
||||
("ssu", value<bool>()->zero_tokens()->default_value(true), "Enable SSU transport")
|
||||
#ifdef _WIN32
|
||||
("svcctl", value<std::string>()->default_value(""), "Windows service management ('install' or 'remove')")
|
||||
("insomnia", value<bool>()->zero_tokens()->default_value(false), "Prevent system from sleeping")
|
||||
("close", value<std::string>()->default_value("ask"), "Action on close: minimize, exit, ask") // TODO: add custom validator or something
|
||||
#endif
|
||||
;
|
||||
|
||||
options_description limits("Limits options");
|
||||
limits.add_options()
|
||||
("limits.coresize", value<uint32_t>()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)")
|
||||
("limits.openfiles", value<uint16_t>()->default_value(0), "Maximum number of open files (0 - use system default)")
|
||||
("limits.transittunnels", value<uint16_t>()->default_value(2500), "Maximum active transit sessions (default:2500)")
|
||||
;
|
||||
|
||||
options_description httpserver("HTTP Server options");
|
||||
httpserver.add_options()
|
||||
("http.enabled", value<bool>()->default_value(true), "Enable or disable webconsole")
|
||||
("http.address", value<std::string>()->default_value("127.0.0.1"), "Webconsole listen address")
|
||||
("http.port", value<uint16_t>()->default_value(7070), "Webconsole listen port")
|
||||
("http.auth", value<bool>()->default_value(false), "Enable Basic HTTP auth for webconsole")
|
||||
("http.user", value<std::string>()->default_value("i2pd"), "Username for basic auth")
|
||||
("http.pass", value<std::string>()->default_value(""), "Password for basic auth (default: random, see logs)")
|
||||
;
|
||||
|
||||
options_description httpproxy("HTTP Proxy options");
|
||||
httpproxy.add_options()
|
||||
("httpproxy.enabled", value<bool>()->default_value(true), "Enable or disable HTTP Proxy")
|
||||
("httpproxy.address", value<std::string>()->default_value("127.0.0.1"), "HTTP Proxy listen address")
|
||||
("httpproxy.port", value<uint16_t>()->default_value(4444), "HTTP Proxy listen port")
|
||||
("httpproxy.keys", value<std::string>()->default_value(""), "File to persist HTTP Proxy keys")
|
||||
("httpproxy.signaturetype", value<i2p::data::SigningKeyType>()->default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default")
|
||||
("httpproxy.inbound.length", value<std::string>()->default_value("3"), "HTTP proxy inbound tunnel length")
|
||||
("httpproxy.outbound.length", value<std::string>()->default_value("3"), "HTTP proxy outbound tunnel length")
|
||||
("httpproxy.inbound.quantity", value<std::string>()->default_value("5"), "HTTP proxy inbound tunnels quantity")
|
||||
("httpproxy.outbound.quantity", value<std::string>()->default_value("5"), "HTTP proxy outbound tunnels quantity")
|
||||
("httpproxy.latency.min", value<std::string>()->default_value("0"), "HTTP proxy min latency for tunnels")
|
||||
("httpproxy.latency.max", value<std::string>()->default_value("0"), "HTTP proxy max latency for tunnels")
|
||||
("httpproxy.outproxy", value<std::string>()->default_value(""), "HTTP proxy upstream out proxy url")
|
||||
("httpproxy.addresshelper", value<bool>()->default_value(true), "Enable or disable addresshelper")
|
||||
;
|
||||
|
||||
options_description socksproxy("SOCKS Proxy options");
|
||||
socksproxy.add_options()
|
||||
("socksproxy.enabled", value<bool>()->default_value(true), "Enable or disable SOCKS Proxy")
|
||||
("socksproxy.address", value<std::string>()->default_value("127.0.0.1"), "SOCKS Proxy listen address")
|
||||
("socksproxy.port", value<uint16_t>()->default_value(4447), "SOCKS Proxy listen port")
|
||||
("socksproxy.keys", value<std::string>()->default_value(""), "File to persist SOCKS Proxy keys")
|
||||
("socksproxy.signaturetype", value<i2p::data::SigningKeyType>()->default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default")
|
||||
("socksproxy.inbound.length", value<std::string>()->default_value("3"), "SOCKS proxy inbound tunnel length")
|
||||
("socksproxy.outbound.length", value<std::string>()->default_value("3"), "SOCKS proxy outbound tunnel length")
|
||||
("socksproxy.inbound.quantity", value<std::string>()->default_value("5"), "SOCKS proxy inbound tunnels quantity")
|
||||
("socksproxy.outbound.quantity", value<std::string>()->default_value("5"), "SOCKS proxy outbound tunnels quantity")
|
||||
("socksproxy.latency.min", value<std::string>()->default_value("0"), "SOCKS proxy min latency for tunnels")
|
||||
("socksproxy.latency.max", value<std::string>()->default_value("0"), "SOCKS proxy max latency for tunnels")
|
||||
("socksproxy.outproxy", value<std::string>()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy")
|
||||
("socksproxy.outproxyport", value<uint16_t>()->default_value(9050), "Upstream outproxy port for SOCKS Proxy")
|
||||
;
|
||||
|
||||
options_description sam("SAM bridge options");
|
||||
sam.add_options()
|
||||
("sam.enabled", value<bool>()->default_value(false), "Enable or disable SAM Application bridge")
|
||||
("sam.address", value<std::string>()->default_value("127.0.0.1"), "SAM listen address")
|
||||
("sam.port", value<uint16_t>()->default_value(7656), "SAM listen port")
|
||||
;
|
||||
|
||||
options_description bob("BOB options");
|
||||
bob.add_options()
|
||||
("bob.enabled", value<bool>()->default_value(false), "Enable or disable BOB command channel")
|
||||
("bob.address", value<std::string>()->default_value("127.0.0.1"), "BOB listen address")
|
||||
("bob.port", value<uint16_t>()->default_value(2827), "BOB listen port")
|
||||
;
|
||||
|
||||
options_description i2cp("I2CP options");
|
||||
i2cp.add_options()
|
||||
("i2cp.enabled", value<bool>()->default_value(false), "Enable or disable I2CP")
|
||||
("i2cp.address", value<std::string>()->default_value("127.0.0.1"), "I2CP listen address")
|
||||
("i2cp.port", value<uint16_t>()->default_value(7654), "I2CP listen port")
|
||||
;
|
||||
|
||||
options_description i2pcontrol("I2PControl options");
|
||||
i2pcontrol.add_options()
|
||||
("i2pcontrol.enabled", value<bool>()->default_value(false), "Enable or disable I2P Control Protocol")
|
||||
("i2pcontrol.address", value<std::string>()->default_value("127.0.0.1"), "I2PCP listen address")
|
||||
("i2pcontrol.port", value<uint16_t>()->default_value(7650), "I2PCP listen port")
|
||||
("i2pcontrol.password", value<std::string>()->default_value("itoopie"), "I2PCP access password")
|
||||
("i2pcontrol.cert", value<std::string>()->default_value("i2pcontrol.crt.pem"), "I2PCP connection cerificate")
|
||||
("i2pcontrol.key", value<std::string>()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key")
|
||||
;
|
||||
|
||||
bool upnp_default = false;
|
||||
#if (defined(USE_UPNP) && (defined(WIN32_APP) || defined(ANDROID)))
|
||||
upnp_default = true; // enable UPNP for windows GUI and android by default
|
||||
#endif
|
||||
options_description upnp("UPnP options");
|
||||
upnp.add_options()
|
||||
("upnp.enabled", value<bool>()->default_value(upnp_default), "Enable or disable UPnP: automatic port forwarding")
|
||||
("upnp.name", value<std::string>()->default_value("I2Pd"), "Name i2pd appears in UPnP forwardings list")
|
||||
;
|
||||
|
||||
options_description precomputation("Precomputation options");
|
||||
precomputation.add_options()
|
||||
("precomputation.elgamal",
|
||||
#if defined(__x86_64__)
|
||||
value<bool>()->default_value(false),
|
||||
#else
|
||||
value<bool>()->default_value(true),
|
||||
#endif
|
||||
"Enable or disable elgamal precomputation table")
|
||||
;
|
||||
|
||||
options_description reseed("Reseed options");
|
||||
reseed.add_options()
|
||||
("reseed.verify", value<bool>()->default_value(false), "Verify .su3 signature")
|
||||
("reseed.threshold", value<uint16_t>()->default_value(25), "Minimum number of known routers before requesting reseed")
|
||||
("reseed.floodfill", value<std::string>()->default_value(""), "Path to router info of floodfill to reseed from")
|
||||
("reseed.file", value<std::string>()->default_value(""), "Path to local .su3 file or HTTPS URL to reseed from")
|
||||
("reseed.zipfile", value<std::string>()->default_value(""), "Path to local .zip file to reseed from")
|
||||
("reseed.urls", value<std::string>()->default_value(
|
||||
"https://reseed.i2p-projekt.de/,"
|
||||
"https://i2p.mooo.com/netDb/,"
|
||||
"https://netdb.i2p2.no/,"
|
||||
// "https://us.reseed.i2p2.no:444/," // mamoth's shit
|
||||
// "https://uk.reseed.i2p2.no:444/," // mamoth's shit
|
||||
"https://i2p-0.manas.ca:8443/,"
|
||||
"https://reseed.i2p.vzaws.com:8443/,"
|
||||
"https://download.xxlspeed.com/,"
|
||||
"https://reseed-ru.lngserv.ru/,"
|
||||
"https://reseed.atomike.ninja/,"
|
||||
"https://reseed.memcpy.io/,"
|
||||
"https://reseed.onion.im/,"
|
||||
"https://itoopie.atomike.ninja/,"
|
||||
"https://randomrng.ddns.net/"
|
||||
), "Reseed URLs, separated by comma")
|
||||
;
|
||||
|
||||
options_description addressbook("AddressBook options");
|
||||
addressbook.add_options()
|
||||
("addressbook.defaulturl", value<std::string>()->default_value(
|
||||
"http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"
|
||||
), "AddressBook subscription URL for initial setup")
|
||||
("addressbook.subscriptions", value<std::string>()->default_value(""),
|
||||
"AddressBook subscriptions URLs, separated by comma");
|
||||
|
||||
options_description trust("Trust options");
|
||||
trust.add_options()
|
||||
("trust.enabled", value<bool>()->default_value(false), "Enable explicit trust options")
|
||||
("trust.family", value<std::string>()->default_value(""), "Router Familiy to trust for first hops")
|
||||
("trust.routers", value<std::string>()->default_value(""), "Only Connect to these routers")
|
||||
("trust.hidden", value<bool>()->default_value(false), "Should we hide our router from other routers?");
|
||||
|
||||
options_description websocket("Websocket Options");
|
||||
websocket.add_options()
|
||||
("websockets.enabled", value<bool>()->default_value(false), "enable websocket server")
|
||||
("websockets.address", value<std::string>()->default_value("127.0.0.1"), "address to bind websocket server on")
|
||||
("websockets.port", value<uint16_t>()->default_value(7666), "port to bind websocket server on");
|
||||
|
||||
options_description exploratory("Exploratory Options");
|
||||
exploratory.add_options()
|
||||
("exploratory.inbound.length", value<int>()->default_value(2), "Exploratory inbound tunnel length")
|
||||
("exploratory.outbound.length", value<int>()->default_value(2), "Exploratory outbound tunnel length")
|
||||
("exploratory.inbound.quantity", value<int>()->default_value(3), "Exploratory inbound tunnels quantity")
|
||||
("exploratory.outbound.quantity", value<int>()->default_value(3), "Exploratory outbound tunnels quantity");
|
||||
|
||||
m_OptionsDesc
|
||||
.add(general)
|
||||
.add(limits)
|
||||
.add(httpserver)
|
||||
.add(httpproxy)
|
||||
.add(socksproxy)
|
||||
.add(sam)
|
||||
.add(bob)
|
||||
.add(i2cp)
|
||||
.add(i2pcontrol)
|
||||
.add(upnp)
|
||||
.add(precomputation)
|
||||
.add(reseed)
|
||||
.add(addressbook)
|
||||
.add(trust)
|
||||
.add(websocket)
|
||||
.add(exploratory)
|
||||
;
|
||||
}
|
||||
|
||||
void ParseCmdline(int argc, char* argv[], bool ignoreUnknown)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto style = boost::program_options::command_line_style::unix_style
|
||||
| boost::program_options::command_line_style::allow_long_disguise;
|
||||
style &= ~ boost::program_options::command_line_style::allow_guessing;
|
||||
if (ignoreUnknown)
|
||||
store(command_line_parser(argc, argv).options(m_OptionsDesc).style (style).allow_unregistered().run(), m_Options);
|
||||
else
|
||||
store(parse_command_line(argc, argv, m_OptionsDesc, style), m_Options);
|
||||
}
|
||||
catch (boost::program_options::error& e)
|
||||
{
|
||||
std::cerr << "args: " << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (!ignoreUnknown && (m_Options.count("help") || m_Options.count("h")))
|
||||
{
|
||||
std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl;
|
||||
std::cout << m_OptionsDesc;
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseConfig(const std::string& path) {
|
||||
if (path == "") return;
|
||||
|
||||
std::ifstream config(path, std::ios::in);
|
||||
|
||||
if (!config.is_open())
|
||||
{
|
||||
std::cerr << "missing/unreadable config file: " << path << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
store(boost::program_options::parse_config_file(config, m_OptionsDesc), m_Options);
|
||||
}
|
||||
catch (boost::program_options::error& e)
|
||||
{
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
};
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
notify(m_Options);
|
||||
}
|
||||
|
||||
bool IsDefault(const char *name) {
|
||||
if (!m_Options.count(name))
|
||||
throw "try to check non-existent option";
|
||||
|
||||
if (m_Options[name].defaulted())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
} // namespace config
|
||||
} // namespace i2p
|
113
libi2pd/Config.h
Normal file
113
libi2pd/Config.h
Normal file
|
@ -0,0 +1,113 @@
|
|||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <string>
|
||||
#include <boost/program_options/options_description.hpp>
|
||||
#include <boost/program_options/variables_map.hpp>
|
||||
|
||||
/**
|
||||
* Functions to parse and store i2pd parameters
|
||||
*
|
||||
* General usage flow:
|
||||
* Init() -- early as possible
|
||||
* ParseCmdline() -- somewhere close to main()
|
||||
* ParseConfig() -- after detecting path to config
|
||||
* Finalize() -- right after all Parse*() functions called
|
||||
* GetOption() -- may be called after Finalize()
|
||||
*/
|
||||
|
||||
namespace i2p {
|
||||
namespace config {
|
||||
extern boost::program_options::variables_map m_Options;
|
||||
|
||||
/**
|
||||
* @brief Initialize list of acceptable parameters
|
||||
*
|
||||
* Should be called before any Parse* functions.
|
||||
*/
|
||||
void Init();
|
||||
|
||||
/**
|
||||
* @brief Parse cmdline parameters, and show help if requested
|
||||
* @param argc Cmdline arguments count, should be passed from main().
|
||||
* @param argv Cmdline parameters array, should be passed from main()
|
||||
*
|
||||
* If --help is given in parameters, shows it's list with description
|
||||
* terminates the program with exitcode 0.
|
||||
*
|
||||
* In case of parameter misuse boost throws an exception.
|
||||
* We internally handle type boost::program_options::unknown_option,
|
||||
* and then terminate program with exitcode 1.
|
||||
*
|
||||
* Other exceptions will be passed to higher level.
|
||||
*/
|
||||
void ParseCmdline(int argc, char* argv[], bool ignoreUnknown = false);
|
||||
|
||||
/**
|
||||
* @brief Load and parse given config file
|
||||
* @param path Path to config file
|
||||
*
|
||||
* If error occured when opening file path is points to,
|
||||
* we show the error message and terminate program.
|
||||
*
|
||||
* In case of parameter misuse boost throws an exception.
|
||||
* We internally handle type boost::program_options::unknown_option,
|
||||
* and then terminate program with exitcode 1.
|
||||
*
|
||||
* Other exceptions will be passed to higher level.
|
||||
*/
|
||||
void ParseConfig(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief Used to combine options from cmdline, config and default values
|
||||
*/
|
||||
void Finalize();
|
||||
|
||||
/* @brief Accessor to parameters by name
|
||||
* @param name Name of the requested parameter
|
||||
* @param value Variable where to store option
|
||||
* @return this function returns false if parameter not found
|
||||
*
|
||||
* Example: uint16_t port; GetOption("sam.port", port);
|
||||
*/
|
||||
template<typename T>
|
||||
bool GetOption(const char *name, T& value) {
|
||||
if (!m_Options.count(name))
|
||||
return false;
|
||||
value = m_Options[name].as<T>();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool GetOption(const std::string& name, T& value)
|
||||
{
|
||||
return GetOption (name.c_str (), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set value of given parameter
|
||||
* @param name Name of settable parameter
|
||||
* @param value New parameter value
|
||||
* @return true if value set up successful, false otherwise
|
||||
*
|
||||
* Example: uint16_t port = 2827; SetOption("bob.port", port);
|
||||
*/
|
||||
template<typename T>
|
||||
bool SetOption(const char *name, const T& value) {
|
||||
if (!m_Options.count(name))
|
||||
return false;
|
||||
m_Options.at(name).value() = value;
|
||||
notify(m_Options);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check is value explicitly given or default
|
||||
* @param name Name of checked parameter
|
||||
* @return true if value set to default, false othervise
|
||||
*/
|
||||
bool IsDefault(const char *name);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_H
|
839
libi2pd/Crypto.cpp
Normal file
839
libi2pd/Crypto.cpp
Normal file
|
@ -0,0 +1,839 @@
|
|||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include "TunnelBase.h"
|
||||
#include <openssl/ssl.h>
|
||||
#include "Log.h"
|
||||
#include "Crypto.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
const uint8_t elgp_[256]=
|
||||
{
|
||||
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,
|
||||
0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
|
||||
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
|
||||
0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
|
||||
0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
|
||||
0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
|
||||
0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05,
|
||||
0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
|
||||
0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB,
|
||||
0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04,
|
||||
0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
|
||||
0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F,
|
||||
0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18,
|
||||
0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
|
||||
0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
const int elgg_ = 2;
|
||||
|
||||
const uint8_t dsap_[128]=
|
||||
{
|
||||
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,
|
||||
0xfc, 0xad, 0xae, 0x31, 0xa0, 0xad, 0x18, 0xfa, 0xb3, 0xf0, 0x1b, 0x00, 0xa3, 0x58, 0xde, 0x23,
|
||||
0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c,
|
||||
0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8,
|
||||
0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c,
|
||||
0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11,
|
||||
0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93
|
||||
};
|
||||
|
||||
const uint8_t dsaq_[20]=
|
||||
{
|
||||
0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d,
|
||||
0x68, 0x40, 0x46, 0xb7
|
||||
};
|
||||
|
||||
const uint8_t dsag_[128]=
|
||||
{
|
||||
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,
|
||||
0x07, 0x5f, 0xf9, 0x08, 0x2e, 0xd3, 0x23, 0x53, 0xd4, 0x37, 0x4d, 0x73, 0x01, 0xcd, 0xa1, 0xd2,
|
||||
0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52,
|
||||
0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc,
|
||||
0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a,
|
||||
0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c,
|
||||
0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82
|
||||
};
|
||||
|
||||
const int rsae_ = 65537;
|
||||
|
||||
struct CryptoConstants
|
||||
{
|
||||
// DH/ElGamal
|
||||
BIGNUM * elgp;
|
||||
BIGNUM * elgg;
|
||||
|
||||
// DSA
|
||||
BIGNUM * dsap;
|
||||
BIGNUM * dsaq;
|
||||
BIGNUM * dsag;
|
||||
|
||||
// RSA
|
||||
BIGNUM * rsae;
|
||||
|
||||
CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_,
|
||||
const uint8_t * dsaq_, const uint8_t * dsag_, int rsae_)
|
||||
{
|
||||
elgp = BN_new ();
|
||||
BN_bin2bn (elgp_, 256, elgp);
|
||||
elgg = BN_new ();
|
||||
BN_set_word (elgg, elgg_);
|
||||
dsap = BN_new ();
|
||||
BN_bin2bn (dsap_, 128, dsap);
|
||||
dsaq = BN_new ();
|
||||
BN_bin2bn (dsaq_, 20, dsaq);
|
||||
dsag = BN_new ();
|
||||
BN_bin2bn (dsag_, 128, dsag);
|
||||
rsae = BN_new ();
|
||||
BN_set_word (rsae, rsae_);
|
||||
}
|
||||
|
||||
~CryptoConstants ()
|
||||
{
|
||||
BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae);
|
||||
}
|
||||
};
|
||||
|
||||
static const CryptoConstants& GetCryptoConstants ()
|
||||
{
|
||||
static CryptoConstants cryptoConstants (elgp_, elgg_, dsap_, dsaq_, dsag_, rsae_);
|
||||
return cryptoConstants;
|
||||
}
|
||||
|
||||
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len)
|
||||
{
|
||||
int offset = len - BN_num_bytes (bn);
|
||||
if (offset < 0) return false;
|
||||
BN_bn2bin (bn, buf + offset);
|
||||
memset (buf, 0, offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
// RSA
|
||||
#define rsae GetCryptoConstants ().rsae
|
||||
const BIGNUM * GetRSAE ()
|
||||
{
|
||||
return rsae;
|
||||
}
|
||||
|
||||
// DSA
|
||||
#define dsap GetCryptoConstants ().dsap
|
||||
#define dsaq GetCryptoConstants ().dsaq
|
||||
#define dsag GetCryptoConstants ().dsag
|
||||
DSA * CreateDSA ()
|
||||
{
|
||||
DSA * dsa = DSA_new ();
|
||||
DSA_set0_pqg (dsa, BN_dup (dsap), BN_dup (dsaq), BN_dup (dsag));
|
||||
DSA_set0_key (dsa, NULL, NULL);
|
||||
return dsa;
|
||||
}
|
||||
|
||||
// DH/ElGamal
|
||||
|
||||
const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226;
|
||||
const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1;
|
||||
const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048;
|
||||
const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8;
|
||||
|
||||
#define elgp GetCryptoConstants ().elgp
|
||||
#define elgg GetCryptoConstants ().elgg
|
||||
|
||||
static BN_MONT_CTX * g_MontCtx = nullptr;
|
||||
static void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums
|
||||
{
|
||||
if (len <= 0) return;
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
g_MontCtx = BN_MONT_CTX_new ();
|
||||
BN_MONT_CTX_set (g_MontCtx, elgp, ctx);
|
||||
auto montCtx = BN_MONT_CTX_new ();
|
||||
BN_MONT_CTX_copy (montCtx, g_MontCtx);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
table[i][0] = BN_new ();
|
||||
if (!i)
|
||||
BN_to_montgomery (table[0][0], elgg, montCtx, ctx);
|
||||
else
|
||||
BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx);
|
||||
for (int j = 1; j < 255; j++)
|
||||
{
|
||||
table[i][j] = BN_new ();
|
||||
BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], montCtx, ctx);
|
||||
}
|
||||
}
|
||||
BN_MONT_CTX_free (montCtx);
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
|
||||
static void DestroyElggTable (BIGNUM * table[][255], int len)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
for (int j = 0; j < 255; j++)
|
||||
{
|
||||
BN_free (table[i][j]);
|
||||
table[i][j] = nullptr;
|
||||
}
|
||||
BN_MONT_CTX_free (g_MontCtx);
|
||||
}
|
||||
|
||||
static BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx)
|
||||
// exp is in Big Endian
|
||||
{
|
||||
if (len <= 0) return nullptr;
|
||||
auto montCtx = BN_MONT_CTX_new ();
|
||||
BN_MONT_CTX_copy (montCtx, g_MontCtx);
|
||||
BIGNUM * res = nullptr;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (res)
|
||||
{
|
||||
if (exp[i])
|
||||
BN_mod_mul_montgomery (res, res, table[len-1-i][exp[i]-1], montCtx, ctx);
|
||||
}
|
||||
else if (exp[i])
|
||||
res = BN_dup (table[len-i-1][exp[i]-1]);
|
||||
}
|
||||
if (res)
|
||||
BN_from_montgomery (res, res, montCtx, ctx);
|
||||
BN_MONT_CTX_free (montCtx);
|
||||
return res;
|
||||
}
|
||||
|
||||
static BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx)
|
||||
{
|
||||
auto len = BN_num_bytes (exp);
|
||||
uint8_t * buf = new uint8_t[len];
|
||||
BN_bn2bin (exp, buf);
|
||||
auto ret = ElggPow (buf, len, table, ctx);
|
||||
delete[] buf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static BIGNUM * (* g_ElggTable)[255] = nullptr;
|
||||
|
||||
// DH
|
||||
|
||||
DHKeys::DHKeys ()
|
||||
{
|
||||
m_DH = DH_new ();
|
||||
DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg));
|
||||
DH_set0_key (m_DH, NULL, NULL);
|
||||
}
|
||||
|
||||
DHKeys::~DHKeys ()
|
||||
{
|
||||
DH_free (m_DH);
|
||||
}
|
||||
|
||||
void DHKeys::GenerateKeys ()
|
||||
{
|
||||
BIGNUM * priv_key = NULL, * pub_key = NULL;
|
||||
#if !defined(__x86_64__) // use short exponent for non x64
|
||||
priv_key = BN_new ();
|
||||
BN_rand (priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1);
|
||||
#endif
|
||||
if (g_ElggTable)
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
priv_key = BN_new ();
|
||||
BN_rand (priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1);
|
||||
#endif
|
||||
auto ctx = BN_CTX_new ();
|
||||
pub_key = ElggPow (priv_key, g_ElggTable, ctx);
|
||||
DH_set0_key (m_DH, pub_key, priv_key);
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
DH_set0_key (m_DH, NULL, priv_key);
|
||||
DH_generate_key (m_DH);
|
||||
DH_get0_key (m_DH, (const BIGNUM **)&pub_key, (const BIGNUM **)&priv_key);
|
||||
}
|
||||
|
||||
bn2buf (pub_key, m_PublicKey, 256);
|
||||
}
|
||||
|
||||
void DHKeys::Agree (const uint8_t * pub, uint8_t * shared)
|
||||
{
|
||||
BIGNUM * pk = BN_bin2bn (pub, 256, NULL);
|
||||
DH_compute_key (shared, pk, m_DH);
|
||||
BN_free (pk);
|
||||
}
|
||||
|
||||
// ElGamal
|
||||
void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
|
||||
{
|
||||
BN_CTX_start (ctx);
|
||||
// everything, but a, because a might come from table
|
||||
BIGNUM * k = BN_CTX_get (ctx);
|
||||
BIGNUM * y = BN_CTX_get (ctx);
|
||||
BIGNUM * b1 = BN_CTX_get (ctx);
|
||||
BIGNUM * b = BN_CTX_get (ctx);
|
||||
// select random k
|
||||
#if defined(__x86_64__)
|
||||
BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64
|
||||
#else
|
||||
BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits
|
||||
#endif
|
||||
// calculate a
|
||||
BIGNUM * a;
|
||||
if (g_ElggTable)
|
||||
a = ElggPow (k, g_ElggTable, ctx);
|
||||
else
|
||||
{
|
||||
a = BN_new ();
|
||||
BN_mod_exp (a, elgg, k, elgp, ctx);
|
||||
}
|
||||
|
||||
// restore y from key
|
||||
BN_bin2bn (key, 256, y);
|
||||
// calculate b1
|
||||
BN_mod_exp (b1, y, k, elgp, ctx);
|
||||
// create m
|
||||
uint8_t m[255];
|
||||
m[0] = 0xFF;
|
||||
memcpy (m+33, data, 222);
|
||||
SHA256 (m+33, 222, m+1);
|
||||
// calculate b = b1*m mod p
|
||||
BN_bin2bn (m, 255, b);
|
||||
BN_mod_mul (b, b1, b, elgp, ctx);
|
||||
// copy a and b
|
||||
if (zeroPadding)
|
||||
{
|
||||
encrypted[0] = 0;
|
||||
bn2buf (a, encrypted + 1, 256);
|
||||
encrypted[257] = 0;
|
||||
bn2buf (b, encrypted + 258, 256);
|
||||
}
|
||||
else
|
||||
{
|
||||
bn2buf (a, encrypted, 256);
|
||||
bn2buf (b, encrypted + 256, 256);
|
||||
}
|
||||
BN_free (a);
|
||||
BN_CTX_end (ctx);
|
||||
}
|
||||
|
||||
bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted,
|
||||
uint8_t * data, BN_CTX * ctx, bool zeroPadding)
|
||||
{
|
||||
BN_CTX_start (ctx);
|
||||
BIGNUM * x = BN_CTX_get (ctx), * a = BN_CTX_get (ctx), * b = BN_CTX_get (ctx);
|
||||
BN_bin2bn (key, 256, x);
|
||||
BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1
|
||||
BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a);
|
||||
BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b);
|
||||
// m = b*(a^x mod p) mod p
|
||||
BN_mod_exp (x, a, x, elgp, ctx);
|
||||
BN_mod_mul (b, b, x, elgp, ctx);
|
||||
uint8_t m[255];
|
||||
bn2buf (b, m, 255);
|
||||
BN_CTX_end (ctx);
|
||||
uint8_t hash[32];
|
||||
SHA256 (m + 33, 222, hash);
|
||||
if (memcmp (m + 1, hash, 32))
|
||||
{
|
||||
LogPrint (eLogError, "ElGamal decrypt hash doesn't match");
|
||||
return false;
|
||||
}
|
||||
memcpy (data, m + 33, 222);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub)
|
||||
{
|
||||
#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
|
||||
RAND_bytes (priv, 256);
|
||||
#else
|
||||
// lower 226 bits (28 bytes and 2 bits) only. short exponent
|
||||
auto numBytes = (ELGAMAL_SHORT_EXPONENT_NUM_BITS)/8 + 1; // 29
|
||||
auto numZeroBytes = 256 - numBytes;
|
||||
RAND_bytes (priv + numZeroBytes, numBytes);
|
||||
memset (priv, 0, numZeroBytes);
|
||||
priv[numZeroBytes] &= 0x03;
|
||||
#endif
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
BIGNUM * p = BN_new ();
|
||||
BN_bin2bn (priv, 256, p);
|
||||
BN_mod_exp (p, elgg, p, elgp, ctx);
|
||||
bn2buf (p, pub, 256);
|
||||
BN_free (p);
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
|
||||
// HMAC
|
||||
const uint64_t IPAD = 0x3636363636363636;
|
||||
const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C;
|
||||
|
||||
#if defined(__AVX__)
|
||||
static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD };
|
||||
static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD };
|
||||
#endif
|
||||
|
||||
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest)
|
||||
// key is 32 bytes
|
||||
// digest is 16 bytes
|
||||
// block size is 64 bytes
|
||||
{
|
||||
uint64_t buf[256];
|
||||
uint64_t hash[12]; // 96 bytes
|
||||
#if defined(__AVX__) // for AVX
|
||||
__asm__
|
||||
(
|
||||
"vmovups %[key], %%ymm0 \n"
|
||||
"vmovups %[ipad], %%ymm1 \n"
|
||||
"vmovups %%ymm1, 32(%[buf]) \n"
|
||||
"vxorps %%ymm0, %%ymm1, %%ymm1 \n"
|
||||
"vmovups %%ymm1, (%[buf]) \n"
|
||||
"vmovups %[opad], %%ymm1 \n"
|
||||
"vmovups %%ymm1, 32(%[hash]) \n"
|
||||
"vxorps %%ymm0, %%ymm1, %%ymm1 \n"
|
||||
"vmovups %%ymm1, (%[hash]) \n"
|
||||
"vzeroall \n" // end of AVX
|
||||
"movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes
|
||||
:
|
||||
: [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads),
|
||||
[buf]"r"(buf), [hash]"r"(hash)
|
||||
: "memory", "%xmm0" // TODO: change to %ymm0 later
|
||||
);
|
||||
#else
|
||||
// ikeypad
|
||||
buf[0] = key.GetLL ()[0] ^ IPAD;
|
||||
buf[1] = key.GetLL ()[1] ^ IPAD;
|
||||
buf[2] = key.GetLL ()[2] ^ IPAD;
|
||||
buf[3] = key.GetLL ()[3] ^ IPAD;
|
||||
buf[4] = IPAD;
|
||||
buf[5] = IPAD;
|
||||
buf[6] = IPAD;
|
||||
buf[7] = IPAD;
|
||||
// okeypad
|
||||
hash[0] = key.GetLL ()[0] ^ OPAD;
|
||||
hash[1] = key.GetLL ()[1] ^ OPAD;
|
||||
hash[2] = key.GetLL ()[2] ^ OPAD;
|
||||
hash[3] = key.GetLL ()[3] ^ OPAD;
|
||||
hash[4] = OPAD;
|
||||
hash[5] = OPAD;
|
||||
hash[6] = OPAD;
|
||||
hash[7] = OPAD;
|
||||
// fill last 16 bytes with zeros (first hash size assumed 32 bytes in I2P)
|
||||
memset (hash + 10, 0, 16);
|
||||
#endif
|
||||
|
||||
// concatenate with msg
|
||||
memcpy (buf + 8, msg, len);
|
||||
// calculate first hash
|
||||
MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes
|
||||
|
||||
// calculate digest
|
||||
MD5((uint8_t *)hash, 96, digest);
|
||||
}
|
||||
|
||||
// AES
|
||||
#ifdef AESNI
|
||||
|
||||
#define KeyExpansion256(round0,round1) \
|
||||
"pshufd $0xff, %%xmm2, %%xmm2 \n" \
|
||||
"movaps %%xmm1, %%xmm4 \n" \
|
||||
"pslldq $4, %%xmm4 \n" \
|
||||
"pxor %%xmm4, %%xmm1 \n" \
|
||||
"pslldq $4, %%xmm4 \n" \
|
||||
"pxor %%xmm4, %%xmm1 \n" \
|
||||
"pslldq $4, %%xmm4 \n" \
|
||||
"pxor %%xmm4, %%xmm1 \n" \
|
||||
"pxor %%xmm2, %%xmm1 \n" \
|
||||
"movaps %%xmm1, "#round0"(%[sched]) \n" \
|
||||
"aeskeygenassist $0, %%xmm1, %%xmm4 \n" \
|
||||
"pshufd $0xaa, %%xmm4, %%xmm2 \n" \
|
||||
"movaps %%xmm3, %%xmm4 \n" \
|
||||
"pslldq $4, %%xmm4 \n" \
|
||||
"pxor %%xmm4, %%xmm3 \n" \
|
||||
"pslldq $4, %%xmm4 \n" \
|
||||
"pxor %%xmm4, %%xmm3 \n" \
|
||||
"pslldq $4, %%xmm4 \n" \
|
||||
"pxor %%xmm4, %%xmm3 \n" \
|
||||
"pxor %%xmm2, %%xmm3 \n" \
|
||||
"movaps %%xmm3, "#round1"(%[sched]) \n"
|
||||
|
||||
void ECBCryptoAESNI::ExpandKey (const AESKey& key)
|
||||
{
|
||||
__asm__
|
||||
(
|
||||
"movups (%[key]), %%xmm1 \n"
|
||||
"movups 16(%[key]), %%xmm3 \n"
|
||||
"movaps %%xmm1, (%[sched]) \n"
|
||||
"movaps %%xmm3, 16(%[sched]) \n"
|
||||
"aeskeygenassist $1, %%xmm3, %%xmm2 \n"
|
||||
KeyExpansion256(32,48)
|
||||
"aeskeygenassist $2, %%xmm3, %%xmm2 \n"
|
||||
KeyExpansion256(64,80)
|
||||
"aeskeygenassist $4, %%xmm3, %%xmm2 \n"
|
||||
KeyExpansion256(96,112)
|
||||
"aeskeygenassist $8, %%xmm3, %%xmm2 \n"
|
||||
KeyExpansion256(128,144)
|
||||
"aeskeygenassist $16, %%xmm3, %%xmm2 \n"
|
||||
KeyExpansion256(160,176)
|
||||
"aeskeygenassist $32, %%xmm3, %%xmm2 \n"
|
||||
KeyExpansion256(192,208)
|
||||
"aeskeygenassist $64, %%xmm3, %%xmm2 \n"
|
||||
// key expansion final
|
||||
"pshufd $0xff, %%xmm2, %%xmm2 \n"
|
||||
"movaps %%xmm1, %%xmm4 \n"
|
||||
"pslldq $4, %%xmm4 \n"
|
||||
"pxor %%xmm4, %%xmm1 \n"
|
||||
"pslldq $4, %%xmm4 \n"
|
||||
"pxor %%xmm4, %%xmm1 \n"
|
||||
"pslldq $4, %%xmm4 \n"
|
||||
"pxor %%xmm4, %%xmm1 \n"
|
||||
"pxor %%xmm2, %%xmm1 \n"
|
||||
"movups %%xmm1, 224(%[sched]) \n"
|
||||
: // output
|
||||
: [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input
|
||||
: "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged
|
||||
);
|
||||
}
|
||||
|
||||
#define EncryptAES256(sched) \
|
||||
"pxor (%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 16(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 32(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 48(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 64(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 80(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 96(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 112(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 128(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 144(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 160(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 176(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 192(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenc 208(%["#sched"]), %%xmm0 \n" \
|
||||
"aesenclast 224(%["#sched"]), %%xmm0 \n"
|
||||
|
||||
void ECBEncryptionAESNI::Encrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||
{
|
||||
__asm__
|
||||
(
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
EncryptAES256(sched)
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
|
||||
);
|
||||
}
|
||||
|
||||
#define DecryptAES256(sched) \
|
||||
"pxor 224(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 208(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 192(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 176(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 160(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 144(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 128(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 112(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 96(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 80(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 64(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 48(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 32(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdec 16(%["#sched"]), %%xmm0 \n" \
|
||||
"aesdeclast (%["#sched"]), %%xmm0 \n"
|
||||
|
||||
void ECBDecryptionAESNI::Decrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||
{
|
||||
__asm__
|
||||
(
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
DecryptAES256(sched)
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
: : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory"
|
||||
);
|
||||
}
|
||||
|
||||
#define CallAESIMC(offset) \
|
||||
"movaps "#offset"(%[shed]), %%xmm0 \n" \
|
||||
"aesimc %%xmm0, %%xmm0 \n" \
|
||||
"movaps %%xmm0, "#offset"(%[shed]) \n"
|
||||
|
||||
void ECBDecryptionAESNI::SetKey (const AESKey& key)
|
||||
{
|
||||
ExpandKey (key); // expand encryption key first
|
||||
// then invert it using aesimc
|
||||
__asm__
|
||||
(
|
||||
CallAESIMC(16)
|
||||
CallAESIMC(32)
|
||||
CallAESIMC(48)
|
||||
CallAESIMC(64)
|
||||
CallAESIMC(80)
|
||||
CallAESIMC(96)
|
||||
CallAESIMC(112)
|
||||
CallAESIMC(128)
|
||||
CallAESIMC(144)
|
||||
CallAESIMC(160)
|
||||
CallAESIMC(176)
|
||||
CallAESIMC(192)
|
||||
CallAESIMC(208)
|
||||
: : [shed]"r"(GetKeySchedule ()) : "%xmm0", "memory"
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
|
||||
{
|
||||
#ifdef AESNI
|
||||
__asm__
|
||||
(
|
||||
"movups (%[iv]), %%xmm1 \n"
|
||||
"1: \n"
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
"pxor %%xmm1, %%xmm0 \n"
|
||||
EncryptAES256(sched)
|
||||
"movaps %%xmm0, %%xmm1 \n"
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
"add $16, %[in] \n"
|
||||
"add $16, %[out] \n"
|
||||
"dec %[num] \n"
|
||||
"jnz 1b \n"
|
||||
"movups %%xmm1, (%[iv]) \n"
|
||||
:
|
||||
: [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
|
||||
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
|
||||
: "%xmm0", "%xmm1", "cc", "memory"
|
||||
);
|
||||
#else
|
||||
for (int i = 0; i < numBlocks; i++)
|
||||
{
|
||||
*m_LastBlock.GetChipherBlock () ^= in[i];
|
||||
m_ECBEncryption.Encrypt (m_LastBlock.GetChipherBlock (), m_LastBlock.GetChipherBlock ());
|
||||
out[i] = *m_LastBlock.GetChipherBlock ();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out)
|
||||
{
|
||||
// len/16
|
||||
int numBlocks = len >> 4;
|
||||
if (numBlocks > 0)
|
||||
Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||
}
|
||||
|
||||
void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out)
|
||||
{
|
||||
#ifdef AESNI
|
||||
__asm__
|
||||
(
|
||||
"movups (%[iv]), %%xmm1 \n"
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
"pxor %%xmm1, %%xmm0 \n"
|
||||
EncryptAES256(sched)
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
"movups %%xmm0, (%[iv]) \n"
|
||||
:
|
||||
: [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
|
||||
[in]"r"(in), [out]"r"(out)
|
||||
: "%xmm0", "%xmm1", "memory"
|
||||
);
|
||||
#else
|
||||
Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
|
||||
{
|
||||
#ifdef AESNI
|
||||
__asm__
|
||||
(
|
||||
"movups (%[iv]), %%xmm1 \n"
|
||||
"1: \n"
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
"movaps %%xmm0, %%xmm2 \n"
|
||||
DecryptAES256(sched)
|
||||
"pxor %%xmm1, %%xmm0 \n"
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
"movaps %%xmm2, %%xmm1 \n"
|
||||
"add $16, %[in] \n"
|
||||
"add $16, %[out] \n"
|
||||
"dec %[num] \n"
|
||||
"jnz 1b \n"
|
||||
"movups %%xmm1, (%[iv]) \n"
|
||||
:
|
||||
: [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
|
||||
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
|
||||
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
|
||||
);
|
||||
#else
|
||||
for (int i = 0; i < numBlocks; i++)
|
||||
{
|
||||
ChipherBlock tmp = in[i];
|
||||
m_ECBDecryption.Decrypt (in + i, out + i);
|
||||
out[i] ^= *m_IV.GetChipherBlock ();
|
||||
*m_IV.GetChipherBlock () = tmp;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out)
|
||||
{
|
||||
int numBlocks = len >> 4;
|
||||
if (numBlocks > 0)
|
||||
Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||
}
|
||||
|
||||
void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out)
|
||||
{
|
||||
#ifdef AESNI
|
||||
__asm__
|
||||
(
|
||||
"movups (%[iv]), %%xmm1 \n"
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
"movups %%xmm0, (%[iv]) \n"
|
||||
DecryptAES256(sched)
|
||||
"pxor %%xmm1, %%xmm0 \n"
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
:
|
||||
: [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
|
||||
[in]"r"(in), [out]"r"(out)
|
||||
: "%xmm0", "%xmm1", "memory"
|
||||
);
|
||||
#else
|
||||
Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out)
|
||||
{
|
||||
#ifdef AESNI
|
||||
__asm__
|
||||
(
|
||||
// encrypt IV
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
EncryptAES256(sched_iv)
|
||||
"movaps %%xmm0, %%xmm1 \n"
|
||||
// double IV encryption
|
||||
EncryptAES256(sched_iv)
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
// encrypt data, IV is xmm1
|
||||
"1: \n"
|
||||
"add $16, %[in] \n"
|
||||
"add $16, %[out] \n"
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
"pxor %%xmm1, %%xmm0 \n"
|
||||
EncryptAES256(sched_l)
|
||||
"movaps %%xmm0, %%xmm1 \n"
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
"dec %[num] \n"
|
||||
"jnz 1b \n"
|
||||
:
|
||||
: [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.GetKeySchedule ()),
|
||||
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
|
||||
: "%xmm0", "%xmm1", "cc", "memory"
|
||||
);
|
||||
#else
|
||||
m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
|
||||
m_LayerEncryption.SetIV (out);
|
||||
m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
|
||||
m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
|
||||
#endif
|
||||
}
|
||||
|
||||
void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out)
|
||||
{
|
||||
#ifdef AESNI
|
||||
__asm__
|
||||
(
|
||||
// decrypt IV
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
DecryptAES256(sched_iv)
|
||||
"movaps %%xmm0, %%xmm1 \n"
|
||||
// double IV encryption
|
||||
DecryptAES256(sched_iv)
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
// decrypt data, IV is xmm1
|
||||
"1: \n"
|
||||
"add $16, %[in] \n"
|
||||
"add $16, %[out] \n"
|
||||
"movups (%[in]), %%xmm0 \n"
|
||||
"movaps %%xmm0, %%xmm2 \n"
|
||||
DecryptAES256(sched_l)
|
||||
"pxor %%xmm1, %%xmm0 \n"
|
||||
"movups %%xmm0, (%[out]) \n"
|
||||
"movaps %%xmm2, %%xmm1 \n"
|
||||
"dec %[num] \n"
|
||||
"jnz 1b \n"
|
||||
:
|
||||
: [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.GetKeySchedule ()),
|
||||
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
|
||||
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
|
||||
);
|
||||
#else
|
||||
m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
|
||||
m_LayerDecryption.SetIV (out);
|
||||
m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
|
||||
m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
|
||||
#endif
|
||||
}
|
||||
|
||||
/* std::vector <std::unique_ptr<std::mutex> > m_OpenSSLMutexes;
|
||||
static void OpensslLockingCallback(int mode, int type, const char * file, int line)
|
||||
{
|
||||
if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ())
|
||||
{
|
||||
if (mode & CRYPTO_LOCK)
|
||||
m_OpenSSLMutexes[type]->lock ();
|
||||
else
|
||||
m_OpenSSLMutexes[type]->unlock ();
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
void InitCrypto (bool precomputation)
|
||||
{
|
||||
SSL_library_init ();
|
||||
/* auto numLocks = CRYPTO_num_locks();
|
||||
for (int i = 0; i < numLocks; i++)
|
||||
m_OpenSSLMutexes.emplace_back (new std::mutex);
|
||||
CRYPTO_set_locking_callback (OpensslLockingCallback);*/
|
||||
if (precomputation)
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255];
|
||||
PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES);
|
||||
#else
|
||||
g_ElggTable = new BIGNUM * [ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255];
|
||||
PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void TerminateCrypto ()
|
||||
{
|
||||
if (g_ElggTable)
|
||||
{
|
||||
DestroyElggTable (g_ElggTable,
|
||||
#if defined(__x86_64__)
|
||||
ELGAMAL_FULL_EXPONENT_NUM_BYTES
|
||||
#else
|
||||
ELGAMAL_SHORT_EXPONENT_NUM_BYTES
|
||||
#endif
|
||||
);
|
||||
delete[] g_ElggTable; g_ElggTable = nullptr;
|
||||
}
|
||||
/* CRYPTO_set_locking_callback (nullptr);
|
||||
m_OpenSSLMutexes.clear ();*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
363
libi2pd/Crypto.h
Normal file
363
libi2pd/Crypto.h
Normal file
|
@ -0,0 +1,363 @@
|
|||
#ifndef CRYPTO_H__
|
||||
#define CRYPTO_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/dsa.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/engine.h>
|
||||
|
||||
#include "Base.h"
|
||||
#include "Tag.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len);
|
||||
|
||||
// DSA
|
||||
DSA * CreateDSA ();
|
||||
|
||||
// RSA
|
||||
const BIGNUM * GetRSAE ();
|
||||
|
||||
// DH
|
||||
class DHKeys
|
||||
{
|
||||
public:
|
||||
|
||||
DHKeys ();
|
||||
~DHKeys ();
|
||||
|
||||
void GenerateKeys ();
|
||||
const uint8_t * GetPublicKey () const { return m_PublicKey; };
|
||||
void Agree (const uint8_t * pub, uint8_t * shared);
|
||||
|
||||
private:
|
||||
|
||||
DH * m_DH;
|
||||
uint8_t m_PublicKey[256];
|
||||
};
|
||||
|
||||
// ElGamal
|
||||
void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false);
|
||||
bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false);
|
||||
void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub);
|
||||
|
||||
// HMAC
|
||||
typedef i2p::data::Tag<32> MACKey;
|
||||
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest);
|
||||
|
||||
// AES
|
||||
struct ChipherBlock
|
||||
{
|
||||
uint8_t buf[16];
|
||||
|
||||
void operator^=(const ChipherBlock& other) // XOR
|
||||
{
|
||||
#if defined(__AVX__) // AVX
|
||||
__asm__
|
||||
(
|
||||
"vmovups (%[buf]), %%xmm0 \n"
|
||||
"vmovups (%[other]), %%xmm1 \n"
|
||||
"vxorps %%xmm0, %%xmm1, %%xmm0 \n"
|
||||
"vmovups %%xmm0, (%[buf]) \n"
|
||||
:
|
||||
: [buf]"r"(buf), [other]"r"(other.buf)
|
||||
: "%xmm0", "%xmm1", "memory"
|
||||
);
|
||||
#elif defined(__SSE__) // SSE
|
||||
__asm__
|
||||
(
|
||||
"movups (%[buf]), %%xmm0 \n"
|
||||
"movups (%[other]), %%xmm1 \n"
|
||||
"pxor %%xmm1, %%xmm0 \n"
|
||||
"movups %%xmm0, (%[buf]) \n"
|
||||
:
|
||||
: [buf]"r"(buf), [other]"r"(other.buf)
|
||||
: "%xmm0", "%xmm1", "memory"
|
||||
);
|
||||
#else
|
||||
// TODO: implement it better
|
||||
for (int i = 0; i < 16; i++)
|
||||
buf[i] ^= other.buf[i];
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
typedef i2p::data::Tag<32> AESKey;
|
||||
|
||||
template<size_t sz>
|
||||
class AESAlignedBuffer // 16 bytes alignment
|
||||
{
|
||||
public:
|
||||
|
||||
AESAlignedBuffer ()
|
||||
{
|
||||
m_Buf = m_UnalignedBuffer;
|
||||
uint8_t rem = ((size_t)m_Buf) & 0x0f;
|
||||
if (rem)
|
||||
m_Buf += (16 - rem);
|
||||
}
|
||||
|
||||
operator uint8_t * () { return m_Buf; };
|
||||
operator const uint8_t * () const { return m_Buf; };
|
||||
ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; };
|
||||
const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; };
|
||||
|
||||
private:
|
||||
|
||||
uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment
|
||||
uint8_t * m_Buf;
|
||||
};
|
||||
|
||||
|
||||
#ifdef AESNI
|
||||
class ECBCryptoAESNI
|
||||
{
|
||||
public:
|
||||
|
||||
uint8_t * GetKeySchedule () { return m_KeySchedule; };
|
||||
|
||||
protected:
|
||||
|
||||
void ExpandKey (const AESKey& key);
|
||||
|
||||
private:
|
||||
|
||||
AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes
|
||||
};
|
||||
|
||||
class ECBEncryptionAESNI: public ECBCryptoAESNI
|
||||
{
|
||||
public:
|
||||
|
||||
void SetKey (const AESKey& key) { ExpandKey (key); };
|
||||
void Encrypt (const ChipherBlock * in, ChipherBlock * out);
|
||||
};
|
||||
|
||||
class ECBDecryptionAESNI: public ECBCryptoAESNI
|
||||
{
|
||||
public:
|
||||
|
||||
void SetKey (const AESKey& key);
|
||||
void Decrypt (const ChipherBlock * in, ChipherBlock * out);
|
||||
};
|
||||
|
||||
typedef ECBEncryptionAESNI ECBEncryption;
|
||||
typedef ECBDecryptionAESNI ECBDecryption;
|
||||
|
||||
#else // use openssl
|
||||
|
||||
class ECBEncryption
|
||||
{
|
||||
public:
|
||||
|
||||
void SetKey (const AESKey& key)
|
||||
{
|
||||
AES_set_encrypt_key (key, 256, &m_Key);
|
||||
}
|
||||
void Encrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||
{
|
||||
AES_encrypt (in->buf, out->buf, &m_Key);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
AES_KEY m_Key;
|
||||
};
|
||||
|
||||
class ECBDecryption
|
||||
{
|
||||
public:
|
||||
|
||||
void SetKey (const AESKey& key)
|
||||
{
|
||||
AES_set_decrypt_key (key, 256, &m_Key);
|
||||
}
|
||||
void Decrypt (const ChipherBlock * in, ChipherBlock * out)
|
||||
{
|
||||
AES_decrypt (in->buf, out->buf, &m_Key);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
AES_KEY m_Key;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
class CBCEncryption
|
||||
{
|
||||
public:
|
||||
|
||||
CBCEncryption () { memset ((uint8_t *)m_LastBlock, 0, 16); };
|
||||
|
||||
void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes
|
||||
void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_LastBlock, iv, 16); }; // 16 bytes
|
||||
|
||||
void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
|
||||
void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out);
|
||||
void Encrypt (const uint8_t * in, uint8_t * out); // one block
|
||||
|
||||
private:
|
||||
|
||||
AESAlignedBuffer<16> m_LastBlock;
|
||||
|
||||
ECBEncryption m_ECBEncryption;
|
||||
};
|
||||
|
||||
class CBCDecryption
|
||||
{
|
||||
public:
|
||||
|
||||
CBCDecryption () { memset ((uint8_t *)m_IV, 0, 16); };
|
||||
|
||||
void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes
|
||||
void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_IV, iv, 16); }; // 16 bytes
|
||||
|
||||
void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
|
||||
void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out);
|
||||
void Decrypt (const uint8_t * in, uint8_t * out); // one block
|
||||
|
||||
private:
|
||||
|
||||
AESAlignedBuffer<16> m_IV;
|
||||
ECBDecryption m_ECBDecryption;
|
||||
};
|
||||
|
||||
class TunnelEncryption // with double IV encryption
|
||||
{
|
||||
public:
|
||||
|
||||
void SetKeys (const AESKey& layerKey, const AESKey& ivKey)
|
||||
{
|
||||
m_LayerEncryption.SetKey (layerKey);
|
||||
m_IVEncryption.SetKey (ivKey);
|
||||
}
|
||||
|
||||
void Encrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
|
||||
|
||||
private:
|
||||
|
||||
ECBEncryption m_IVEncryption;
|
||||
#ifdef AESNI
|
||||
ECBEncryption m_LayerEncryption;
|
||||
#else
|
||||
CBCEncryption m_LayerEncryption;
|
||||
#endif
|
||||
};
|
||||
|
||||
class TunnelDecryption // with double IV encryption
|
||||
{
|
||||
public:
|
||||
|
||||
void SetKeys (const AESKey& layerKey, const AESKey& ivKey)
|
||||
{
|
||||
m_LayerDecryption.SetKey (layerKey);
|
||||
m_IVDecryption.SetKey (ivKey);
|
||||
}
|
||||
|
||||
void Decrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data)
|
||||
|
||||
private:
|
||||
|
||||
ECBDecryption m_IVDecryption;
|
||||
#ifdef AESNI
|
||||
ECBDecryption m_LayerDecryption;
|
||||
#else
|
||||
CBCDecryption m_LayerDecryption;
|
||||
#endif
|
||||
};
|
||||
|
||||
void InitCrypto (bool precomputation);
|
||||
void TerminateCrypto ();
|
||||
}
|
||||
}
|
||||
|
||||
// take care about openssl version
|
||||
#include <openssl/opensslv.h>
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER) // 1.1.0 or LibreSSL
|
||||
// define getters and setters introduced in 1.1.0
|
||||
inline int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g)
|
||||
{
|
||||
if (d->p) BN_free (d->p);
|
||||
if (d->q) BN_free (d->q);
|
||||
if (d->g) BN_free (d->g);
|
||||
d->p = p; d->q = q; d->g = g; return 1;
|
||||
}
|
||||
inline int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key)
|
||||
{
|
||||
if (d->pub_key) BN_free (d->pub_key);
|
||||
if (d->priv_key) BN_free (d->priv_key);
|
||||
d->pub_key = pub_key; d->priv_key = priv_key; return 1;
|
||||
}
|
||||
inline void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key)
|
||||
{ *pub_key = d->pub_key; *priv_key = d->priv_key; }
|
||||
inline int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s)
|
||||
{
|
||||
if (sig->r) BN_free (sig->r);
|
||||
if (sig->s) BN_free (sig->s);
|
||||
sig->r = r; sig->s = s; return 1;
|
||||
}
|
||||
inline void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
|
||||
{ *pr = sig->r; *ps = sig->s; }
|
||||
|
||||
inline int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
|
||||
{
|
||||
if (sig->r) BN_free (sig->r);
|
||||
if (sig->s) BN_free (sig->s);
|
||||
sig->r = r; sig->s = s; return 1;
|
||||
}
|
||||
inline void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
|
||||
{ *pr = sig->r; *ps = sig->s; }
|
||||
|
||||
inline int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
|
||||
{
|
||||
if (r->n) BN_free (r->n);
|
||||
if (r->e) BN_free (r->e);
|
||||
if (r->d) BN_free (r->d);
|
||||
r->n = n; r->e = e; r->d = d; return 1;
|
||||
}
|
||||
inline void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
|
||||
{ *n = r->n; *e = r->e; *d = r->d; }
|
||||
|
||||
inline int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
|
||||
{
|
||||
if (dh->p) BN_free (dh->p);
|
||||
if (dh->q) BN_free (dh->q);
|
||||
if (dh->g) BN_free (dh->g);
|
||||
dh->p = p; dh->q = q; dh->g = g; return 1;
|
||||
}
|
||||
inline int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key)
|
||||
{
|
||||
if (dh->pub_key) BN_free (dh->pub_key);
|
||||
if (dh->priv_key) BN_free (dh->priv_key);
|
||||
dh->pub_key = pub_key; dh->priv_key = priv_key; return 1;
|
||||
}
|
||||
inline void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key)
|
||||
{ *pub_key = dh->pub_key; *priv_key = dh->priv_key; }
|
||||
|
||||
inline RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey)
|
||||
{ return pkey->pkey.rsa; }
|
||||
|
||||
inline EVP_MD_CTX *EVP_MD_CTX_new ()
|
||||
{ return EVP_MD_CTX_create(); }
|
||||
inline void EVP_MD_CTX_free (EVP_MD_CTX *ctx)
|
||||
{ EVP_MD_CTX_destroy (ctx); }
|
||||
|
||||
// ssl
|
||||
#define TLS_method TLSv1_method
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
369
libi2pd/Datagram.cpp
Normal file
369
libi2pd/Datagram.cpp
Normal file
|
@ -0,0 +1,369 @@
|
|||
#include <string.h>
|
||||
#include <vector>
|
||||
#include "Crypto.h"
|
||||
#include "Log.h"
|
||||
#include "TunnelBase.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Destination.h"
|
||||
#include "Datagram.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace datagram
|
||||
{
|
||||
DatagramDestination::DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner):
|
||||
m_Owner (owner.get()),
|
||||
m_Receiver (nullptr)
|
||||
{
|
||||
m_Identity.FromBase64 (owner->GetIdentity()->ToBase64());
|
||||
}
|
||||
|
||||
DatagramDestination::~DatagramDestination ()
|
||||
{
|
||||
m_Sessions.clear();
|
||||
}
|
||||
|
||||
void DatagramDestination::SendDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort)
|
||||
{
|
||||
auto owner = m_Owner;
|
||||
std::vector<uint8_t> v(MAX_DATAGRAM_SIZE);
|
||||
uint8_t * buf = v.data();
|
||||
auto identityLen = m_Identity.ToBuffer (buf, MAX_DATAGRAM_SIZE);
|
||||
uint8_t * signature = buf + identityLen;
|
||||
auto signatureLen = m_Identity.GetSignatureLen ();
|
||||
uint8_t * buf1 = signature + signatureLen;
|
||||
size_t headerLen = identityLen + signatureLen;
|
||||
|
||||
memcpy (buf1, payload, len);
|
||||
if (m_Identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
|
||||
{
|
||||
uint8_t hash[32];
|
||||
SHA256(buf1, len, hash);
|
||||
owner->Sign (hash, 32, signature);
|
||||
}
|
||||
else
|
||||
owner->Sign (buf1, len, signature);
|
||||
|
||||
auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort);
|
||||
auto session = ObtainSession(identity);
|
||||
session->SendMsg(msg);
|
||||
}
|
||||
|
||||
|
||||
void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len)
|
||||
{
|
||||
i2p::data::IdentityEx identity;
|
||||
size_t identityLen = identity.FromBuffer (buf, len);
|
||||
const uint8_t * signature = buf + identityLen;
|
||||
size_t headerLen = identityLen + identity.GetSignatureLen ();
|
||||
|
||||
bool verified = false;
|
||||
if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
|
||||
{
|
||||
uint8_t hash[32];
|
||||
SHA256(buf + headerLen, len - headerLen, hash);
|
||||
verified = identity.Verify (hash, 32, signature);
|
||||
}
|
||||
else
|
||||
verified = identity.Verify (buf + headerLen, len - headerLen, signature);
|
||||
|
||||
if (verified)
|
||||
{
|
||||
auto h = identity.GetIdentHash();
|
||||
auto session = ObtainSession(h);
|
||||
session->Ack();
|
||||
auto r = FindReceiver(toPort);
|
||||
if(r)
|
||||
r(identity, fromPort, toPort, buf + headerLen, len -headerLen);
|
||||
else
|
||||
LogPrint (eLogWarning, "DatagramDestination: no receiver for port ", toPort);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Datagram signature verification failed");
|
||||
}
|
||||
|
||||
DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_ReceiversMutex);
|
||||
Receiver r = m_Receiver;
|
||||
auto itr = m_ReceiversByPorts.find(port);
|
||||
if (itr != m_ReceiversByPorts.end())
|
||||
r = itr->second;
|
||||
return r;
|
||||
}
|
||||
|
||||
void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
|
||||
{
|
||||
// unzip it
|
||||
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
|
||||
size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE);
|
||||
if (uncompressedLen)
|
||||
HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
|
||||
else
|
||||
LogPrint (eLogWarning, "Datagram: decompression failed");
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
|
||||
{
|
||||
auto msg = NewI2NPMessage ();
|
||||
uint8_t * buf = msg->GetPayload ();
|
||||
buf += 4; // reserve for length
|
||||
size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len);
|
||||
if (size)
|
||||
{
|
||||
htobe32buf (msg->GetPayload (), size); // length
|
||||
htobe16buf (buf + 4, fromPort); // source port
|
||||
htobe16buf (buf + 6, toPort); // destination port
|
||||
buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
|
||||
msg->len += size + 4;
|
||||
msg->FillI2NPMessageHeader (eI2NPData);
|
||||
}
|
||||
else
|
||||
msg = nullptr;
|
||||
return msg;
|
||||
}
|
||||
|
||||
void DatagramDestination::CleanUp ()
|
||||
{
|
||||
if (m_Sessions.empty ()) return;
|
||||
auto now = i2p::util::GetMillisecondsSinceEpoch();
|
||||
LogPrint(eLogDebug, "DatagramDestination: clean up sessions");
|
||||
std::unique_lock<std::mutex> lock(m_SessionsMutex);
|
||||
// for each session ...
|
||||
for (auto it = m_Sessions.begin (); it != m_Sessions.end (); )
|
||||
{
|
||||
// check if expired
|
||||
if (now - it->second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE)
|
||||
{
|
||||
LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", it->first.ToBase32());
|
||||
it->second->Stop ();
|
||||
it = m_Sessions.erase (it); // we are expired
|
||||
}
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<DatagramSession> DatagramDestination::ObtainSession(const i2p::data::IdentHash & identity)
|
||||
{
|
||||
std::shared_ptr<DatagramSession> session = nullptr;
|
||||
std::lock_guard<std::mutex> lock(m_SessionsMutex);
|
||||
auto itr = m_Sessions.find(identity);
|
||||
if (itr == m_Sessions.end()) {
|
||||
// not found, create new session
|
||||
session = std::make_shared<DatagramSession>(m_Owner, identity);
|
||||
session->Start ();
|
||||
m_Sessions[identity] = session;
|
||||
} else {
|
||||
session = itr->second;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
std::shared_ptr<DatagramSession::Info> DatagramDestination::GetInfoForRemote(const i2p::data::IdentHash & remote)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_SessionsMutex);
|
||||
for ( auto & item : m_Sessions)
|
||||
{
|
||||
if(item.first == remote) return std::make_shared<DatagramSession::Info>(item.second->GetSessionInfo());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination,
|
||||
const i2p::data::IdentHash & remoteIdent) :
|
||||
m_LocalDestination(localDestination),
|
||||
m_RemoteIdent(remoteIdent),
|
||||
m_SendQueueTimer(localDestination->GetService()),
|
||||
m_RequestingLS(false)
|
||||
{
|
||||
}
|
||||
|
||||
void DatagramSession::Start ()
|
||||
{
|
||||
m_LastUse = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
ScheduleFlushSendQueue();
|
||||
}
|
||||
|
||||
void DatagramSession::Stop ()
|
||||
{
|
||||
m_SendQueueTimer.cancel ();
|
||||
}
|
||||
|
||||
void DatagramSession::SendMsg(std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
// we used this session
|
||||
m_LastUse = i2p::util::GetMillisecondsSinceEpoch();
|
||||
// schedule send
|
||||
auto self = shared_from_this();
|
||||
m_LocalDestination->GetService().post(std::bind(&DatagramSession::HandleSend, self, msg));
|
||||
}
|
||||
|
||||
DatagramSession::Info DatagramSession::GetSessionInfo() const
|
||||
{
|
||||
if(!m_RoutingSession)
|
||||
return DatagramSession::Info(nullptr, nullptr, m_LastUse);
|
||||
|
||||
auto routingPath = m_RoutingSession->GetSharedRoutingPath();
|
||||
if (!routingPath)
|
||||
return DatagramSession::Info(nullptr, nullptr, m_LastUse);
|
||||
auto lease = routingPath->remoteLease;
|
||||
auto tunnel = routingPath->outboundTunnel;
|
||||
if(lease)
|
||||
{
|
||||
if(tunnel)
|
||||
return DatagramSession::Info(lease->tunnelGateway, tunnel->GetEndpointIdentHash(), m_LastUse);
|
||||
else
|
||||
return DatagramSession::Info(lease->tunnelGateway, nullptr, m_LastUse);
|
||||
}
|
||||
else if(tunnel)
|
||||
return DatagramSession::Info(nullptr, tunnel->GetEndpointIdentHash(), m_LastUse);
|
||||
else
|
||||
return DatagramSession::Info(nullptr, nullptr, m_LastUse);
|
||||
}
|
||||
|
||||
void DatagramSession::Ack()
|
||||
{
|
||||
m_LastUse = i2p::util::GetMillisecondsSinceEpoch();
|
||||
auto path = GetSharedRoutingPath();
|
||||
if(path)
|
||||
path->updateTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::garlic::GarlicRoutingPath> DatagramSession::GetSharedRoutingPath ()
|
||||
{
|
||||
if(!m_RoutingSession) {
|
||||
if(!m_RemoteLeaseSet) {
|
||||
m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent);
|
||||
}
|
||||
if(!m_RemoteLeaseSet) {
|
||||
// no remote lease set
|
||||
if(!m_RequestingLS) {
|
||||
m_RequestingLS = true;
|
||||
m_LocalDestination->RequestDestination(m_RemoteIdent, std::bind(&DatagramSession::HandleLeaseSetUpdated, this, std::placeholders::_1));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true);
|
||||
}
|
||||
auto path = m_RoutingSession->GetSharedRoutingPath();
|
||||
if(path) {
|
||||
if (m_CurrentOutboundTunnel && !m_CurrentOutboundTunnel->IsEstablished()) {
|
||||
// bad outbound tunnel, switch outbound tunnel
|
||||
m_CurrentOutboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(m_CurrentOutboundTunnel);
|
||||
path->outboundTunnel = m_CurrentOutboundTunnel;
|
||||
}
|
||||
if(m_CurrentRemoteLease && m_CurrentRemoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) {
|
||||
// bad lease, switch to next one
|
||||
if(m_RemoteLeaseSet && m_RemoteLeaseSet->IsExpired())
|
||||
m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent);
|
||||
if(m_RemoteLeaseSet) {
|
||||
auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding([&](const i2p::data::Lease& l) -> bool {
|
||||
return l.tunnelID == m_CurrentRemoteLease->tunnelID;
|
||||
});
|
||||
auto sz = ls.size();
|
||||
if (sz) {
|
||||
auto idx = rand() % sz;
|
||||
m_CurrentRemoteLease = ls[idx];
|
||||
}
|
||||
} else {
|
||||
// no remote lease set?
|
||||
LogPrint(eLogWarning, "DatagramSession: no cached remote lease set for ", m_RemoteIdent.ToBase32());
|
||||
}
|
||||
path->remoteLease = m_CurrentRemoteLease;
|
||||
}
|
||||
} else {
|
||||
// no current path, make one
|
||||
path = std::make_shared<i2p::garlic::GarlicRoutingPath>();
|
||||
// switch outbound tunnel if bad
|
||||
if(m_CurrentOutboundTunnel == nullptr || ! m_CurrentOutboundTunnel->IsEstablished()) {
|
||||
m_CurrentOutboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(m_CurrentOutboundTunnel);
|
||||
}
|
||||
// switch lease if bad
|
||||
if(m_CurrentRemoteLease && m_CurrentRemoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) {
|
||||
if(!m_RemoteLeaseSet) {
|
||||
m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent);
|
||||
}
|
||||
if(m_RemoteLeaseSet) {
|
||||
// pick random next good lease
|
||||
auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding([&] (const i2p::data::Lease & l) -> bool {
|
||||
if(m_CurrentRemoteLease)
|
||||
return l.tunnelGateway == m_CurrentRemoteLease->tunnelGateway;
|
||||
return false;
|
||||
});
|
||||
auto sz = ls.size();
|
||||
if(sz) {
|
||||
auto idx = rand() % sz;
|
||||
m_CurrentRemoteLease = ls[idx];
|
||||
}
|
||||
} else {
|
||||
// no remote lease set currently, bail
|
||||
LogPrint(eLogWarning, "DatagramSession: no remote lease set found for ", m_RemoteIdent.ToBase32());
|
||||
return nullptr;
|
||||
}
|
||||
} else if (!m_CurrentRemoteLease) {
|
||||
if(!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent);
|
||||
if (m_RemoteLeaseSet)
|
||||
{
|
||||
auto ls = m_RemoteLeaseSet->GetNonExpiredLeases();
|
||||
auto sz = ls.size();
|
||||
if (sz) {
|
||||
auto idx = rand() % sz;
|
||||
m_CurrentRemoteLease = ls[idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
path->outboundTunnel = m_CurrentOutboundTunnel;
|
||||
path->remoteLease = m_CurrentRemoteLease;
|
||||
m_RoutingSession->SetSharedRoutingPath(path);
|
||||
}
|
||||
return path;
|
||||
|
||||
}
|
||||
|
||||
void DatagramSession::HandleLeaseSetUpdated(std::shared_ptr<i2p::data::LeaseSet> ls)
|
||||
{
|
||||
m_RequestingLS = false;
|
||||
if(!ls) return;
|
||||
// only update lease set if found and newer than previous lease set
|
||||
uint64_t oldExpire = 0;
|
||||
if(m_RemoteLeaseSet) oldExpire = m_RemoteLeaseSet->GetExpirationTime();
|
||||
if(ls && ls->GetExpirationTime() > oldExpire) m_RemoteLeaseSet = ls;
|
||||
}
|
||||
|
||||
void DatagramSession::HandleSend(std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
m_SendQueue.push_back(msg);
|
||||
// flush queue right away if full
|
||||
if(m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue();
|
||||
}
|
||||
|
||||
void DatagramSession::FlushSendQueue ()
|
||||
{
|
||||
|
||||
std::vector<i2p::tunnel::TunnelMessageBlock> send;
|
||||
auto routingPath = GetSharedRoutingPath();
|
||||
// if we don't have a routing path we will drop all queued messages
|
||||
if(routingPath && routingPath->outboundTunnel && routingPath->remoteLease)
|
||||
{
|
||||
for (const auto & msg : m_SendQueue)
|
||||
{
|
||||
auto m = m_RoutingSession->WrapSingleMessage(msg);
|
||||
send.push_back(i2p::tunnel::TunnelMessageBlock{i2p::tunnel::eDeliveryTypeTunnel,routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, m});
|
||||
}
|
||||
routingPath->outboundTunnel->SendTunnelDataMsg(send);
|
||||
}
|
||||
m_SendQueue.clear();
|
||||
ScheduleFlushSendQueue();
|
||||
}
|
||||
|
||||
void DatagramSession::ScheduleFlushSendQueue()
|
||||
{
|
||||
boost::posix_time::milliseconds dlt(10);
|
||||
m_SendQueueTimer.expires_from_now(dlt);
|
||||
auto self = shared_from_this();
|
||||
m_SendQueueTimer.async_wait([self](const boost::system::error_code & ec) { if(ec) return; self->FlushSendQueue(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
149
libi2pd/Datagram.h
Normal file
149
libi2pd/Datagram.h
Normal file
|
@ -0,0 +1,149 @@
|
|||
#ifndef DATAGRAM_H__
|
||||
#define DATAGRAM_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include "Base.h"
|
||||
#include "Identity.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Garlic.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
class ClientDestination;
|
||||
}
|
||||
namespace datagram
|
||||
{
|
||||
// milliseconds for max session idle time
|
||||
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
|
||||
const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000;
|
||||
// milliseconds before lease expire should we try switching leases
|
||||
const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW = 30 * 1000;
|
||||
// milliseconds fudge factor for leases handover
|
||||
const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE = 1000;
|
||||
// milliseconds minimum time between path switches
|
||||
const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000;
|
||||
// max 64 messages buffered in send queue for each datagram session
|
||||
const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64;
|
||||
|
||||
class DatagramSession : public std::enable_shared_from_this<DatagramSession>
|
||||
{
|
||||
public:
|
||||
DatagramSession(i2p::client::ClientDestination * localDestination, const i2p::data::IdentHash & remoteIdent);
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
|
||||
/** @brief ack the garlic routing path */
|
||||
void Ack();
|
||||
|
||||
/** send an i2np message to remote endpoint for this session */
|
||||
void SendMsg(std::shared_ptr<I2NPMessage> msg);
|
||||
/** get the last time in milliseconds for when we used this datagram session */
|
||||
uint64_t LastActivity() const { return m_LastUse; }
|
||||
|
||||
struct Info
|
||||
{
|
||||
std::shared_ptr<const i2p::data::IdentHash> IBGW;
|
||||
std::shared_ptr<const i2p::data::IdentHash> OBEP;
|
||||
const uint64_t activity;
|
||||
|
||||
Info() : IBGW(nullptr), OBEP(nullptr), activity(0) {}
|
||||
Info(const uint8_t * ibgw, const uint8_t * obep, const uint64_t a) :
|
||||
activity(a) {
|
||||
if(ibgw) IBGW = std::make_shared<i2p::data::IdentHash>(ibgw);
|
||||
else IBGW = nullptr;
|
||||
if(obep) OBEP = std::make_shared<i2p::data::IdentHash>(obep);
|
||||
else OBEP = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
Info GetSessionInfo() const;
|
||||
|
||||
private:
|
||||
|
||||
void FlushSendQueue();
|
||||
void ScheduleFlushSendQueue();
|
||||
|
||||
void HandleSend(std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
std::shared_ptr<i2p::garlic::GarlicRoutingPath> GetSharedRoutingPath();
|
||||
|
||||
void HandleLeaseSetUpdated(std::shared_ptr<i2p::data::LeaseSet> ls);
|
||||
|
||||
private:
|
||||
i2p::client::ClientDestination * m_LocalDestination;
|
||||
i2p::data::IdentHash m_RemoteIdent;
|
||||
std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
|
||||
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
|
||||
std::shared_ptr<const i2p::data::Lease> m_CurrentRemoteLease;
|
||||
std::shared_ptr<i2p::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
|
||||
boost::asio::deadline_timer m_SendQueueTimer;
|
||||
std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
|
||||
uint64_t m_LastUse;
|
||||
bool m_RequestingLS;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<DatagramSession> DatagramSession_ptr;
|
||||
|
||||
const size_t MAX_DATAGRAM_SIZE = 32768;
|
||||
class DatagramDestination
|
||||
{
|
||||
typedef std::function<void (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)> Receiver;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner);
|
||||
~DatagramDestination ();
|
||||
|
||||
void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0);
|
||||
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
|
||||
|
||||
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
|
||||
void ResetReceiver () { m_Receiver = nullptr; };
|
||||
|
||||
void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; };
|
||||
void ResetReceiver (uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); };
|
||||
|
||||
std::shared_ptr<DatagramSession::Info> GetInfoForRemote(const i2p::data::IdentHash & remote);
|
||||
|
||||
// clean up stale sessions
|
||||
void CleanUp ();
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<DatagramSession> ObtainSession(const i2p::data::IdentHash & ident);
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort);
|
||||
|
||||
void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len);
|
||||
|
||||
/** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */
|
||||
Receiver FindReceiver(uint16_t port);
|
||||
|
||||
private:
|
||||
i2p::client::ClientDestination * m_Owner;
|
||||
i2p::data::IdentityEx m_Identity;
|
||||
Receiver m_Receiver; // default
|
||||
std::mutex m_SessionsMutex;
|
||||
std::map<i2p::data::IdentHash, DatagramSession_ptr > m_Sessions;
|
||||
std::mutex m_ReceiversMutex;
|
||||
std::map<uint16_t, Receiver> m_ReceiversByPorts;
|
||||
|
||||
i2p::data::GzipInflator m_Inflator;
|
||||
i2p::data::GzipDeflator m_Deflator;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
946
libi2pd/Destination.cpp
Normal file
946
libi2pd/Destination.cpp
Normal file
|
@ -0,0 +1,946 @@
|
|||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include "Crypto.h"
|
||||
#include "Log.h"
|
||||
#include "FS.h"
|
||||
#include "Timestamp.h"
|
||||
#include "NetDb.h"
|
||||
#include "Destination.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
LeaseSetDestination::LeaseSetDestination (bool isPublic, const std::map<std::string, std::string> * params):
|
||||
m_IsRunning (false), m_Thread (nullptr), m_IsPublic (isPublic),
|
||||
m_PublishReplyToken (0), m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service),
|
||||
m_PublishVerificationTimer (m_Service), m_PublishDelayTimer (m_Service), m_CleanupTimer (m_Service)
|
||||
{
|
||||
int inLen = DEFAULT_INBOUND_TUNNEL_LENGTH;
|
||||
int inQty = DEFAULT_INBOUND_TUNNELS_QUANTITY;
|
||||
int outLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH;
|
||||
int outQty = DEFAULT_OUTBOUND_TUNNELS_QUANTITY;
|
||||
int numTags = DEFAULT_TAGS_TO_SEND;
|
||||
std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
|
||||
try {
|
||||
if (params) {
|
||||
auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH);
|
||||
if (it != params->end ())
|
||||
inLen = std::stoi(it->second);
|
||||
it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH);
|
||||
if (it != params->end ())
|
||||
outLen = std::stoi(it->second);
|
||||
it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY);
|
||||
if (it != params->end ())
|
||||
inQty = std::stoi(it->second);
|
||||
it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY);
|
||||
if (it != params->end ())
|
||||
outQty = std::stoi(it->second);
|
||||
it = params->find (I2CP_PARAM_TAGS_TO_SEND);
|
||||
if (it != params->end ())
|
||||
numTags = std::stoi(it->second);
|
||||
LogPrint (eLogInfo, "Destination: parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags");
|
||||
it = params->find (I2CP_PARAM_EXPLICIT_PEERS);
|
||||
if (it != params->end ())
|
||||
{
|
||||
explicitPeers = std::make_shared<std::vector<i2p::data::IdentHash> >();
|
||||
std::stringstream ss(it->second);
|
||||
std::string b64;
|
||||
while (std::getline (ss, b64, ','))
|
||||
{
|
||||
i2p::data::IdentHash ident;
|
||||
ident.FromBase64 (b64);
|
||||
explicitPeers->push_back (ident);
|
||||
LogPrint (eLogInfo, "Destination: Added to explicit peers list: ", b64);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::exception & ex) {
|
||||
LogPrint(eLogError, "Destination: unable to parse parameters for destination: ", ex.what());
|
||||
}
|
||||
SetNumTags (numTags);
|
||||
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty);
|
||||
if (explicitPeers)
|
||||
m_Pool->SetExplicitPeers (explicitPeers);
|
||||
if(params)
|
||||
{
|
||||
auto itr = params->find(I2CP_PARAM_MAX_TUNNEL_LATENCY);
|
||||
if (itr != params->end()) {
|
||||
auto maxlatency = std::stoi(itr->second);
|
||||
itr = params->find(I2CP_PARAM_MIN_TUNNEL_LATENCY);
|
||||
if (itr != params->end()) {
|
||||
auto minlatency = std::stoi(itr->second);
|
||||
if ( minlatency > 0 && maxlatency > 0 ) {
|
||||
// set tunnel pool latency
|
||||
LogPrint(eLogInfo, "Destination: requiring tunnel latency [", minlatency, "ms, ", maxlatency, "ms]");
|
||||
m_Pool->RequireLatency(minlatency, maxlatency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LeaseSetDestination::~LeaseSetDestination ()
|
||||
{
|
||||
if (m_IsRunning)
|
||||
Stop ();
|
||||
if (m_Pool)
|
||||
i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool);
|
||||
for (auto& it: m_LeaseSetRequests)
|
||||
it.second->Complete (nullptr);
|
||||
}
|
||||
|
||||
void LeaseSetDestination::Run ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Service.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "Destination: runtime exception: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LeaseSetDestination::Start ()
|
||||
{
|
||||
if (!m_IsRunning)
|
||||
{
|
||||
LoadTags ();
|
||||
m_IsRunning = true;
|
||||
m_Pool->SetLocalDestination (shared_from_this ());
|
||||
m_Pool->SetActive (true);
|
||||
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
|
||||
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ()));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LeaseSetDestination::Stop ()
|
||||
{
|
||||
if (m_IsRunning)
|
||||
{
|
||||
m_CleanupTimer.cancel ();
|
||||
m_PublishConfirmationTimer.cancel ();
|
||||
m_PublishVerificationTimer.cancel ();
|
||||
|
||||
m_IsRunning = false;
|
||||
if (m_Pool)
|
||||
{
|
||||
m_Pool->SetLocalDestination (nullptr);
|
||||
i2p::tunnel::tunnels.StopTunnelPool (m_Pool);
|
||||
}
|
||||
m_Service.stop ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = 0;
|
||||
}
|
||||
SaveTags ();
|
||||
CleanUp (); // GarlicDestination
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<const i2p::data::LeaseSet> LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
|
||||
{
|
||||
std::shared_ptr<i2p::data::LeaseSet> remoteLS;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
|
||||
auto it = m_RemoteLeaseSets.find (ident);
|
||||
if (it != m_RemoteLeaseSets.end ())
|
||||
remoteLS = it->second;
|
||||
}
|
||||
|
||||
if (remoteLS)
|
||||
{
|
||||
if (!remoteLS->IsExpired ())
|
||||
{
|
||||
if (remoteLS->ExpiresSoon())
|
||||
{
|
||||
LogPrint(eLogDebug, "Destination: Lease Set expires soon, updating before expire");
|
||||
// update now before expiration for smooth handover
|
||||
auto s = shared_from_this ();
|
||||
RequestDestination(ident, [s, ident] (std::shared_ptr<i2p::data::LeaseSet> ls) {
|
||||
if(ls && !ls->IsExpired())
|
||||
{
|
||||
ls->PopulateLeases();
|
||||
{
|
||||
std::lock_guard<std::mutex> _lock(s->m_RemoteLeaseSetsMutex);
|
||||
s->m_RemoteLeaseSets[ident] = ls;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return remoteLS;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Destination: remote LeaseSet expired");
|
||||
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
|
||||
m_RemoteLeaseSets.erase (ident);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ls = i2p::data::netdb.FindLeaseSet (ident);
|
||||
if (ls && !ls->IsExpired ())
|
||||
{
|
||||
ls->PopulateLeases (); // since we don't store them in netdb
|
||||
std::lock_guard<std::mutex> _lock(m_RemoteLeaseSetsMutex);
|
||||
m_RemoteLeaseSets[ident] = ls;
|
||||
return ls;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<const i2p::data::LocalLeaseSet> LeaseSetDestination::GetLeaseSet ()
|
||||
{
|
||||
if (!m_Pool) return nullptr;
|
||||
if (!m_LeaseSet)
|
||||
UpdateLeaseSet ();
|
||||
std::lock_guard<std::mutex> l(m_LeaseSetMutex);
|
||||
return m_LeaseSet;
|
||||
}
|
||||
|
||||
void LeaseSetDestination::SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> l(m_LeaseSetMutex);
|
||||
m_LeaseSet.reset (newLeaseSet);
|
||||
}
|
||||
i2p::garlic::GarlicDestination::SetLeaseSetUpdated ();
|
||||
if (m_IsPublic)
|
||||
{
|
||||
m_PublishVerificationTimer.cancel ();
|
||||
Publish ();
|
||||
}
|
||||
}
|
||||
|
||||
void LeaseSetDestination::UpdateLeaseSet ()
|
||||
{
|
||||
int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels
|
||||
if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum
|
||||
CreateNewLeaseSet (m_Pool->GetInboundTunnels (numTunnels));
|
||||
}
|
||||
|
||||
bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t k[32], t[32];
|
||||
} data;
|
||||
memcpy (data.k, key, 32);
|
||||
memcpy (data.t, tag, 32);
|
||||
auto s = shared_from_this ();
|
||||
m_Service.post ([s,data](void)
|
||||
{
|
||||
s->AddSessionKey (data.k, data.t);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg));
|
||||
}
|
||||
|
||||
void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msg));
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||
{
|
||||
uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET];
|
||||
switch (typeID)
|
||||
{
|
||||
case eI2NPData:
|
||||
HandleDataMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
|
||||
break;
|
||||
case eI2NPDeliveryStatus:
|
||||
// we assume tunnel tests non-encrypted
|
||||
HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||
break;
|
||||
case eI2NPDatabaseStore:
|
||||
HandleDatabaseStoreMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
|
||||
break;
|
||||
case eI2NPDatabaseSearchReply:
|
||||
HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET));
|
||||
break;
|
||||
default:
|
||||
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||
}
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len)
|
||||
{
|
||||
uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET);
|
||||
size_t offset = DATABASE_STORE_HEADER_SIZE;
|
||||
if (replyToken)
|
||||
{
|
||||
LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore");
|
||||
offset += 36;
|
||||
}
|
||||
i2p::data::IdentHash key (buf + DATABASE_STORE_KEY_OFFSET);
|
||||
std::shared_ptr<i2p::data::LeaseSet> leaseSet;
|
||||
if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet
|
||||
{
|
||||
LogPrint (eLogDebug, "Destination: Remote LeaseSet");
|
||||
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
|
||||
auto it = m_RemoteLeaseSets.find (key);
|
||||
if (it != m_RemoteLeaseSets.end ())
|
||||
{
|
||||
leaseSet = it->second;
|
||||
if (leaseSet->IsNewer (buf + offset, len - offset))
|
||||
{
|
||||
leaseSet->Update (buf + offset, len - offset);
|
||||
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key)
|
||||
LogPrint (eLogDebug, "Destination: Remote LeaseSet updated");
|
||||
else
|
||||
{
|
||||
LogPrint (eLogDebug, "Destination: Remote LeaseSet update failed");
|
||||
m_RemoteLeaseSets.erase (it);
|
||||
leaseSet = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogDebug, "Destination: Remote LeaseSet is older. Not updated");
|
||||
}
|
||||
else
|
||||
{
|
||||
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset);
|
||||
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key)
|
||||
{
|
||||
if (leaseSet->GetIdentHash () != GetIdentHash ())
|
||||
{
|
||||
LogPrint (eLogDebug, "Destination: New remote LeaseSet added");
|
||||
m_RemoteLeaseSets[key] = leaseSet;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "Destination: New remote LeaseSet failed");
|
||||
leaseSet = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped");
|
||||
|
||||
auto it1 = m_LeaseSetRequests.find (key);
|
||||
if (it1 != m_LeaseSetRequests.end ())
|
||||
{
|
||||
it1->second->requestTimeoutTimer.cancel ();
|
||||
if (it1->second) it1->second->Complete (leaseSet);
|
||||
m_LeaseSetRequests.erase (it1);
|
||||
}
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len)
|
||||
{
|
||||
i2p::data::IdentHash key (buf);
|
||||
int num = buf[32]; // num
|
||||
LogPrint (eLogDebug, "Destination: DatabaseSearchReply for ", key.ToBase64 (), " num=", num);
|
||||
auto it = m_LeaseSetRequests.find (key);
|
||||
if (it != m_LeaseSetRequests.end ())
|
||||
{
|
||||
auto request = it->second;
|
||||
bool found = false;
|
||||
if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
|
||||
{
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
i2p::data::IdentHash peerHash (buf + 33 + i*32);
|
||||
if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash))
|
||||
{
|
||||
LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message
|
||||
i2p::data::netdb.RequestDestination (peerHash);
|
||||
}
|
||||
}
|
||||
|
||||
auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded);
|
||||
if (floodfill)
|
||||
{
|
||||
LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ());
|
||||
if (SendLeaseSetRequest (key, floodfill, request))
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
|
||||
request->Complete (nullptr);
|
||||
m_LeaseSetRequests.erase (key);
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found");
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
|
||||
if (msgID == m_PublishReplyToken)
|
||||
{
|
||||
LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed for ", GetIdentHash().ToBase32());
|
||||
m_ExcludedFloodfills.clear ();
|
||||
m_PublishReplyToken = 0;
|
||||
// schedule verification
|
||||
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
|
||||
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
}
|
||||
else
|
||||
i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg);
|
||||
}
|
||||
|
||||
void LeaseSetDestination::SetLeaseSetUpdated ()
|
||||
{
|
||||
UpdateLeaseSet ();
|
||||
}
|
||||
|
||||
void LeaseSetDestination::Publish ()
|
||||
{
|
||||
if (!m_LeaseSet || !m_Pool)
|
||||
{
|
||||
LogPrint (eLogError, "Destination: Can't publish non-existing LeaseSet");
|
||||
return;
|
||||
}
|
||||
if (m_PublishReplyToken)
|
||||
{
|
||||
LogPrint (eLogDebug, "Destination: Publishing LeaseSet is pending");
|
||||
return;
|
||||
}
|
||||
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (ts < m_LastSubmissionTime + PUBLISH_MIN_INTERVAL)
|
||||
{
|
||||
LogPrint (eLogDebug, "Destination: Publishing LeaseSet is too fast. Wait for ", PUBLISH_MIN_INTERVAL, " seconds");
|
||||
m_PublishDelayTimer.cancel ();
|
||||
m_PublishDelayTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_MIN_INTERVAL));
|
||||
m_PublishDelayTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishDelayTimer,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
return;
|
||||
}
|
||||
auto outbound = m_Pool->GetNextOutboundTunnel ();
|
||||
if (!outbound)
|
||||
{
|
||||
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
|
||||
return;
|
||||
}
|
||||
auto inbound = m_Pool->GetNextInboundTunnel ();
|
||||
if (!inbound)
|
||||
{
|
||||
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels");
|
||||
return;
|
||||
}
|
||||
auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills);
|
||||
if (!floodfill)
|
||||
{
|
||||
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
|
||||
m_ExcludedFloodfills.clear ();
|
||||
return;
|
||||
}
|
||||
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
|
||||
LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ());
|
||||
RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4);
|
||||
auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken, inbound));
|
||||
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
|
||||
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg);
|
||||
m_LastSubmissionTime = ts;
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
if (m_PublishReplyToken)
|
||||
{
|
||||
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again");
|
||||
m_PublishReplyToken = 0;
|
||||
Publish ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
auto s = shared_from_this ();
|
||||
RequestLeaseSet (GetIdentHash (),
|
||||
// "this" added due to bug in gcc 4.7-4.8
|
||||
[s,this](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
|
||||
{
|
||||
if (leaseSet)
|
||||
{
|
||||
if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet)
|
||||
{
|
||||
// we got latest LeasetSet
|
||||
LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", GetIdentHash().ToBase32());
|
||||
s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL));
|
||||
s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1));
|
||||
return;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogDebug, "Destination: LeaseSet is different than just published for ", GetIdentHash().ToBase32());
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32());
|
||||
// we have to publish again
|
||||
s->Publish ();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandlePublishDelayTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
Publish ();
|
||||
}
|
||||
|
||||
bool LeaseSetDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
|
||||
{
|
||||
if (!m_Pool || !IsReady ())
|
||||
{
|
||||
if (requestComplete)
|
||||
m_Service.post ([requestComplete](void){requestComplete (nullptr);});
|
||||
return false;
|
||||
}
|
||||
m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete));
|
||||
return true;
|
||||
}
|
||||
|
||||
void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify)
|
||||
{
|
||||
auto s = shared_from_this ();
|
||||
m_Service.post ([dest, notify, s](void)
|
||||
{
|
||||
auto it = s->m_LeaseSetRequests.find (dest);
|
||||
if (it != s->m_LeaseSetRequests.end ())
|
||||
{
|
||||
auto requestComplete = it->second;
|
||||
s->m_LeaseSetRequests.erase (it);
|
||||
if (notify && requestComplete) requestComplete->Complete (nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
|
||||
{
|
||||
std::set<i2p::data::IdentHash> excluded;
|
||||
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded);
|
||||
if (floodfill)
|
||||
{
|
||||
auto request = std::make_shared<LeaseSetRequest> (m_Service);
|
||||
if (requestComplete)
|
||||
request->requestComplete.push_back (requestComplete);
|
||||
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> >(dest,request));
|
||||
if (ret.second) // inserted
|
||||
{
|
||||
request->requestTime = ts;
|
||||
if (!SendLeaseSetRequest (dest, floodfill, request))
|
||||
{
|
||||
// request failed
|
||||
m_LeaseSetRequests.erase (ret.first);
|
||||
if (requestComplete) requestComplete (nullptr);
|
||||
}
|
||||
}
|
||||
else // duplicate
|
||||
{
|
||||
LogPrint (eLogInfo, "Destination: Request of LeaseSet ", dest.ToBase64 (), " is pending already");
|
||||
if (ts > ret.first->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
|
||||
{
|
||||
// something went wrong
|
||||
m_LeaseSetRequests.erase (ret.first);
|
||||
if (requestComplete) requestComplete (nullptr);
|
||||
}
|
||||
else if (requestComplete)
|
||||
ret.first->second->requestComplete.push_back (requestComplete);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "Destination: Can't request LeaseSet, no floodfills found");
|
||||
if (requestComplete) requestComplete (nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool LeaseSetDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest,
|
||||
std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request)
|
||||
{
|
||||
if (!request->replyTunnel || !request->replyTunnel->IsEstablished ())
|
||||
request->replyTunnel = m_Pool->GetNextInboundTunnel ();
|
||||
if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found");
|
||||
if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ())
|
||||
request->outboundTunnel = m_Pool->GetNextOutboundTunnel ();
|
||||
if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found");
|
||||
|
||||
if (request->replyTunnel && request->outboundTunnel)
|
||||
{
|
||||
request->excluded.insert (nextFloodfill->GetIdentHash ());
|
||||
request->requestTimeoutTimer.cancel ();
|
||||
|
||||
uint8_t replyKey[32], replyTag[32];
|
||||
RAND_bytes (replyKey, 32); // random session key
|
||||
RAND_bytes (replyTag, 32); // random session tag
|
||||
AddSessionKey (replyKey, replyTag);
|
||||
|
||||
auto msg = WrapMessage (nextFloodfill,
|
||||
CreateLeaseSetDatabaseLookupMsg (dest, request->excluded,
|
||||
request->replyTunnel, replyKey, replyTag));
|
||||
request->outboundTunnel->SendTunnelDataMsg (
|
||||
{
|
||||
i2p::tunnel::TunnelMessageBlock
|
||||
{
|
||||
i2p::tunnel::eDeliveryTypeRouter,
|
||||
nextFloodfill->GetIdentHash (), 0, msg
|
||||
}
|
||||
});
|
||||
request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT));
|
||||
request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer,
|
||||
shared_from_this (), std::placeholders::_1, dest));
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
auto it = m_LeaseSetRequests.find (dest);
|
||||
if (it != m_LeaseSetRequests.end ())
|
||||
{
|
||||
bool done = false;
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
|
||||
{
|
||||
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
|
||||
if (floodfill)
|
||||
{
|
||||
// reset tunnels, because one them might fail
|
||||
it->second->outboundTunnel = nullptr;
|
||||
it->second->replyTunnel = nullptr;
|
||||
done = !SendLeaseSetRequest (dest, floodfill, it->second);
|
||||
}
|
||||
else
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds");
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (done)
|
||||
{
|
||||
auto requestComplete = it->second;
|
||||
m_LeaseSetRequests.erase (it);
|
||||
if (requestComplete) requestComplete->Complete (nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LeaseSetDestination::HandleCleanupTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
CleanupExpiredTags ();
|
||||
CleanupRemoteLeaseSets ();
|
||||
CleanupDestination ();
|
||||
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
|
||||
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
|
||||
shared_from_this (), std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
|
||||
void LeaseSetDestination::CleanupRemoteLeaseSets ()
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
|
||||
for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();)
|
||||
{
|
||||
if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired
|
||||
{
|
||||
LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
|
||||
it = m_RemoteLeaseSets.erase (it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params):
|
||||
LeaseSetDestination (isPublic, params),
|
||||
m_Keys (keys), m_DatagramDestination (nullptr),
|
||||
m_ReadyChecker(GetService())
|
||||
{
|
||||
if (isPublic)
|
||||
PersistTemporaryKeys ();
|
||||
else
|
||||
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
|
||||
if (isPublic)
|
||||
LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created");
|
||||
}
|
||||
|
||||
ClientDestination::~ClientDestination ()
|
||||
{
|
||||
}
|
||||
|
||||
bool ClientDestination::Start ()
|
||||
{
|
||||
if (LeaseSetDestination::Start ())
|
||||
{
|
||||
m_StreamingDestination = std::make_shared<i2p::stream::StreamingDestination> (GetSharedFromThis ()); // TODO:
|
||||
m_StreamingDestination->Start ();
|
||||
for (auto& it: m_StreamingDestinationsByPorts)
|
||||
it.second->Start ();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ClientDestination::Stop ()
|
||||
{
|
||||
if (LeaseSetDestination::Stop ())
|
||||
{
|
||||
m_ReadyChecker.cancel();
|
||||
m_StreamingDestination->Stop ();
|
||||
//m_StreamingDestination->SetOwner (nullptr);
|
||||
m_StreamingDestination = nullptr;
|
||||
for (auto& it: m_StreamingDestinationsByPorts)
|
||||
{
|
||||
it.second->Stop ();
|
||||
//it.second->SetOwner (nullptr);
|
||||
}
|
||||
m_StreamingDestinationsByPorts.clear ();
|
||||
if (m_DatagramDestination)
|
||||
{
|
||||
delete m_DatagramDestination;
|
||||
m_DatagramDestination = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef I2LUA
|
||||
void ClientDestination::Ready(ReadyPromise & p)
|
||||
{
|
||||
ScheduleCheckForReady(&p);
|
||||
}
|
||||
|
||||
void ClientDestination::ScheduleCheckForReady(ReadyPromise * p)
|
||||
{
|
||||
// tick every 100ms
|
||||
m_ReadyChecker.expires_from_now(boost::posix_time::milliseconds(100));
|
||||
m_ReadyChecker.async_wait([&, p] (const boost::system::error_code & ecode) {
|
||||
HandleCheckForReady(ecode, p);
|
||||
});
|
||||
}
|
||||
|
||||
void ClientDestination::HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p)
|
||||
{
|
||||
if(ecode) // error happened
|
||||
p->set_value(nullptr);
|
||||
else if(IsReady()) // we are ready
|
||||
p->set_value(std::shared_ptr<ClientDestination>(this));
|
||||
else // we are not ready
|
||||
ScheduleCheckForReady(p);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len)
|
||||
{
|
||||
uint32_t length = bufbe32toh (buf);
|
||||
buf += 4;
|
||||
// we assume I2CP payload
|
||||
uint16_t fromPort = bufbe16toh (buf + 4), // source
|
||||
toPort = bufbe16toh (buf + 6); // destination
|
||||
switch (buf[9])
|
||||
{
|
||||
case PROTOCOL_TYPE_STREAMING:
|
||||
{
|
||||
// streaming protocol
|
||||
auto dest = GetStreamingDestination (toPort);
|
||||
if (dest)
|
||||
dest->HandleDataMessagePayload (buf, length);
|
||||
else
|
||||
LogPrint (eLogError, "Destination: Missing streaming destination");
|
||||
}
|
||||
break;
|
||||
case PROTOCOL_TYPE_DATAGRAM:
|
||||
// datagram protocol
|
||||
if (m_DatagramDestination)
|
||||
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length);
|
||||
else
|
||||
LogPrint (eLogError, "Destination: Missing datagram destination");
|
||||
break;
|
||||
default:
|
||||
LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port)
|
||||
{
|
||||
if (!streamRequestComplete)
|
||||
{
|
||||
LogPrint (eLogError, "Destination: request callback is not specified in CreateStream");
|
||||
return;
|
||||
}
|
||||
auto leaseSet = FindLeaseSet (dest);
|
||||
if (leaseSet)
|
||||
streamRequestComplete(CreateStream (leaseSet, port));
|
||||
else
|
||||
{
|
||||
auto s = GetSharedFromThis ();
|
||||
RequestDestination (dest,
|
||||
[s, streamRequestComplete, port](std::shared_ptr<i2p::data::LeaseSet> ls)
|
||||
{
|
||||
if (ls)
|
||||
streamRequestComplete(s->CreateStream (ls, port));
|
||||
else
|
||||
streamRequestComplete (nullptr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port)
|
||||
{
|
||||
if (m_StreamingDestination)
|
||||
return m_StreamingDestination->CreateNewOutgoingStream (remote, port);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::GetStreamingDestination (int port) const
|
||||
{
|
||||
if (port)
|
||||
{
|
||||
auto it = m_StreamingDestinationsByPorts.find (port);
|
||||
if (it != m_StreamingDestinationsByPorts.end ())
|
||||
return it->second;
|
||||
}
|
||||
// if port is zero or not found, use default destination
|
||||
return m_StreamingDestination;
|
||||
}
|
||||
|
||||
void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor)
|
||||
{
|
||||
if (m_StreamingDestination)
|
||||
m_StreamingDestination->SetAcceptor (acceptor);
|
||||
}
|
||||
|
||||
void ClientDestination::StopAcceptingStreams ()
|
||||
{
|
||||
if (m_StreamingDestination)
|
||||
m_StreamingDestination->ResetAcceptor ();
|
||||
}
|
||||
|
||||
bool ClientDestination::IsAcceptingStreams () const
|
||||
{
|
||||
if (m_StreamingDestination)
|
||||
return m_StreamingDestination->IsAcceptorSet ();
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClientDestination::AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor)
|
||||
{
|
||||
if (m_StreamingDestination)
|
||||
m_StreamingDestination->AcceptOnce (acceptor);
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::CreateStreamingDestination (int port, bool gzip)
|
||||
{
|
||||
auto dest = std::make_shared<i2p::stream::StreamingDestination> (GetSharedFromThis (), port, gzip);
|
||||
if (port)
|
||||
m_StreamingDestinationsByPorts[port] = dest;
|
||||
else // update default
|
||||
m_StreamingDestination = dest;
|
||||
return dest;
|
||||
}
|
||||
|
||||
i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination ()
|
||||
{
|
||||
if (m_DatagramDestination == nullptr)
|
||||
m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ());
|
||||
return m_DatagramDestination;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const i2p::stream::Stream> > ClientDestination::GetAllStreams () const
|
||||
{
|
||||
std::vector<std::shared_ptr<const i2p::stream::Stream> > ret;
|
||||
if (m_StreamingDestination)
|
||||
{
|
||||
for (auto& it: m_StreamingDestination->GetStreams ())
|
||||
ret.push_back (it.second);
|
||||
}
|
||||
for (auto& it: m_StreamingDestinationsByPorts)
|
||||
for (auto& it1: it.second->GetStreams ())
|
||||
ret.push_back (it1.second);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ClientDestination::PersistTemporaryKeys ()
|
||||
{
|
||||
std::string ident = GetIdentHash().ToBase32();
|
||||
std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat"));
|
||||
std::ifstream f(path, std::ifstream::binary);
|
||||
|
||||
if (f) {
|
||||
f.read ((char *)m_EncryptionPublicKey, 256);
|
||||
f.read ((char *)m_EncryptionPrivateKey, 256);
|
||||
return;
|
||||
}
|
||||
|
||||
LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p");
|
||||
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
|
||||
|
||||
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
|
||||
if (f1) {
|
||||
f1.write ((char *)m_EncryptionPublicKey, 256);
|
||||
f1.write ((char *)m_EncryptionPrivateKey, 256);
|
||||
return;
|
||||
}
|
||||
LogPrint(eLogError, "Destinations: Can't save keys to ", path);
|
||||
}
|
||||
|
||||
void ClientDestination::CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels)
|
||||
{
|
||||
auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), m_EncryptionPublicKey, tunnels);
|
||||
// sign
|
||||
Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO
|
||||
SetLeaseSet (leaseSet);
|
||||
}
|
||||
|
||||
void ClientDestination::CleanupDestination ()
|
||||
{
|
||||
if (m_DatagramDestination) m_DatagramDestination->CleanUp ();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
238
libi2pd/Destination.h
Normal file
238
libi2pd/Destination.h
Normal file
|
@ -0,0 +1,238 @@
|
|||
#ifndef DESTINATION_H__
|
||||
#define DESTINATION_H__
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#ifdef I2LUA
|
||||
#include <future>
|
||||
#endif
|
||||
#include <boost/asio.hpp>
|
||||
#include "Identity.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "Crypto.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "Garlic.h"
|
||||
#include "NetDb.h"
|
||||
#include "Streaming.h"
|
||||
#include "Datagram.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
const uint8_t PROTOCOL_TYPE_STREAMING = 6;
|
||||
const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
|
||||
const uint8_t PROTOCOL_TYPE_RAW = 18;
|
||||
const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
|
||||
const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successfull publish
|
||||
const int PUBLISH_MIN_INTERVAL = 20; // in seconds
|
||||
const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically
|
||||
const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds
|
||||
const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds
|
||||
const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes
|
||||
const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7;
|
||||
|
||||
// I2CP
|
||||
const char I2CP_PARAM_INBOUND_TUNNEL_LENGTH[] = "inbound.length";
|
||||
const int DEFAULT_INBOUND_TUNNEL_LENGTH = 3;
|
||||
const char I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH[] = "outbound.length";
|
||||
const int DEFAULT_OUTBOUND_TUNNEL_LENGTH = 3;
|
||||
const char I2CP_PARAM_INBOUND_TUNNELS_QUANTITY[] = "inbound.quantity";
|
||||
const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5;
|
||||
const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity";
|
||||
const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5;
|
||||
const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers";
|
||||
const int STREAM_REQUEST_TIMEOUT = 60; //in seconds
|
||||
const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend";
|
||||
const int DEFAULT_TAGS_TO_SEND = 40;
|
||||
|
||||
// latency
|
||||
const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min";
|
||||
const int DEFAULT_MIN_TUNNEL_LATENCY = 0;
|
||||
const char I2CP_PARAM_MAX_TUNNEL_LATENCY[] = "latency.max";
|
||||
const int DEFAULT_MAX_TUNNEL_LATENCY = 0;
|
||||
|
||||
typedef std::function<void (std::shared_ptr<i2p::stream::Stream> stream)> StreamRequestComplete;
|
||||
|
||||
class LeaseSetDestination: public i2p::garlic::GarlicDestination,
|
||||
public std::enable_shared_from_this<LeaseSetDestination>
|
||||
{
|
||||
typedef std::function<void (std::shared_ptr<i2p::data::LeaseSet> leaseSet)> RequestComplete;
|
||||
// leaseSet = nullptr means not found
|
||||
struct LeaseSetRequest
|
||||
{
|
||||
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
|
||||
std::set<i2p::data::IdentHash> excluded;
|
||||
uint64_t requestTime;
|
||||
boost::asio::deadline_timer requestTimeoutTimer;
|
||||
std::list<RequestComplete> requestComplete;
|
||||
std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
|
||||
std::shared_ptr<i2p::tunnel::InboundTunnel> replyTunnel;
|
||||
|
||||
void Complete (std::shared_ptr<i2p::data::LeaseSet> ls)
|
||||
{
|
||||
for (auto& it: requestComplete) it (ls);
|
||||
requestComplete.clear ();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
|
||||
LeaseSetDestination (bool isPublic, const std::map<std::string, std::string> * params = nullptr);
|
||||
~LeaseSetDestination ();
|
||||
|
||||
virtual bool Start ();
|
||||
virtual bool Stop ();
|
||||
bool IsRunning () const { return m_IsRunning; };
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () { return m_Pool; };
|
||||
bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; };
|
||||
std::shared_ptr<const i2p::data::LeaseSet> FindLeaseSet (const i2p::data::IdentHash& ident);
|
||||
bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr);
|
||||
void CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify = true);
|
||||
|
||||
// implements GarlicDestination
|
||||
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet ();
|
||||
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
|
||||
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||
|
||||
// override GarlicDestination
|
||||
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
|
||||
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
void SetLeaseSetUpdated ();
|
||||
|
||||
protected:
|
||||
|
||||
void SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet);
|
||||
virtual void CleanupDestination () {}; // additional clean up in derived classes
|
||||
// I2CP
|
||||
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
|
||||
virtual void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels) = 0;
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void UpdateLeaseSet ();
|
||||
void Publish ();
|
||||
void HandlePublishConfirmationTimer (const boost::system::error_code& ecode);
|
||||
void HandlePublishVerificationTimer (const boost::system::error_code& ecode);
|
||||
void HandlePublishDelayTimer (const boost::system::error_code& ecode);
|
||||
void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len);
|
||||
void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len);
|
||||
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete);
|
||||
bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request);
|
||||
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
|
||||
void HandleCleanupTimer (const boost::system::error_code& ecode);
|
||||
void CleanupRemoteLeaseSets ();
|
||||
|
||||
private:
|
||||
|
||||
volatile bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service m_Service;
|
||||
mutable std::mutex m_RemoteLeaseSetsMutex;
|
||||
std::map<i2p::data::IdentHash, std::shared_ptr<i2p::data::LeaseSet> > m_RemoteLeaseSets;
|
||||
std::map<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> > m_LeaseSetRequests;
|
||||
|
||||
std::shared_ptr<i2p::tunnel::TunnelPool> m_Pool;
|
||||
std::mutex m_LeaseSetMutex;
|
||||
std::shared_ptr<i2p::data::LocalLeaseSet> m_LeaseSet;
|
||||
bool m_IsPublic;
|
||||
uint32_t m_PublishReplyToken;
|
||||
uint64_t m_LastSubmissionTime; // in seconds
|
||||
std::set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
|
||||
|
||||
boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer,
|
||||
m_PublishDelayTimer, m_CleanupTimer;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); };
|
||||
const decltype(m_RemoteLeaseSets)& GetLeaseSets () const { return m_RemoteLeaseSets; };
|
||||
};
|
||||
|
||||
class ClientDestination: public LeaseSetDestination
|
||||
{
|
||||
public:
|
||||
#ifdef I2LUA
|
||||
// type for informing that a client destination is ready
|
||||
typedef std::promise<std::shared_ptr<ClientDestination> > ReadyPromise;
|
||||
// informs promise with shared_from_this() when this destination is ready to use
|
||||
// if cancelled before ready, informs promise with nullptr
|
||||
void Ready(ReadyPromise & p);
|
||||
#endif
|
||||
|
||||
ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
|
||||
~ClientDestination ();
|
||||
|
||||
virtual bool Start ();
|
||||
virtual bool Stop ();
|
||||
|
||||
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); };
|
||||
|
||||
// streaming
|
||||
std::shared_ptr<i2p::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional
|
||||
std::shared_ptr<i2p::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
|
||||
// following methods operate with default streaming destination
|
||||
void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0);
|
||||
std::shared_ptr<i2p::stream::Stream> CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
|
||||
void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor);
|
||||
void StopAcceptingStreams ();
|
||||
bool IsAcceptingStreams () const;
|
||||
void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor);
|
||||
|
||||
// datagram
|
||||
i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; };
|
||||
i2p::datagram::DatagramDestination * CreateDatagramDestination ();
|
||||
|
||||
// implements LocalDestination
|
||||
const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; };
|
||||
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
|
||||
|
||||
protected:
|
||||
|
||||
void CleanupDestination ();
|
||||
// I2CP
|
||||
void HandleDataMessage (const uint8_t * buf, size_t len);
|
||||
void CreateNewLeaseSet (std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<ClientDestination> GetSharedFromThis ()
|
||||
{ return std::static_pointer_cast<ClientDestination>(shared_from_this ()); }
|
||||
void PersistTemporaryKeys ();
|
||||
#ifdef I2LUA
|
||||
void ScheduleCheckForReady(ReadyPromise * p);
|
||||
void HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p);
|
||||
#endif
|
||||
private:
|
||||
|
||||
i2p::data::PrivateKeys m_Keys;
|
||||
uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256];
|
||||
|
||||
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
|
||||
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
|
||||
i2p::datagram::DatagramDestination * m_DatagramDestination;
|
||||
|
||||
boost::asio::deadline_timer m_ReadyChecker;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
std::vector<std::shared_ptr<const i2p::stream::Stream> > GetAllStreams () const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
61
libi2pd/Event.cpp
Normal file
61
libi2pd/Event.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
#include "Event.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace event
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EventCore core;
|
||||
#endif
|
||||
|
||||
void EventCore::SetListener(EventListener * l)
|
||||
{
|
||||
m_listener = l;
|
||||
LogPrint(eLogInfo, "Event: listener set");
|
||||
}
|
||||
|
||||
void EventCore::QueueEvent(const EventType & ev)
|
||||
{
|
||||
if(m_listener) m_listener->HandleEvent(ev);
|
||||
}
|
||||
|
||||
void EventCore::CollectEvent(const std::string & type, const std::string & ident, uint64_t val)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_collect_mutex);
|
||||
std::string key = type + "." + ident;
|
||||
if (m_collected.find(key) == m_collected.end())
|
||||
{
|
||||
m_collected[key] = {type, key, 0};
|
||||
}
|
||||
m_collected[key].Val += val;
|
||||
}
|
||||
|
||||
void EventCore::PumpCollected(EventListener * listener)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_collect_mutex);
|
||||
if(listener)
|
||||
{
|
||||
for(const auto & ev : m_collected) {
|
||||
listener->HandlePumpEvent({{"type", ev.second.Key}, {"ident", ev.second.Ident}}, ev.second.Val);
|
||||
}
|
||||
}
|
||||
m_collected.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QueueIntEvent(const std::string & type, const std::string & ident, uint64_t val)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
i2p::event::core.CollectEvent(type, ident, val);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EmitEvent(const EventType & e)
|
||||
{
|
||||
#if WITH_EVENTS
|
||||
i2p::event::core.QueueEvent(e);
|
||||
#endif
|
||||
}
|
||||
|
53
libi2pd/Event.h
Normal file
53
libi2pd/Event.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef EVENT_H__
|
||||
#define EVENT_H__
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
typedef std::map<std::string, std::string> EventType;
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace event
|
||||
{
|
||||
class EventListener {
|
||||
public:
|
||||
virtual ~EventListener() {};
|
||||
virtual void HandleEvent(const EventType & ev) = 0;
|
||||
/** @brief handle collected event when pumped */
|
||||
virtual void HandlePumpEvent(const EventType & ev, const uint64_t & val) = 0;
|
||||
};
|
||||
|
||||
class EventCore
|
||||
{
|
||||
public:
|
||||
void QueueEvent(const EventType & ev);
|
||||
void CollectEvent(const std::string & type, const std::string & ident, uint64_t val);
|
||||
void SetListener(EventListener * l);
|
||||
void PumpCollected(EventListener * l);
|
||||
|
||||
private:
|
||||
std::mutex m_collect_mutex;
|
||||
struct CollectedEvent
|
||||
{
|
||||
std::string Key;
|
||||
std::string Ident;
|
||||
uint64_t Val;
|
||||
};
|
||||
std::map<std::string, CollectedEvent> m_collected;
|
||||
EventListener * m_listener = nullptr;
|
||||
};
|
||||
#ifdef WITH_EVENTS
|
||||
extern EventCore core;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void QueueIntEvent(const std::string & type, const std::string & ident, uint64_t val);
|
||||
void EmitEvent(const EventType & ev);
|
||||
|
||||
#endif
|
206
libi2pd/FS.cpp
Normal file
206
libi2pd/FS.cpp
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016, 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 <algorithm>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h>
|
||||
#endif
|
||||
|
||||
#include "Base.h"
|
||||
#include "FS.h"
|
||||
#include "Log.h"
|
||||
#include "Garlic.h"
|
||||
|
||||
namespace i2p {
|
||||
namespace fs {
|
||||
std::string appName = "i2pd";
|
||||
std::string dataDir = "";
|
||||
#ifdef _WIN32
|
||||
std::string dirSep = "\\";
|
||||
#else
|
||||
std::string dirSep = "/";
|
||||
#endif
|
||||
|
||||
const std::string & GetAppName () {
|
||||
return appName;
|
||||
}
|
||||
|
||||
void SetAppName (const std::string& name) {
|
||||
appName = name;
|
||||
}
|
||||
|
||||
const std::string & GetDataDir () {
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
void DetectDataDir(const std::string & cmdline_param, bool isService) {
|
||||
if (cmdline_param != "") {
|
||||
dataDir = cmdline_param;
|
||||
return;
|
||||
}
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
char localAppData[MAX_PATH];
|
||||
// check executable directory first
|
||||
GetModuleFileName (NULL, localAppData, MAX_PATH);
|
||||
auto execPath = boost::filesystem::path(localAppData).parent_path();
|
||||
// if config file exists in .exe's folder use it
|
||||
if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string
|
||||
dataDir = execPath.string ();
|
||||
else
|
||||
{
|
||||
// otherwise %appdata%
|
||||
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
|
||||
dataDir = std::string(localAppData) + "\\" + appName;
|
||||
}
|
||||
return;
|
||||
#elif defined(MAC_OSX)
|
||||
char *home = getenv("HOME");
|
||||
dataDir = (home != NULL && strlen(home) > 0) ? home : "";
|
||||
dataDir += "/Library/Application Support/" + appName;
|
||||
return;
|
||||
#else /* other unix */
|
||||
#if defined(ANDROID)
|
||||
const char * ext = getenv("EXTERNAL_STORAGE");
|
||||
if (!ext) ext = "/sdcard";
|
||||
if (boost::filesystem::exists(ext))
|
||||
{
|
||||
dataDir = std::string (ext) + "/" + appName;
|
||||
return;
|
||||
}
|
||||
// otherwise use /data/files
|
||||
#endif
|
||||
char *home = getenv("HOME");
|
||||
if (isService) {
|
||||
dataDir = "/var/lib/" + appName;
|
||||
} else if (home != NULL && strlen(home) > 0) {
|
||||
dataDir = std::string(home) + "/." + appName;
|
||||
} else {
|
||||
dataDir = "/tmp/" + appName;
|
||||
}
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Init() {
|
||||
if (!boost::filesystem::exists(dataDir))
|
||||
boost::filesystem::create_directory(dataDir);
|
||||
std::string destinations = DataDirPath("destinations");
|
||||
if (!boost::filesystem::exists(destinations))
|
||||
boost::filesystem::create_directory(destinations);
|
||||
std::string tags = DataDirPath("tags");
|
||||
if (!boost::filesystem::exists(tags))
|
||||
boost::filesystem::create_directory(tags);
|
||||
else
|
||||
i2p::garlic::CleanUpTagsFiles ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadDir(const std::string & path, std::vector<std::string> & files) {
|
||||
if (!boost::filesystem::exists(path))
|
||||
return false;
|
||||
boost::filesystem::directory_iterator it(path);
|
||||
boost::filesystem::directory_iterator end;
|
||||
|
||||
for ( ; it != end; it++) {
|
||||
if (!boost::filesystem::is_regular_file(it->status()))
|
||||
continue;
|
||||
files.push_back(it->path().string());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Exists(const std::string & path) {
|
||||
return boost::filesystem::exists(path);
|
||||
}
|
||||
|
||||
uint32_t GetLastUpdateTime (const std::string & path)
|
||||
{
|
||||
if (!boost::filesystem::exists(path)) return 0;
|
||||
boost::system::error_code ec;
|
||||
auto t = boost::filesystem::last_write_time (path, ec);
|
||||
return ec ? 0 : t;
|
||||
}
|
||||
|
||||
bool Remove(const std::string & path) {
|
||||
if (!boost::filesystem::exists(path))
|
||||
return false;
|
||||
return boost::filesystem::remove(path);
|
||||
}
|
||||
|
||||
bool CreateDirectory (const std::string& path)
|
||||
{
|
||||
if (boost::filesystem::exists(path) &&
|
||||
boost::filesystem::is_directory (boost::filesystem::status (path))) return true;
|
||||
return boost::filesystem::create_directory(path);
|
||||
}
|
||||
|
||||
void HashedStorage::SetPlace(const std::string &path) {
|
||||
root = path + i2p::fs::dirSep + name;
|
||||
}
|
||||
|
||||
bool HashedStorage::Init(const char * chars, size_t count) {
|
||||
if (!boost::filesystem::exists(root)) {
|
||||
boost::filesystem::create_directories(root);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
|
||||
if (boost::filesystem::exists(p))
|
||||
continue;
|
||||
if (boost::filesystem::create_directory(p))
|
||||
continue; /* ^ throws exception on failure */
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HashedStorage::Path(const std::string & ident) const {
|
||||
std::string safe_ident = ident;
|
||||
std::replace(safe_ident.begin(), safe_ident.end(), '/', '-');
|
||||
std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-');
|
||||
|
||||
std::stringstream t("");
|
||||
t << this->root << i2p::fs::dirSep;
|
||||
t << prefix1 << safe_ident[0] << i2p::fs::dirSep;
|
||||
t << prefix2 << safe_ident << "." << suffix;
|
||||
|
||||
return t.str();
|
||||
}
|
||||
|
||||
void HashedStorage::Remove(const std::string & ident) {
|
||||
std::string path = Path(ident);
|
||||
if (!boost::filesystem::exists(path))
|
||||
return;
|
||||
boost::filesystem::remove(path);
|
||||
}
|
||||
|
||||
void HashedStorage::Traverse(std::vector<std::string> & files) {
|
||||
Iterate([&files] (const std::string & fname) {
|
||||
files.push_back(fname);
|
||||
});
|
||||
}
|
||||
|
||||
void HashedStorage::Iterate(FilenameVisitor v)
|
||||
{
|
||||
boost::filesystem::path p(root);
|
||||
boost::filesystem::recursive_directory_iterator it(p);
|
||||
boost::filesystem::recursive_directory_iterator end;
|
||||
|
||||
for ( ; it != end; it++) {
|
||||
if (!boost::filesystem::is_regular_file( it->status() ))
|
||||
continue;
|
||||
const std::string & t = it->path().string();
|
||||
v(t);
|
||||
}
|
||||
}
|
||||
} // fs
|
||||
} // i2p
|
161
libi2pd/FS.h
Normal file
161
libi2pd/FS.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016, 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 FS_H__
|
||||
#define FS_H__
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
|
||||
namespace i2p {
|
||||
namespace fs {
|
||||
extern std::string dirSep;
|
||||
|
||||
/**
|
||||
* @brief Class to work with NetDb & Router profiles
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
|
||||
* auto h = HashedStorage("name", "y", "z-", ".txt");
|
||||
* h.SetPlace("/tmp/hs-test");
|
||||
* h.GetName() -> gives "name"
|
||||
* h.GetRoot() -> gives "/tmp/hs-test/name"
|
||||
* h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet
|
||||
* h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt
|
||||
* h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists
|
||||
* std::vector<std::string> files;
|
||||
* h.Traverse(files); <- finds all files in storage and saves in given vector
|
||||
*/
|
||||
class HashedStorage {
|
||||
protected:
|
||||
std::string root; /**< path to storage with it's name included */
|
||||
std::string name; /**< name of the storage */
|
||||
std::string prefix1; /**< hashed directory prefix */
|
||||
std::string prefix2; /**< prefix of file in storage */
|
||||
std::string suffix; /**< suffix of file in storage (extension) */
|
||||
|
||||
public:
|
||||
typedef std::function<void(const std::string &)> FilenameVisitor;
|
||||
HashedStorage(const char *n, const char *p1, const char *p2, const char *s):
|
||||
name(n), prefix1(p1), prefix2(p2), suffix(s) {};
|
||||
|
||||
/** create subdirs in storage */
|
||||
bool Init(const char* chars, size_t cnt);
|
||||
const std::string & GetRoot() const { return root; }
|
||||
const std::string & GetName() const { return name; }
|
||||
/** set directory where to place storage directory */
|
||||
void SetPlace(const std::string & path);
|
||||
/** path to file with given ident */
|
||||
std::string Path(const std::string & ident) const;
|
||||
/** remove file by ident */
|
||||
void Remove(const std::string & ident);
|
||||
/** find all files in storage and store list in provided vector */
|
||||
void Traverse(std::vector<std::string> & files);
|
||||
/** visit every file in this storage with a visitor */
|
||||
void Iterate(FilenameVisitor v);
|
||||
};
|
||||
|
||||
/** @brief Returns current application name, default 'i2pd' */
|
||||
const std::string & GetAppName ();
|
||||
/** @brief Set applicaton name, affects autodetection of datadir */
|
||||
void SetAppName (const std::string& name);
|
||||
|
||||
/** @brief Returns datadir path */
|
||||
const std::string & GetDataDir();
|
||||
|
||||
/**
|
||||
* @brief Set datadir either from cmdline option or using autodetection
|
||||
* @param cmdline_param Value of cmdline parameter --datadir=<something>
|
||||
* @param isService Value of cmdline parameter --service
|
||||
*
|
||||
* Examples of autodetected paths:
|
||||
*
|
||||
* Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\
|
||||
* Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\
|
||||
* Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/
|
||||
* Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/
|
||||
*/
|
||||
void DetectDataDir(const std::string & cmdline_datadir, bool isService = false);
|
||||
|
||||
/**
|
||||
* @brief Create subdirectories inside datadir
|
||||
*/
|
||||
bool Init();
|
||||
|
||||
/**
|
||||
* @brief Get list of files in directory
|
||||
* @param path Path to directory
|
||||
* @param files Vector to store found files
|
||||
* @return true on success and false if directory not exists
|
||||
*/
|
||||
bool ReadDir(const std::string & path, std::vector<std::string> & files);
|
||||
|
||||
/**
|
||||
* @brief Remove file with given path
|
||||
* @param path Absolute path to file
|
||||
* @return true on success, false if file not exists, throws exception on error
|
||||
*/
|
||||
bool Remove(const std::string & path);
|
||||
|
||||
/**
|
||||
* @brief Check existence of file
|
||||
* @param path Absolute path to file
|
||||
* @return true if file exists, false otherwise
|
||||
*/
|
||||
bool Exists(const std::string & path);
|
||||
|
||||
uint32_t GetLastUpdateTime (const std::string & path); // seconds since epoch
|
||||
|
||||
bool CreateDirectory (const std::string& path);
|
||||
|
||||
template<typename T>
|
||||
void _ExpandPath(std::stringstream & path, T c) {
|
||||
path << i2p::fs::dirSep << c;
|
||||
}
|
||||
|
||||
template<typename T, typename ... Other>
|
||||
void _ExpandPath(std::stringstream & path, T c, Other ... other) {
|
||||
_ExpandPath(path, c);
|
||||
_ExpandPath(path, other ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get path relative to datadir
|
||||
*
|
||||
* Examples (with datadir = "/tmp/i2pd"):
|
||||
*
|
||||
* i2p::fs::Path("test") -> '/tmp/i2pd/test'
|
||||
* i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt'
|
||||
*/
|
||||
template<typename ... Other>
|
||||
std::string DataDirPath(Other ... components) {
|
||||
std::stringstream s("");
|
||||
s << i2p::fs::GetDataDir();
|
||||
_ExpandPath(s, components ...);
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
template<typename Storage, typename... Filename>
|
||||
std::string StorageRootPath (const Storage& storage, Filename... filenames)
|
||||
{
|
||||
std::stringstream s("");
|
||||
s << storage.GetRoot ();
|
||||
_ExpandPath(s, filenames...);
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
} // fs
|
||||
} // i2p
|
||||
|
||||
#endif // /* FS_H__ */
|
181
libi2pd/Family.cpp
Normal file
181
libi2pd/Family.cpp
Normal file
|
@ -0,0 +1,181 @@
|
|||
#include <string.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include "Crypto.h"
|
||||
#include "FS.h"
|
||||
#include "Log.h"
|
||||
#include "Family.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
Families::Families ()
|
||||
{
|
||||
}
|
||||
|
||||
Families::~Families ()
|
||||
{
|
||||
}
|
||||
|
||||
void Families::LoadCertificate (const std::string& filename)
|
||||
{
|
||||
SSL_CTX * ctx = SSL_CTX_new (TLS_method ());
|
||||
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
|
||||
if (ret)
|
||||
{
|
||||
SSL * ssl = SSL_new (ctx);
|
||||
X509 * cert = SSL_get_certificate (ssl);
|
||||
if (cert)
|
||||
{
|
||||
std::shared_ptr<i2p::crypto::Verifier> verifier;
|
||||
// extract issuer name
|
||||
char name[100];
|
||||
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
|
||||
char * cn = strstr (name, "CN=");
|
||||
if (cn)
|
||||
{
|
||||
cn += 3;
|
||||
char * family = strstr (cn, ".family");
|
||||
if (family) family[0] = 0;
|
||||
}
|
||||
auto pkey = X509_get_pubkey (cert);
|
||||
int keyType = EVP_PKEY_base_id (pkey);
|
||||
switch (keyType)
|
||||
{
|
||||
case EVP_PKEY_DSA:
|
||||
// TODO:
|
||||
break;
|
||||
case EVP_PKEY_EC:
|
||||
{
|
||||
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
|
||||
if (ecKey)
|
||||
{
|
||||
auto group = EC_KEY_get0_group (ecKey);
|
||||
if (group)
|
||||
{
|
||||
int curve = EC_GROUP_get_curve_name (group);
|
||||
if (curve == NID_X9_62_prime256v1)
|
||||
{
|
||||
uint8_t signingKey[64];
|
||||
BIGNUM * x = BN_new(), * y = BN_new();
|
||||
EC_POINT_get_affine_coordinates_GFp (group,
|
||||
EC_KEY_get0_public_key (ecKey), x, y, NULL);
|
||||
i2p::crypto::bn2buf (x, signingKey, 32);
|
||||
i2p::crypto::bn2buf (y, signingKey + 32, 32);
|
||||
BN_free (x); BN_free (y);
|
||||
verifier = std::make_shared<i2p::crypto::ECDSAP256Verifier>(signingKey);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
|
||||
}
|
||||
EC_KEY_free (ecKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported");
|
||||
}
|
||||
EVP_PKEY_free (pkey);
|
||||
if (verifier && cn)
|
||||
m_SigningKeys[cn] = verifier;
|
||||
}
|
||||
SSL_free (ssl);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Family: Can't open certificate file ", filename);
|
||||
SSL_CTX_free (ctx);
|
||||
}
|
||||
|
||||
void Families::LoadCertificates ()
|
||||
{
|
||||
std::string certDir = i2p::fs::DataDirPath("certificates", "family");
|
||||
std::vector<std::string> files;
|
||||
int numCertificates = 0;
|
||||
|
||||
if (!i2p::fs::ReadDir(certDir, files)) {
|
||||
LogPrint(eLogWarning, "Family: Can't load family certificates from ", certDir);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const std::string & file : files) {
|
||||
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
|
||||
LogPrint(eLogWarning, "Family: ignoring file ", file);
|
||||
continue;
|
||||
}
|
||||
LoadCertificate (file);
|
||||
numCertificates++;
|
||||
}
|
||||
LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded");
|
||||
}
|
||||
|
||||
bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
|
||||
const char * signature, const char * key)
|
||||
{
|
||||
uint8_t buf[50], signatureBuf[64];
|
||||
size_t len = family.length (), signatureLen = strlen (signature);
|
||||
if (len + 32 > 50)
|
||||
{
|
||||
LogPrint (eLogError, "Family: ", family, " is too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy (buf, family.c_str (), len);
|
||||
memcpy (buf + len, (const uint8_t *)ident, 32);
|
||||
len += 32;
|
||||
Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
|
||||
auto it = m_SigningKeys.find (family);
|
||||
if (it != m_SigningKeys.end ())
|
||||
return it->second->Verify (buf, len, signatureBuf);
|
||||
// TODO: process key
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident)
|
||||
{
|
||||
auto filename = i2p::fs::DataDirPath("family", (family + ".key"));
|
||||
std::string sig;
|
||||
SSL_CTX * ctx = SSL_CTX_new (TLS_method ());
|
||||
int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
|
||||
if (ret)
|
||||
{
|
||||
SSL * ssl = SSL_new (ctx);
|
||||
EVP_PKEY * pkey = SSL_get_privatekey (ssl);
|
||||
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
|
||||
if (ecKey)
|
||||
{
|
||||
auto group = EC_KEY_get0_group (ecKey);
|
||||
if (group)
|
||||
{
|
||||
int curve = EC_GROUP_get_curve_name (group);
|
||||
if (curve == NID_X9_62_prime256v1)
|
||||
{
|
||||
uint8_t signingPrivateKey[32], buf[50], signature[64];
|
||||
i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32);
|
||||
i2p::crypto::ECDSAP256Signer signer (signingPrivateKey);
|
||||
size_t len = family.length ();
|
||||
memcpy (buf, family.c_str (), len);
|
||||
memcpy (buf + len, (const uint8_t *)ident, 32);
|
||||
len += 32;
|
||||
signer.Sign (buf, len, signature);
|
||||
len = Base64EncodingBufferSize (64);
|
||||
char * b64 = new char[len+1];
|
||||
len = ByteStreamToBase64 (signature, 64, b64, len);
|
||||
b64[len] = 0;
|
||||
sig = b64;
|
||||
delete[] b64;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
|
||||
}
|
||||
}
|
||||
SSL_free (ssl);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Family: Can't open keys file: ", filename);
|
||||
SSL_CTX_free (ctx);
|
||||
return sig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
libi2pd/Family.h
Normal file
38
libi2pd/Family.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef FAMILY_H__
|
||||
#define FAMILY_H__
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "Signature.h"
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
class Families
|
||||
{
|
||||
public:
|
||||
|
||||
Families ();
|
||||
~Families ();
|
||||
void LoadCertificates ();
|
||||
bool VerifyFamily (const std::string& family, const IdentHash& ident,
|
||||
const char * signature, const char * key = nullptr);
|
||||
|
||||
private:
|
||||
|
||||
void LoadCertificate (const std::string& filename);
|
||||
|
||||
private:
|
||||
|
||||
std::map<std::string, std::shared_ptr<i2p::crypto::Verifier> > m_SigningKeys;
|
||||
};
|
||||
|
||||
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident);
|
||||
// return base64 signature of empty string in case of failure
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
787
libi2pd/Garlic.cpp
Normal file
787
libi2pd/Garlic.cpp
Normal file
|
@ -0,0 +1,787 @@
|
|||
#include <inttypes.h>
|
||||
#include "I2PEndian.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "Crypto.h"
|
||||
#include "RouterContext.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Tunnel.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "Transports.h"
|
||||
#include "Timestamp.h"
|
||||
#include "Log.h"
|
||||
#include "FS.h"
|
||||
#include "Garlic.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace garlic
|
||||
{
|
||||
GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner,
|
||||
std::shared_ptr<const i2p::data::RoutingDestination> destination, int numTags, bool attachLeaseSet):
|
||||
m_Owner (owner), m_Destination (destination), m_NumTags (numTags),
|
||||
m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend),
|
||||
m_LeaseSetUpdateMsgID (0)
|
||||
{
|
||||
// create new session tags and session key
|
||||
RAND_bytes (m_SessionKey, 32);
|
||||
m_Encryption.SetKey (m_SessionKey);
|
||||
}
|
||||
|
||||
GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag):
|
||||
m_Owner (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend), m_LeaseSetUpdateMsgID (0)
|
||||
{
|
||||
memcpy (m_SessionKey, sessionKey, 32);
|
||||
m_Encryption.SetKey (m_SessionKey);
|
||||
m_SessionTags.push_back (sessionTag);
|
||||
m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
}
|
||||
|
||||
GarlicRoutingSession::~GarlicRoutingSession ()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<GarlicRoutingPath> GarlicRoutingSession::GetSharedRoutingPath ()
|
||||
{
|
||||
if (!m_SharedRoutingPath) return nullptr;
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED ||
|
||||
!m_SharedRoutingPath->outboundTunnel->IsEstablished () ||
|
||||
ts*1000LL > m_SharedRoutingPath->remoteLease->endDate ||
|
||||
ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT)
|
||||
m_SharedRoutingPath = nullptr;
|
||||
if (m_SharedRoutingPath) m_SharedRoutingPath->numTimesUsed++;
|
||||
return m_SharedRoutingPath;
|
||||
}
|
||||
|
||||
void GarlicRoutingSession::SetSharedRoutingPath (std::shared_ptr<GarlicRoutingPath> path)
|
||||
{
|
||||
if (path && path->outboundTunnel && path->remoteLease)
|
||||
{
|
||||
path->updateTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
path->numTimesUsed = 0;
|
||||
}
|
||||
else
|
||||
path = nullptr;
|
||||
m_SharedRoutingPath = path;
|
||||
}
|
||||
|
||||
GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags ()
|
||||
{
|
||||
auto tags = new UnconfirmedTags (m_NumTags);
|
||||
tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (int i = 0; i < m_NumTags; i++)
|
||||
{
|
||||
RAND_bytes (tags->sessionTags[i], 32);
|
||||
tags->sessionTags[i].creationTime = tags->tagsCreationTime;
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
void GarlicRoutingSession::MessageConfirmed (uint32_t msgID)
|
||||
{
|
||||
TagsConfirmed (msgID);
|
||||
if (msgID == m_LeaseSetUpdateMsgID)
|
||||
{
|
||||
m_LeaseSetUpdateStatus = eLeaseSetUpToDate;
|
||||
m_LeaseSetUpdateMsgID = 0;
|
||||
LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed");
|
||||
}
|
||||
else
|
||||
CleanupExpiredTags ();
|
||||
}
|
||||
|
||||
void GarlicRoutingSession::TagsConfirmed (uint32_t msgID)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
auto it = m_UnconfirmedTagsMsgs.find (msgID);
|
||||
if (it != m_UnconfirmedTagsMsgs.end ())
|
||||
{
|
||||
auto& tags = it->second;
|
||||
if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
for (int i = 0; i < tags->numTags; i++)
|
||||
m_SessionTags.push_back (tags->sessionTags[i]);
|
||||
}
|
||||
m_UnconfirmedTagsMsgs.erase (it);
|
||||
}
|
||||
}
|
||||
|
||||
bool GarlicRoutingSession::CleanupExpiredTags ()
|
||||
{
|
||||
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();)
|
||||
{
|
||||
if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
|
||||
it = m_SessionTags.erase (it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
CleanupUnconfirmedTags ();
|
||||
if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT)
|
||||
{
|
||||
if (m_Owner)
|
||||
m_Owner->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID);
|
||||
m_LeaseSetUpdateMsgID = 0;
|
||||
}
|
||||
return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty ();
|
||||
}
|
||||
|
||||
bool GarlicRoutingSession::CleanupUnconfirmedTags ()
|
||||
{
|
||||
bool ret = false;
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
// delete expired unconfirmed tags
|
||||
for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();)
|
||||
{
|
||||
if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT)
|
||||
{
|
||||
if (m_Owner)
|
||||
m_Owner->RemoveDeliveryStatusSession (it->first);
|
||||
it = m_UnconfirmedTagsMsgs.erase (it);
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> GarlicRoutingSession::WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg)
|
||||
{
|
||||
auto m = NewI2NPMessage ();
|
||||
m->Align (12); // in order to get buf aligned to 16 (12 + 4)
|
||||
size_t len = 0;
|
||||
uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length
|
||||
|
||||
// find non-expired tag
|
||||
bool tagFound = false;
|
||||
SessionTag tag;
|
||||
if (m_NumTags > 0)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
while (!m_SessionTags.empty ())
|
||||
{
|
||||
if (ts < m_SessionTags.front ().creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
tag = m_SessionTags.front ();
|
||||
m_SessionTags.pop_front (); // use same tag only once
|
||||
tagFound = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
m_SessionTags.pop_front (); // remove expired tag
|
||||
}
|
||||
}
|
||||
// create message
|
||||
if (!tagFound) // new session
|
||||
{
|
||||
LogPrint (eLogInfo, "Garlic: No tags available, will use ElGamal");
|
||||
if (!m_Destination)
|
||||
{
|
||||
LogPrint (eLogError, "Garlic: Can't use ElGamal for unknown destination");
|
||||
return nullptr;
|
||||
}
|
||||
// create ElGamal block
|
||||
ElGamalBlock elGamal;
|
||||
memcpy (elGamal.sessionKey, m_SessionKey, 32);
|
||||
RAND_bytes (elGamal.preIV, 32); // Pre-IV
|
||||
uint8_t iv[32]; // IV is first 16 bytes
|
||||
SHA256(elGamal.preIV, 32, iv);
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
i2p::crypto::ElGamalEncrypt (m_Destination->GetEncryptionPublicKey (), (uint8_t *)&elGamal, buf, ctx, true);
|
||||
BN_CTX_free (ctx);
|
||||
m_Encryption.SetIV (iv);
|
||||
buf += 514;
|
||||
len += 514;
|
||||
}
|
||||
else // existing session
|
||||
{
|
||||
// session tag
|
||||
memcpy (buf, tag, 32);
|
||||
uint8_t iv[32]; // IV is first 16 bytes
|
||||
SHA256(tag, 32, iv);
|
||||
m_Encryption.SetIV (iv);
|
||||
buf += 32;
|
||||
len += 32;
|
||||
}
|
||||
// AES block
|
||||
len += CreateAESBlock (buf, msg);
|
||||
htobe32buf (m->GetPayload (), len);
|
||||
m->len += len + 4;
|
||||
m->FillI2NPMessageHeader (eI2NPGarlic);
|
||||
return m;
|
||||
}
|
||||
|
||||
size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg)
|
||||
{
|
||||
size_t blockSize = 0;
|
||||
bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3);
|
||||
UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr;
|
||||
htobuf16 (buf, newTags ? htobe16 (newTags->numTags) : 0); // tag count
|
||||
blockSize += 2;
|
||||
if (newTags) // session tags recreated
|
||||
{
|
||||
for (int i = 0; i < newTags->numTags; i++)
|
||||
{
|
||||
memcpy (buf + blockSize, newTags->sessionTags[i], 32); // tags
|
||||
blockSize += 32;
|
||||
}
|
||||
}
|
||||
uint32_t * payloadSize = (uint32_t *)(buf + blockSize);
|
||||
blockSize += 4;
|
||||
uint8_t * payloadHash = buf + blockSize;
|
||||
blockSize += 32;
|
||||
buf[blockSize] = 0; // flag
|
||||
blockSize++;
|
||||
size_t len = CreateGarlicPayload (buf + blockSize, msg, newTags);
|
||||
htobe32buf (payloadSize, len);
|
||||
SHA256(buf + blockSize, len, payloadHash);
|
||||
blockSize += len;
|
||||
size_t rem = blockSize % 16;
|
||||
if (rem)
|
||||
blockSize += (16-rem); //padding
|
||||
m_Encryption.Encrypt(buf, blockSize, buf);
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags)
|
||||
{
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
uint32_t msgID;
|
||||
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||
size_t size = 0;
|
||||
uint8_t * numCloves = payload + size;
|
||||
*numCloves = 0;
|
||||
size++;
|
||||
|
||||
if (m_Owner)
|
||||
{
|
||||
// resubmit non-confirmed LeaseSet
|
||||
if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted && ts > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT)
|
||||
{
|
||||
m_LeaseSetUpdateStatus = eLeaseSetUpdated;
|
||||
SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed
|
||||
}
|
||||
|
||||
// attach DeviveryStatus if necessary
|
||||
if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated
|
||||
{
|
||||
// clove is DeliveryStatus
|
||||
auto cloveSize = CreateDeliveryStatusClove (payload + size, msgID);
|
||||
if (cloveSize > 0) // successive?
|
||||
{
|
||||
size += cloveSize;
|
||||
(*numCloves)++;
|
||||
if (newTags) // new tags created
|
||||
{
|
||||
newTags->msgID = msgID;
|
||||
m_UnconfirmedTagsMsgs.insert (std::make_pair(msgID, std::unique_ptr<UnconfirmedTags>(newTags)));
|
||||
newTags = nullptr; // got acquired
|
||||
}
|
||||
m_Owner->DeliveryStatusSent (shared_from_this (), msgID);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Garlic: DeliveryStatus clove was not created");
|
||||
}
|
||||
// attach LeaseSet
|
||||
if (m_LeaseSetUpdateStatus == eLeaseSetUpdated)
|
||||
{
|
||||
if (m_LeaseSetUpdateMsgID) m_Owner->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); // remove previous
|
||||
m_LeaseSetUpdateStatus = eLeaseSetSubmitted;
|
||||
m_LeaseSetUpdateMsgID = msgID;
|
||||
m_LeaseSetSubmissionTime = ts;
|
||||
// clove if our leaseSet must be attached
|
||||
auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ());
|
||||
size += CreateGarlicClove (payload + size, leaseSet, false);
|
||||
(*numCloves)++;
|
||||
}
|
||||
}
|
||||
if (msg) // clove message ifself if presented
|
||||
{
|
||||
size += CreateGarlicClove (payload + size, msg, m_Destination ? m_Destination->IsDestination () : false);
|
||||
(*numCloves)++;
|
||||
}
|
||||
memset (payload + size, 0, 3); // certificate of message
|
||||
size += 3;
|
||||
htobe32buf (payload + size, msgID); // MessageID
|
||||
size += 4;
|
||||
htobe64buf (payload + size, ts + 8000); // Expiration of message, 8 sec
|
||||
size += 8;
|
||||
|
||||
if (newTags) delete newTags; // not acquired, delete
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination)
|
||||
{
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
|
||||
size_t size = 0;
|
||||
if (isDestination)
|
||||
{
|
||||
buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination
|
||||
size++;
|
||||
memcpy (buf + size, m_Destination->GetIdentHash (), 32);
|
||||
size += 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[size] = 0;// delivery instructions flag local
|
||||
size++;
|
||||
}
|
||||
|
||||
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
|
||||
size += msg->GetLength ();
|
||||
uint32_t cloveID;
|
||||
RAND_bytes ((uint8_t *)&cloveID, 4);
|
||||
htobe32buf (buf + size, cloveID); // CloveID
|
||||
size += 4;
|
||||
htobe64buf (buf + size, ts); // Expiration of clove
|
||||
size += 8;
|
||||
memset (buf + size, 0, 3); // certificate of clove
|
||||
size += 3;
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t GarlicRoutingSession::CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID)
|
||||
{
|
||||
size_t size = 0;
|
||||
if (m_Owner)
|
||||
{
|
||||
auto inboundTunnel = m_Owner->GetTunnelPool ()->GetNextInboundTunnel ();
|
||||
if (inboundTunnel)
|
||||
{
|
||||
buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel
|
||||
size++;
|
||||
// hash and tunnelID sequence is reversed for Garlic
|
||||
memcpy (buf + size, inboundTunnel->GetNextIdentHash (), 32); // To Hash
|
||||
size += 32;
|
||||
htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID
|
||||
size += 4;
|
||||
// create msg
|
||||
auto msg = CreateDeliveryStatusMsg (msgID);
|
||||
if (m_Owner)
|
||||
{
|
||||
//encrypt
|
||||
uint8_t key[32], tag[32];
|
||||
RAND_bytes (key, 32); // random session key
|
||||
RAND_bytes (tag, 32); // random session tag
|
||||
m_Owner->SubmitSessionKey (key, tag);
|
||||
GarlicRoutingSession garlic (key, tag);
|
||||
msg = garlic.WrapSingleMessage (msg);
|
||||
}
|
||||
memcpy (buf + size, msg->GetBuffer (), msg->GetLength ());
|
||||
size += msg->GetLength ();
|
||||
// fill clove
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec
|
||||
uint32_t cloveID;
|
||||
RAND_bytes ((uint8_t *)&cloveID, 4);
|
||||
htobe32buf (buf + size, cloveID); // CloveID
|
||||
size += 4;
|
||||
htobe64buf (buf + size, ts); // Expiration of clove
|
||||
size += 8;
|
||||
memset (buf + size, 0, 3); // certificate of clove
|
||||
size += 3;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Garlic: No inbound tunnels in the pool for DeliveryStatus");
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Garlic: Missing local LeaseSet");
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
GarlicDestination::GarlicDestination (): m_NumTags (32) // 32 tags by default
|
||||
{
|
||||
m_Ctx = BN_CTX_new ();
|
||||
}
|
||||
|
||||
GarlicDestination::~GarlicDestination ()
|
||||
{
|
||||
BN_CTX_free (m_Ctx);
|
||||
}
|
||||
|
||||
void GarlicDestination::CleanUp ()
|
||||
{
|
||||
m_Sessions.clear ();
|
||||
m_DeliveryStatusSessions.clear ();
|
||||
m_Tags.clear ();
|
||||
}
|
||||
void GarlicDestination::AddSessionKey (const uint8_t * key, const uint8_t * tag)
|
||||
{
|
||||
if (key)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
m_Tags[SessionTag(tag, ts)] = std::make_shared<AESDecryption>(key);
|
||||
}
|
||||
}
|
||||
|
||||
bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
|
||||
{
|
||||
AddSessionKey (key, tag);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
uint8_t * buf = msg->GetPayload ();
|
||||
uint32_t length = bufbe32toh (buf);
|
||||
if (length > msg->GetLength ())
|
||||
{
|
||||
LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds I2NP message length ", msg->GetLength ());
|
||||
return;
|
||||
}
|
||||
buf += 4; // length
|
||||
auto it = m_Tags.find (SessionTag(buf));
|
||||
if (it != m_Tags.end ())
|
||||
{
|
||||
// tag found. Use AES
|
||||
auto decryption = it->second;
|
||||
m_Tags.erase (it); // tag might be used only once
|
||||
if (length >= 32)
|
||||
{
|
||||
uint8_t iv[32]; // IV is first 16 bytes
|
||||
SHA256(buf, 32, iv);
|
||||
decryption->SetIV (iv);
|
||||
decryption->Decrypt (buf + 32, length - 32, buf + 32);
|
||||
HandleAESBlock (buf + 32, length - 32, decryption, msg->from);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes");
|
||||
}
|
||||
else
|
||||
{
|
||||
// tag not found. Use ElGamal
|
||||
ElGamalBlock elGamal;
|
||||
if (length >= 514 && i2p::crypto::ElGamalDecrypt (GetEncryptionPrivateKey (), buf, (uint8_t *)&elGamal, m_Ctx, true))
|
||||
{
|
||||
auto decryption = std::make_shared<AESDecryption>(elGamal.sessionKey);
|
||||
uint8_t iv[32]; // IV is first 16 bytes
|
||||
SHA256(elGamal.preIV, 32, iv);
|
||||
decryption->SetIV (iv);
|
||||
decryption->Decrypt(buf + 514, length - 514, buf + 514);
|
||||
HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Garlic: Failed to decrypt message");
|
||||
}
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption,
|
||||
std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||
{
|
||||
uint16_t tagCount = bufbe16toh (buf);
|
||||
buf += 2; len -= 2;
|
||||
if (tagCount > 0)
|
||||
{
|
||||
if (tagCount*32 > len)
|
||||
{
|
||||
LogPrint (eLogError, "Garlic: Tag count ", tagCount, " exceeds length ", len);
|
||||
return ;
|
||||
}
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (int i = 0; i < tagCount; i++)
|
||||
m_Tags[SessionTag(buf + i*32, ts)] = decryption;
|
||||
}
|
||||
buf += tagCount*32;
|
||||
len -= tagCount*32;
|
||||
uint32_t payloadSize = bufbe32toh (buf);
|
||||
if (payloadSize > len)
|
||||
{
|
||||
LogPrint (eLogError, "Garlic: Unexpected payload size ", payloadSize);
|
||||
return;
|
||||
}
|
||||
buf += 4;
|
||||
uint8_t * payloadHash = buf;
|
||||
buf += 32;// payload hash.
|
||||
if (*buf) // session key?
|
||||
buf += 32; // new session key
|
||||
buf++; // flag
|
||||
|
||||
// payload
|
||||
uint8_t digest[32];
|
||||
SHA256 (buf, payloadSize, digest);
|
||||
if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match
|
||||
{
|
||||
LogPrint (eLogError, "Garlic: wrong payload hash");
|
||||
return;
|
||||
}
|
||||
HandleGarlicPayload (buf, payloadSize, from);
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||
{
|
||||
const uint8_t * buf1 = buf;
|
||||
int numCloves = buf[0];
|
||||
LogPrint (eLogDebug, "Garlic: ", numCloves," cloves");
|
||||
buf++;
|
||||
for (int i = 0; i < numCloves; i++)
|
||||
{
|
||||
// delivery instructions
|
||||
uint8_t flag = buf[0];
|
||||
buf++; // flag
|
||||
if (flag & 0x80) // encrypted?
|
||||
{
|
||||
// TODO: implement
|
||||
LogPrint (eLogWarning, "Garlic: clove encrypted");
|
||||
buf += 32;
|
||||
}
|
||||
GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03);
|
||||
switch (deliveryType)
|
||||
{
|
||||
case eGarlicDeliveryTypeLocal:
|
||||
LogPrint (eLogDebug, "Garlic: type local");
|
||||
HandleI2NPMessage (buf, len, from);
|
||||
break;
|
||||
case eGarlicDeliveryTypeDestination:
|
||||
LogPrint (eLogDebug, "Garlic: type destination");
|
||||
buf += 32; // destination. check it later or for multiple destinations
|
||||
HandleI2NPMessage (buf, len, from);
|
||||
break;
|
||||
case eGarlicDeliveryTypeTunnel:
|
||||
{
|
||||
LogPrint (eLogDebug, "Garlic: type tunnel");
|
||||
// gwHash and gwTunnel sequence is reverted
|
||||
uint8_t * gwHash = buf;
|
||||
buf += 32;
|
||||
uint32_t gwTunnel = bufbe32toh (buf);
|
||||
buf += 4;
|
||||
auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from);
|
||||
if (from) // received through an inbound tunnel
|
||||
{
|
||||
std::shared_ptr<i2p::tunnel::OutboundTunnel> tunnel;
|
||||
if (from->GetTunnelPool ())
|
||||
tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel ();
|
||||
else
|
||||
LogPrint (eLogError, "Garlic: Tunnel pool is not set for inbound tunnel");
|
||||
if (tunnel) // we have send it through an outbound tunnel
|
||||
tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg);
|
||||
else
|
||||
LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove");
|
||||
}
|
||||
else // received directly
|
||||
i2p::transport::transports.SendMessage (gwHash, i2p::CreateTunnelGatewayMsg (gwTunnel, msg)); // send directly
|
||||
break;
|
||||
}
|
||||
case eGarlicDeliveryTypeRouter:
|
||||
{
|
||||
uint8_t * ident = buf;
|
||||
buf += 32;
|
||||
if (!from) // received directly
|
||||
i2p::transport::transports.SendMessage (ident,
|
||||
CreateI2NPMessage (buf, GetI2NPMessageLength (buf)));
|
||||
else
|
||||
LogPrint (eLogWarning, "Garlic: type router for inbound tunnels not supported");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType);
|
||||
}
|
||||
buf += GetI2NPMessageLength (buf); // I2NP
|
||||
buf += 4; // CloveID
|
||||
buf += 8; // Date
|
||||
buf += 3; // Certificate
|
||||
if (buf - buf1 > (int)len)
|
||||
{
|
||||
LogPrint (eLogError, "Garlic: clove is too long");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> GarlicDestination::WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
|
||||
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet)
|
||||
{
|
||||
auto session = GetRoutingSession (destination, attachLeaseSet);
|
||||
return session->WrapSingleMessage (msg);
|
||||
}
|
||||
|
||||
std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
|
||||
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet)
|
||||
{
|
||||
GarlicRoutingSessionPtr session;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
auto it = m_Sessions.find (destination->GetIdentHash ());
|
||||
if (it != m_Sessions.end ())
|
||||
session = it->second;
|
||||
}
|
||||
if (!session)
|
||||
{
|
||||
session = std::make_shared<GarlicRoutingSession> (this, destination,
|
||||
attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
m_Sessions[destination->GetIdentHash ()] = session;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
void GarlicDestination::CleanupExpiredTags ()
|
||||
{
|
||||
// incoming
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
int numExpiredTags = 0;
|
||||
for (auto it = m_Tags.begin (); it != m_Tags.end ();)
|
||||
{
|
||||
if (ts > it->first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
numExpiredTags++;
|
||||
it = m_Tags.erase (it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
if (numExpiredTags > 0)
|
||||
LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ());
|
||||
|
||||
// outgoing
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
for (auto it = m_Sessions.begin (); it != m_Sessions.end ();)
|
||||
{
|
||||
it->second->GetSharedRoutingPath (); // delete shared path if necessary
|
||||
if (!it->second->CleanupExpiredTags ())
|
||||
{
|
||||
LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted");
|
||||
it->second->SetOwner (nullptr);
|
||||
it = m_Sessions.erase (it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
// delivery status sessions
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_DeliveryStatusSessionsMutex);
|
||||
for (auto it = m_DeliveryStatusSessions.begin (); it != m_DeliveryStatusSessions.end (); )
|
||||
{
|
||||
if (it->second->GetOwner () != this)
|
||||
it = m_DeliveryStatusSessions.erase (it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_DeliveryStatusSessionsMutex);
|
||||
m_DeliveryStatusSessions.erase (msgID);
|
||||
}
|
||||
|
||||
void GarlicDestination::DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_DeliveryStatusSessionsMutex);
|
||||
m_DeliveryStatusSessions[msgID] = session;
|
||||
}
|
||||
|
||||
void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
uint32_t msgID = bufbe32toh (msg->GetPayload ());
|
||||
GarlicRoutingSessionPtr session;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_DeliveryStatusSessionsMutex);
|
||||
auto it = m_DeliveryStatusSessions.find (msgID);
|
||||
if (it != m_DeliveryStatusSessions.end ())
|
||||
{
|
||||
session = it->second;
|
||||
m_DeliveryStatusSessions.erase (it);
|
||||
}
|
||||
}
|
||||
if (session)
|
||||
{
|
||||
session->MessageConfirmed (msgID);
|
||||
LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged");
|
||||
}
|
||||
}
|
||||
|
||||
void GarlicDestination::SetLeaseSetUpdated ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SessionsMutex);
|
||||
for (auto& it: m_Sessions)
|
||||
it.second->SetLeaseSetUpdated ();
|
||||
}
|
||||
|
||||
void GarlicDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
HandleGarlicMessage (msg);
|
||||
}
|
||||
|
||||
void GarlicDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
HandleDeliveryStatusMessage (msg);
|
||||
}
|
||||
|
||||
void GarlicDestination::SaveTags ()
|
||||
{
|
||||
if (m_Tags.empty ()) return;
|
||||
std::string ident = GetIdentHash().ToBase32();
|
||||
std::string path = i2p::fs::DataDirPath("tags", (ident + ".tags"));
|
||||
std::ofstream f (path, std::ofstream::binary | std::ofstream::out | std::ofstream::trunc);
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
// 4 bytes timestamp, 32 bytes tag, 32 bytes key
|
||||
for (auto it: m_Tags)
|
||||
{
|
||||
if (ts < it.first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
f.write ((char *)&it.first.creationTime, 4);
|
||||
f.write ((char *)it.first.data (), 32);
|
||||
f.write ((char *)it.second->GetKey ().data (), 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GarlicDestination::LoadTags ()
|
||||
{
|
||||
std::string ident = GetIdentHash().ToBase32();
|
||||
std::string path = i2p::fs::DataDirPath("tags", (ident + ".tags"));
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (ts < i2p::fs::GetLastUpdateTime (path) + INCOMING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
// might contain non-expired tags
|
||||
std::ifstream f (path, std::ifstream::binary);
|
||||
if (f)
|
||||
{
|
||||
std::map<i2p::crypto::AESKey, std::shared_ptr<AESDecryption> > keys;
|
||||
// 4 bytes timestamp, 32 bytes tag, 32 bytes key
|
||||
while (!f.eof ())
|
||||
{
|
||||
uint32_t t;
|
||||
uint8_t tag[32], key[32];
|
||||
f.read ((char *)&t, 4); if (f.eof ()) break;
|
||||
if (ts < t + INCOMING_TAGS_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
f.read ((char *)tag, 32);
|
||||
f.read ((char *)key, 32);
|
||||
}
|
||||
else
|
||||
f.seekg (64, std::ios::cur); // skip
|
||||
if (f.eof ()) break;
|
||||
|
||||
std::shared_ptr<AESDecryption> decryption;
|
||||
auto it = keys.find (key);
|
||||
if (it != keys.end ())
|
||||
decryption = it->second;
|
||||
else
|
||||
decryption = std::make_shared<AESDecryption>(key);
|
||||
m_Tags.insert (std::make_pair (SessionTag (tag, ts), decryption));
|
||||
}
|
||||
if (!m_Tags.empty ())
|
||||
LogPrint (eLogInfo, m_Tags.size (), " loaded for ", ident);
|
||||
}
|
||||
}
|
||||
i2p::fs::Remove (path);
|
||||
}
|
||||
|
||||
void CleanUpTagsFiles ()
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
i2p::fs::ReadDir (i2p::fs::DataDirPath("tags"), files);
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it: files)
|
||||
if (ts >= i2p::fs::GetLastUpdateTime (it) + INCOMING_TAGS_EXPIRATION_TIMEOUT)
|
||||
i2p::fs::Remove (it);
|
||||
}
|
||||
}
|
||||
}
|
234
libi2pd/Garlic.h
Normal file
234
libi2pd/Garlic.h
Normal file
|
@ -0,0 +1,234 @@
|
|||
#ifndef GARLIC_H__
|
||||
#define GARLIC_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "Crypto.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "Queue.h"
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class OutboundTunnel;
|
||||
}
|
||||
|
||||
namespace garlic
|
||||
{
|
||||
|
||||
enum GarlicDeliveryType
|
||||
{
|
||||
eGarlicDeliveryTypeLocal = 0,
|
||||
eGarlicDeliveryTypeDestination = 1,
|
||||
eGarlicDeliveryTypeRouter = 2,
|
||||
eGarlicDeliveryTypeTunnel = 3
|
||||
};
|
||||
|
||||
struct ElGamalBlock
|
||||
{
|
||||
uint8_t sessionKey[32];
|
||||
uint8_t preIV[32];
|
||||
uint8_t padding[158];
|
||||
};
|
||||
|
||||
const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes
|
||||
const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes
|
||||
const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds
|
||||
const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
|
||||
const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds
|
||||
const int ROUTING_PATH_MAX_NUM_TIMES_USED = 100; // how many times might be used
|
||||
|
||||
struct SessionTag: public i2p::data::Tag<32>
|
||||
{
|
||||
SessionTag (const uint8_t * buf, uint32_t ts = 0): Tag<32>(buf), creationTime (ts) {};
|
||||
SessionTag () = default;
|
||||
SessionTag (const SessionTag& ) = default;
|
||||
SessionTag& operator= (const SessionTag& ) = default;
|
||||
#ifndef _WIN32
|
||||
SessionTag (SessionTag&& ) = default;
|
||||
SessionTag& operator= (SessionTag&& ) = default;
|
||||
#endif
|
||||
uint32_t creationTime; // seconds since epoch
|
||||
};
|
||||
|
||||
// AESDecryption is associated with session tags and store key
|
||||
class AESDecryption: public i2p::crypto::CBCDecryption
|
||||
{
|
||||
public:
|
||||
|
||||
AESDecryption (const uint8_t * key): m_Key (key)
|
||||
{
|
||||
SetKey (key);
|
||||
}
|
||||
const i2p::crypto::AESKey& GetKey () const { return m_Key; };
|
||||
|
||||
private:
|
||||
|
||||
i2p::crypto::AESKey m_Key;
|
||||
};
|
||||
|
||||
struct GarlicRoutingPath
|
||||
{
|
||||
std::shared_ptr<i2p::tunnel::OutboundTunnel> outboundTunnel;
|
||||
std::shared_ptr<const i2p::data::Lease> remoteLease;
|
||||
int rtt; // RTT
|
||||
uint32_t updateTime; // seconds since epoch
|
||||
int numTimesUsed;
|
||||
};
|
||||
|
||||
class GarlicDestination;
|
||||
class GarlicRoutingSession: public std::enable_shared_from_this<GarlicRoutingSession>
|
||||
{
|
||||
enum LeaseSetUpdateStatus
|
||||
{
|
||||
eLeaseSetUpToDate = 0,
|
||||
eLeaseSetUpdated,
|
||||
eLeaseSetSubmitted,
|
||||
eLeaseSetDoNotSend
|
||||
};
|
||||
|
||||
struct UnconfirmedTags
|
||||
{
|
||||
UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; };
|
||||
~UnconfirmedTags () { delete[] sessionTags; };
|
||||
uint32_t msgID;
|
||||
int numTags;
|
||||
SessionTag * sessionTags;
|
||||
uint32_t tagsCreationTime;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr<const i2p::data::RoutingDestination> destination,
|
||||
int numTags, bool attachLeaseSet);
|
||||
GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption
|
||||
~GarlicRoutingSession ();
|
||||
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg);
|
||||
void MessageConfirmed (uint32_t msgID);
|
||||
bool CleanupExpiredTags (); // returns true if something left
|
||||
bool CleanupUnconfirmedTags (); // returns true if something has been deleted
|
||||
|
||||
void SetLeaseSetUpdated ()
|
||||
{
|
||||
if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated;
|
||||
};
|
||||
bool IsLeaseSetNonConfirmed () const { return m_LeaseSetUpdateStatus == eLeaseSetSubmitted; };
|
||||
bool IsLeaseSetUpdated () const { return m_LeaseSetUpdateStatus == eLeaseSetUpdated; };
|
||||
uint64_t GetLeaseSetSubmissionTime () const { return m_LeaseSetSubmissionTime; }
|
||||
|
||||
std::shared_ptr<GarlicRoutingPath> GetSharedRoutingPath ();
|
||||
void SetSharedRoutingPath (std::shared_ptr<GarlicRoutingPath> path);
|
||||
|
||||
const GarlicDestination * GetOwner () const { return m_Owner; }
|
||||
void SetOwner (GarlicDestination * owner) { m_Owner = owner; }
|
||||
|
||||
private:
|
||||
|
||||
size_t CreateAESBlock (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg);
|
||||
size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const I2NPMessage> msg, UnconfirmedTags * newTags);
|
||||
size_t CreateGarlicClove (uint8_t * buf, std::shared_ptr<const I2NPMessage> msg, bool isDestination);
|
||||
size_t CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID);
|
||||
|
||||
void TagsConfirmed (uint32_t msgID);
|
||||
UnconfirmedTags * GenerateSessionTags ();
|
||||
|
||||
private:
|
||||
|
||||
GarlicDestination * m_Owner;
|
||||
std::shared_ptr<const i2p::data::RoutingDestination> m_Destination;
|
||||
|
||||
i2p::crypto::AESKey m_SessionKey;
|
||||
std::list<SessionTag> m_SessionTags;
|
||||
int m_NumTags;
|
||||
std::map<uint32_t, std::unique_ptr<UnconfirmedTags> > m_UnconfirmedTagsMsgs; // msgID->tags
|
||||
|
||||
LeaseSetUpdateStatus m_LeaseSetUpdateStatus;
|
||||
uint32_t m_LeaseSetUpdateMsgID;
|
||||
uint64_t m_LeaseSetSubmissionTime; // in milliseconds
|
||||
|
||||
i2p::crypto::CBCEncryption m_Encryption;
|
||||
|
||||
std::shared_ptr<GarlicRoutingPath> m_SharedRoutingPath;
|
||||
|
||||
public:
|
||||
// for HTTP only
|
||||
size_t GetNumOutgoingTags () const { return m_SessionTags.size (); };
|
||||
};
|
||||
//using GarlicRoutingSessionPtr = std::shared_ptr<GarlicRoutingSession>;
|
||||
typedef std::shared_ptr<GarlicRoutingSession> GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8
|
||||
|
||||
class GarlicDestination: public i2p::data::LocalDestination
|
||||
{
|
||||
public:
|
||||
|
||||
GarlicDestination ();
|
||||
~GarlicDestination ();
|
||||
|
||||
void CleanUp ();
|
||||
void SetNumTags (int numTags) { m_NumTags = numTags; };
|
||||
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet);
|
||||
void CleanupExpiredTags ();
|
||||
void RemoveDeliveryStatusSession (uint32_t msgID);
|
||||
std::shared_ptr<I2NPMessage> WrapMessage (std::shared_ptr<const i2p::data::RoutingDestination> destination,
|
||||
std::shared_ptr<I2NPMessage> msg, bool attachLeaseSet = false);
|
||||
|
||||
void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
|
||||
virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread
|
||||
void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID);
|
||||
|
||||
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
virtual void SetLeaseSetUpdated ();
|
||||
|
||||
virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
|
||||
virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
|
||||
virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
void HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
void SaveTags ();
|
||||
void LoadTags ();
|
||||
|
||||
private:
|
||||
|
||||
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption,
|
||||
std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||
|
||||
private:
|
||||
|
||||
BN_CTX * m_Ctx; // incoming
|
||||
// outgoing sessions
|
||||
int m_NumTags;
|
||||
std::mutex m_SessionsMutex;
|
||||
std::map<i2p::data::IdentHash, GarlicRoutingSessionPtr> m_Sessions;
|
||||
// incoming
|
||||
std::map<SessionTag, std::shared_ptr<AESDecryption> > m_Tags;
|
||||
// DeliveryStatus
|
||||
std::mutex m_DeliveryStatusSessionsMutex;
|
||||
std::map<uint32_t, GarlicRoutingSessionPtr> m_DeliveryStatusSessions; // msgID -> session
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
size_t GetNumIncomingTags () const { return m_Tags.size (); }
|
||||
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
|
||||
};
|
||||
|
||||
void CleanUpTagsFiles ();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
529
libi2pd/Gost.cpp
Normal file
529
libi2pd/Gost.cpp
Normal file
|
@ -0,0 +1,529 @@
|
|||
#include <string.h>
|
||||
#include <array>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/evp.h>
|
||||
#include "I2PEndian.h"
|
||||
#include "Gost.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
|
||||
// GOST R 34.10
|
||||
|
||||
GOSTR3410Curve::GOSTR3410Curve (BIGNUM * a, BIGNUM * b, BIGNUM * p, BIGNUM * q, BIGNUM * x, BIGNUM * y)
|
||||
{
|
||||
m_KeyLen = BN_num_bytes (p);
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
m_Group = EC_GROUP_new_curve_GFp (p, a, b, ctx);
|
||||
EC_POINT * P = EC_POINT_new (m_Group);
|
||||
EC_POINT_set_affine_coordinates_GFp (m_Group, P, x, y, ctx);
|
||||
EC_GROUP_set_generator (m_Group, P, q, nullptr);
|
||||
EC_GROUP_set_curve_name (m_Group, NID_id_GostR3410_2001);
|
||||
EC_POINT_free(P);
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
|
||||
GOSTR3410Curve::~GOSTR3410Curve ()
|
||||
{
|
||||
EC_GROUP_free (m_Group);
|
||||
}
|
||||
|
||||
EC_POINT * GOSTR3410Curve::MulP (const BIGNUM * n) const
|
||||
{
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
auto p = EC_POINT_new (m_Group);
|
||||
EC_POINT_mul (m_Group, p, n, nullptr, nullptr, ctx);
|
||||
BN_CTX_free (ctx);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool GOSTR3410Curve::GetXY (const EC_POINT * p, BIGNUM * x, BIGNUM * y) const
|
||||
{
|
||||
return EC_POINT_get_affine_coordinates_GFp (m_Group, p, x, y, nullptr);
|
||||
}
|
||||
|
||||
EC_POINT * GOSTR3410Curve::CreatePoint (const BIGNUM * x, const BIGNUM * y) const
|
||||
{
|
||||
EC_POINT * p = EC_POINT_new (m_Group);
|
||||
EC_POINT_set_affine_coordinates_GFp (m_Group, p, x, y, nullptr);
|
||||
return p;
|
||||
}
|
||||
|
||||
void GOSTR3410Curve::Sign (const BIGNUM * priv, const BIGNUM * digest, BIGNUM * r, BIGNUM * s)
|
||||
{
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
BN_CTX_start (ctx);
|
||||
BIGNUM * q = BN_CTX_get (ctx);
|
||||
EC_GROUP_get_order(m_Group, q, ctx);
|
||||
BIGNUM * k = BN_CTX_get (ctx);
|
||||
BN_rand_range (k, q); // 0 < k < q
|
||||
EC_POINT * C = MulP (k); // C = k*P
|
||||
GetXY (C, r, nullptr); // r = Cx
|
||||
EC_POINT_free (C);
|
||||
BN_mod_mul (s, r, priv, q, ctx); // (r*priv)%q
|
||||
BIGNUM * tmp = BN_CTX_get (ctx);
|
||||
BN_mod_mul (tmp, k, digest, q, ctx); // (k*digest)%q
|
||||
BN_mod_add (s, s, tmp, q, ctx); // (r*priv+k*digest)%q
|
||||
BN_CTX_end (ctx);
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
|
||||
bool GOSTR3410Curve::Verify (const EC_POINT * pub, const BIGNUM * digest, const BIGNUM * r, const BIGNUM * s)
|
||||
{
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
BN_CTX_start (ctx);
|
||||
BIGNUM * q = BN_CTX_get (ctx);
|
||||
EC_GROUP_get_order(m_Group, q, ctx);
|
||||
BIGNUM * h = BN_CTX_get (ctx);
|
||||
BN_mod (h, digest, q, ctx); // h = digest % q
|
||||
BN_mod_inverse (h, h, q, ctx); // 1/h mod q
|
||||
BIGNUM * z1 = BN_CTX_get (ctx);
|
||||
BN_mod_mul (z1, s, h, q, ctx); // z1 = s/h
|
||||
BIGNUM * z2 = BN_CTX_get (ctx);
|
||||
BN_sub (z2, q, r); // z2 = -r
|
||||
BN_mod_mul (z2, z2, h, q, ctx); // z2 = -r/h
|
||||
EC_POINT * C = EC_POINT_new (m_Group);
|
||||
EC_POINT_mul (m_Group, C, z1, pub, z2, ctx); // z1*P + z2*pub
|
||||
BIGNUM * x = BN_CTX_get (ctx);
|
||||
GetXY (C, x, nullptr); // Cx
|
||||
BN_mod (x, x, q, ctx); // Cx % q
|
||||
bool ret = !BN_cmp (x, r); // Cx = r ?
|
||||
EC_POINT_free (C);
|
||||
BN_CTX_end (ctx);
|
||||
BN_CTX_free (ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
EC_POINT * GOSTR3410Curve::RecoverPublicKey (const BIGNUM * digest, const BIGNUM * r, const BIGNUM * s, bool isNegativeY) const
|
||||
{
|
||||
// s*P = r*Q + h*C
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
BN_CTX_start (ctx);
|
||||
EC_POINT * C = EC_POINT_new (m_Group); // C = k*P = (rx, ry)
|
||||
EC_POINT * Q = nullptr;
|
||||
if (EC_POINT_set_compressed_coordinates_GFp (m_Group, C, r, isNegativeY ? 1 : 0, ctx))
|
||||
{
|
||||
EC_POINT * S = EC_POINT_new (m_Group); // S = s*P
|
||||
EC_POINT_mul (m_Group, S, s, nullptr, nullptr, ctx);
|
||||
BIGNUM * q = BN_CTX_get (ctx);
|
||||
EC_GROUP_get_order(m_Group, q, ctx);
|
||||
BIGNUM * h = BN_CTX_get (ctx);
|
||||
BN_mod (h, digest, q, ctx); // h = digest % q
|
||||
BN_sub (h, q, h); // h = -h
|
||||
EC_POINT * H = EC_POINT_new (m_Group);
|
||||
EC_POINT_mul (m_Group, H, nullptr, C, h, ctx); // -h*C
|
||||
EC_POINT_add (m_Group, C, S, H, ctx); // s*P - h*C
|
||||
EC_POINT_free (H);
|
||||
EC_POINT_free (S);
|
||||
BIGNUM * r1 = BN_CTX_get (ctx);
|
||||
BN_mod_inverse (r1, r, q, ctx);
|
||||
Q = EC_POINT_new (m_Group);
|
||||
EC_POINT_mul (m_Group, Q, nullptr, C, r1, ctx); // (s*P - h*C)/r
|
||||
}
|
||||
EC_POINT_free (C);
|
||||
BN_CTX_end (ctx);
|
||||
BN_CTX_free (ctx);
|
||||
return Q;
|
||||
}
|
||||
|
||||
static GOSTR3410Curve * CreateGOSTR3410Curve (GOSTR3410ParamSet paramSet)
|
||||
{
|
||||
// a, b, p, q, x, y
|
||||
static const char * params[eGOSTR3410NumParamSets][6] =
|
||||
{
|
||||
{
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94",
|
||||
"A6",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893",
|
||||
"1",
|
||||
"8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14"
|
||||
}, // CryptoPro A
|
||||
{
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4",
|
||||
"E8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275",
|
||||
"3",
|
||||
"7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4"
|
||||
} // tc26-2012-paramSetA-512
|
||||
};
|
||||
|
||||
BIGNUM * a = nullptr, * b = nullptr, * p = nullptr, * q =nullptr, * x = nullptr, * y = nullptr;
|
||||
BN_hex2bn(&a, params[paramSet][0]);
|
||||
BN_hex2bn(&b, params[paramSet][1]);
|
||||
BN_hex2bn(&p, params[paramSet][2]);
|
||||
BN_hex2bn(&q, params[paramSet][3]);
|
||||
BN_hex2bn(&x, params[paramSet][4]);
|
||||
BN_hex2bn(&y, params[paramSet][5]);
|
||||
auto curve = new GOSTR3410Curve (a, b, p, q, x, y);
|
||||
BN_free (a); BN_free (b); BN_free (p); BN_free (q); BN_free (x); BN_free (y);
|
||||
return curve;
|
||||
}
|
||||
|
||||
static std::array<std::unique_ptr<GOSTR3410Curve>, eGOSTR3410NumParamSets> g_GOSTR3410Curves;
|
||||
std::unique_ptr<GOSTR3410Curve>& GetGOSTR3410Curve (GOSTR3410ParamSet paramSet)
|
||||
{
|
||||
if (!g_GOSTR3410Curves[paramSet])
|
||||
{
|
||||
auto c = CreateGOSTR3410Curve (paramSet);
|
||||
if (!g_GOSTR3410Curves[paramSet]) // make sure it was not created already
|
||||
g_GOSTR3410Curves[paramSet].reset (c);
|
||||
else
|
||||
delete c;
|
||||
}
|
||||
return g_GOSTR3410Curves[paramSet];
|
||||
}
|
||||
|
||||
// ГОСТ 34.11-2012
|
||||
|
||||
static const uint8_t sbox_[256] =
|
||||
{
|
||||
0xFC, 0xEE, 0xDD, 0x11, 0xCF, 0x6E, 0x31, 0x16, 0xFB, 0xC4, 0xFA, 0xDA, 0x23, 0xC5, 0x04, 0x4D,
|
||||
0xE9, 0x77, 0xF0, 0xDB, 0x93, 0x2E, 0x99, 0xBA, 0x17, 0x36, 0xF1, 0xBB, 0x14, 0xCD, 0x5F, 0xC1,
|
||||
0xF9, 0x18, 0x65, 0x5A, 0xE2, 0x5C, 0xEF, 0x21, 0x81, 0x1C, 0x3C, 0x42, 0x8B, 0x01, 0x8E, 0x4F,
|
||||
0x05, 0x84, 0x02, 0xAE, 0xE3, 0x6A, 0x8F, 0xA0, 0x06, 0x0B, 0xED, 0x98, 0x7F, 0xD4, 0xD3, 0x1F,
|
||||
0xEB, 0x34, 0x2C, 0x51, 0xEA, 0xC8, 0x48, 0xAB, 0xF2, 0x2A, 0x68, 0xA2, 0xFD, 0x3A, 0xCE, 0xCC,
|
||||
0xB5, 0x70, 0x0E, 0x56, 0x08, 0x0C, 0x76, 0x12, 0xBF, 0x72, 0x13, 0x47, 0x9C, 0xB7, 0x5D, 0x87,
|
||||
0x15, 0xA1, 0x96, 0x29, 0x10, 0x7B, 0x9A, 0xC7, 0xF3, 0x91, 0x78, 0x6F, 0x9D, 0x9E, 0xB2, 0xB1,
|
||||
0x32, 0x75, 0x19, 0x3D, 0xFF, 0x35, 0x8A, 0x7E, 0x6D, 0x54, 0xC6, 0x80, 0xC3, 0xBD, 0x0D, 0x57,
|
||||
0xDF, 0xF5, 0x24, 0xA9, 0x3E, 0xA8, 0x43, 0xC9, 0xD7, 0x79, 0xD6, 0xF6, 0x7C, 0x22, 0xB9, 0x03,
|
||||
0xE0, 0x0F, 0xEC, 0xDE, 0x7A, 0x94, 0xB0, 0xBC, 0xDC, 0xE8, 0x28, 0x50, 0x4E, 0x33, 0x0A, 0x4A,
|
||||
0xA7, 0x97, 0x60, 0x73, 0x1E, 0x00, 0x62, 0x44, 0x1A, 0xB8, 0x38, 0x82, 0x64, 0x9F, 0x26, 0x41,
|
||||
0xAD, 0x45, 0x46, 0x92, 0x27, 0x5E, 0x55, 0x2F, 0x8C, 0xA3, 0xA5, 0x7D, 0x69, 0xD5, 0x95, 0x3B,
|
||||
0x07, 0x58, 0xB3, 0x40, 0x86, 0xAC, 0x1D, 0xF7, 0x30, 0x37, 0x6B, 0xE4, 0x88, 0xD9, 0xE7, 0x89,
|
||||
0xE1, 0x1B, 0x83, 0x49, 0x4C, 0x3F, 0xF8, 0xFE, 0x8D, 0x53, 0xAA, 0x90, 0xCA, 0xD8, 0x85, 0x61,
|
||||
0x20, 0x71, 0x67, 0xA4, 0x2D, 0x2B, 0x09, 0x5B, 0xCB, 0x9B, 0x25, 0xD0, 0xBE, 0xE5, 0x6C, 0x52,
|
||||
0x59, 0xA6, 0x74, 0xD2, 0xE6, 0xF4, 0xB4, 0xC0, 0xD1, 0x66, 0xAF, 0xC2, 0x39, 0x4B, 0x63, 0xB6
|
||||
};
|
||||
|
||||
static const uint64_t A_[64] =
|
||||
{
|
||||
0x8e20faa72ba0b470, 0x47107ddd9b505a38, 0xad08b0e0c3282d1c, 0xd8045870ef14980e,
|
||||
0x6c022c38f90a4c07, 0x3601161cf205268d, 0x1b8e0b0e798c13c8, 0x83478b07b2468764,
|
||||
0xa011d380818e8f40, 0x5086e740ce47c920, 0x2843fd2067adea10, 0x14aff010bdd87508,
|
||||
0x0ad97808d06cb404, 0x05e23c0468365a02, 0x8c711e02341b2d01, 0x46b60f011a83988e,
|
||||
0x90dab52a387ae76f, 0x486dd4151c3dfdb9, 0x24b86a840e90f0d2, 0x125c354207487869,
|
||||
0x092e94218d243cba, 0x8a174a9ec8121e5d, 0x4585254f64090fa0, 0xaccc9ca9328a8950,
|
||||
0x9d4df05d5f661451, 0xc0a878a0a1330aa6, 0x60543c50de970553, 0x302a1e286fc58ca7,
|
||||
0x18150f14b9ec46dd, 0x0c84890ad27623e0, 0x0642ca05693b9f70, 0x0321658cba93c138,
|
||||
0x86275df09ce8aaa8, 0x439da0784e745554, 0xafc0503c273aa42a, 0xd960281e9d1d5215,
|
||||
0xe230140fc0802984, 0x71180a8960409a42, 0xb60c05ca30204d21, 0x5b068c651810a89e,
|
||||
0x456c34887a3805b9, 0xac361a443d1c8cd2, 0x561b0d22900e4669, 0x2b838811480723ba,
|
||||
0x9bcf4486248d9f5d, 0xc3e9224312c8c1a0, 0xeffa11af0964ee50, 0xf97d86d98a327728,
|
||||
0xe4fa2054a80b329c, 0x727d102a548b194e, 0x39b008152acb8227, 0x9258048415eb419d,
|
||||
0x492c024284fbaec0, 0xaa16012142f35760, 0x550b8e9e21f7a530, 0xa48b474f9ef5dc18,
|
||||
0x70a6a56e2440598e, 0x3853dc371220a247, 0x1ca76e95091051ad, 0x0edd37c48a08a6d8,
|
||||
0x07e095624504536c, 0x8d70c431ac02a736, 0xc83862965601dd1b, 0x641c314b2b8ee083
|
||||
};
|
||||
|
||||
static const uint8_t C_[12][64] =
|
||||
{
|
||||
{
|
||||
0xb1,0x08,0x5b,0xda,0x1e,0xca,0xda,0xe9,0xeb,0xcb,0x2f,0x81,0xc0,0x65,0x7c,0x1f,
|
||||
0x2f,0x6a,0x76,0x43,0x2e,0x45,0xd0,0x16,0x71,0x4e,0xb8,0x8d,0x75,0x85,0xc4,0xfc,
|
||||
0x4b,0x7c,0xe0,0x91,0x92,0x67,0x69,0x01,0xa2,0x42,0x2a,0x08,0xa4,0x60,0xd3,0x15,
|
||||
0x05,0x76,0x74,0x36,0xcc,0x74,0x4d,0x23,0xdd,0x80,0x65,0x59,0xf2,0xa6,0x45,0x07
|
||||
},
|
||||
{
|
||||
0x6f,0xa3,0xb5,0x8a,0xa9,0x9d,0x2f,0x1a,0x4f,0xe3,0x9d,0x46,0x0f,0x70,0xb5,0xd7,
|
||||
0xf3,0xfe,0xea,0x72,0x0a,0x23,0x2b,0x98,0x61,0xd5,0x5e,0x0f,0x16,0xb5,0x01,0x31,
|
||||
0x9a,0xb5,0x17,0x6b,0x12,0xd6,0x99,0x58,0x5c,0xb5,0x61,0xc2,0xdb,0x0a,0xa7,0xca,
|
||||
0x55,0xdd,0xa2,0x1b,0xd7,0xcb,0xcd,0x56,0xe6,0x79,0x04,0x70,0x21,0xb1,0x9b,0xb7
|
||||
},
|
||||
{
|
||||
0xf5,0x74,0xdc,0xac,0x2b,0xce,0x2f,0xc7,0x0a,0x39,0xfc,0x28,0x6a,0x3d,0x84,0x35,
|
||||
0x06,0xf1,0x5e,0x5f,0x52,0x9c,0x1f,0x8b,0xf2,0xea,0x75,0x14,0xb1,0x29,0x7b,0x7b,
|
||||
0xd3,0xe2,0x0f,0xe4,0x90,0x35,0x9e,0xb1,0xc1,0xc9,0x3a,0x37,0x60,0x62,0xdb,0x09,
|
||||
0xc2,0xb6,0xf4,0x43,0x86,0x7a,0xdb,0x31,0x99,0x1e,0x96,0xf5,0x0a,0xba,0x0a,0xb2
|
||||
},
|
||||
{
|
||||
0xef,0x1f,0xdf,0xb3,0xe8,0x15,0x66,0xd2,0xf9,0x48,0xe1,0xa0,0x5d,0x71,0xe4,0xdd,
|
||||
0x48,0x8e,0x85,0x7e,0x33,0x5c,0x3c,0x7d,0x9d,0x72,0x1c,0xad,0x68,0x5e,0x35,0x3f,
|
||||
0xa9,0xd7,0x2c,0x82,0xed,0x03,0xd6,0x75,0xd8,0xb7,0x13,0x33,0x93,0x52,0x03,0xbe,
|
||||
0x34,0x53,0xea,0xa1,0x93,0xe8,0x37,0xf1,0x22,0x0c,0xbe,0xbc,0x84,0xe3,0xd1,0x2e
|
||||
},
|
||||
{
|
||||
0x4b,0xea,0x6b,0xac,0xad,0x47,0x47,0x99,0x9a,0x3f,0x41,0x0c,0x6c,0xa9,0x23,0x63,
|
||||
0x7f,0x15,0x1c,0x1f,0x16,0x86,0x10,0x4a,0x35,0x9e,0x35,0xd7,0x80,0x0f,0xff,0xbd,
|
||||
0xbf,0xcd,0x17,0x47,0x25,0x3a,0xf5,0xa3,0xdf,0xff,0x00,0xb7,0x23,0x27,0x1a,0x16,
|
||||
0x7a,0x56,0xa2,0x7e,0xa9,0xea,0x63,0xf5,0x60,0x17,0x58,0xfd,0x7c,0x6c,0xfe,0x57
|
||||
},
|
||||
{
|
||||
0xae,0x4f,0xae,0xae,0x1d,0x3a,0xd3,0xd9,0x6f,0xa4,0xc3,0x3b,0x7a,0x30,0x39,0xc0,
|
||||
0x2d,0x66,0xc4,0xf9,0x51,0x42,0xa4,0x6c,0x18,0x7f,0x9a,0xb4,0x9a,0xf0,0x8e,0xc6,
|
||||
0xcf,0xfa,0xa6,0xb7,0x1c,0x9a,0xb7,0xb4,0x0a,0xf2,0x1f,0x66,0xc2,0xbe,0xc6,0xb6,
|
||||
0xbf,0x71,0xc5,0x72,0x36,0x90,0x4f,0x35,0xfa,0x68,0x40,0x7a,0x46,0x64,0x7d,0x6e
|
||||
},
|
||||
{
|
||||
0xf4,0xc7,0x0e,0x16,0xee,0xaa,0xc5,0xec,0x51,0xac,0x86,0xfe,0xbf,0x24,0x09,0x54,
|
||||
0x39,0x9e,0xc6,0xc7,0xe6,0xbf,0x87,0xc9,0xd3,0x47,0x3e,0x33,0x19,0x7a,0x93,0xc9,
|
||||
0x09,0x92,0xab,0xc5,0x2d,0x82,0x2c,0x37,0x06,0x47,0x69,0x83,0x28,0x4a,0x05,0x04,
|
||||
0x35,0x17,0x45,0x4c,0xa2,0x3c,0x4a,0xf3,0x88,0x86,0x56,0x4d,0x3a,0x14,0xd4,0x93
|
||||
},
|
||||
{
|
||||
0x9b,0x1f,0x5b,0x42,0x4d,0x93,0xc9,0xa7,0x03,0xe7,0xaa,0x02,0x0c,0x6e,0x41,0x41,
|
||||
0x4e,0xb7,0xf8,0x71,0x9c,0x36,0xde,0x1e,0x89,0xb4,0x44,0x3b,0x4d,0xdb,0xc4,0x9a,
|
||||
0xf4,0x89,0x2b,0xcb,0x92,0x9b,0x06,0x90,0x69,0xd1,0x8d,0x2b,0xd1,0xa5,0xc4,0x2f,
|
||||
0x36,0xac,0xc2,0x35,0x59,0x51,0xa8,0xd9,0xa4,0x7f,0x0d,0xd4,0xbf,0x02,0xe7,0x1e
|
||||
},
|
||||
{
|
||||
0x37,0x8f,0x5a,0x54,0x16,0x31,0x22,0x9b,0x94,0x4c,0x9a,0xd8,0xec,0x16,0x5f,0xde,
|
||||
0x3a,0x7d,0x3a,0x1b,0x25,0x89,0x42,0x24,0x3c,0xd9,0x55,0xb7,0xe0,0x0d,0x09,0x84,
|
||||
0x80,0x0a,0x44,0x0b,0xdb,0xb2,0xce,0xb1,0x7b,0x2b,0x8a,0x9a,0xa6,0x07,0x9c,0x54,
|
||||
0x0e,0x38,0xdc,0x92,0xcb,0x1f,0x2a,0x60,0x72,0x61,0x44,0x51,0x83,0x23,0x5a,0xdb
|
||||
},
|
||||
{
|
||||
0xab,0xbe,0xde,0xa6,0x80,0x05,0x6f,0x52,0x38,0x2a,0xe5,0x48,0xb2,0xe4,0xf3,0xf3,
|
||||
0x89,0x41,0xe7,0x1c,0xff,0x8a,0x78,0xdb,0x1f,0xff,0xe1,0x8a,0x1b,0x33,0x61,0x03,
|
||||
0x9f,0xe7,0x67,0x02,0xaf,0x69,0x33,0x4b,0x7a,0x1e,0x6c,0x30,0x3b,0x76,0x52,0xf4,
|
||||
0x36,0x98,0xfa,0xd1,0x15,0x3b,0xb6,0xc3,0x74,0xb4,0xc7,0xfb,0x98,0x45,0x9c,0xed
|
||||
},
|
||||
{
|
||||
0x7b,0xcd,0x9e,0xd0,0xef,0xc8,0x89,0xfb,0x30,0x02,0xc6,0xcd,0x63,0x5a,0xfe,0x94,
|
||||
0xd8,0xfa,0x6b,0xbb,0xeb,0xab,0x07,0x61,0x20,0x01,0x80,0x21,0x14,0x84,0x66,0x79,
|
||||
0x8a,0x1d,0x71,0xef,0xea,0x48,0xb9,0xca,0xef,0xba,0xcd,0x1d,0x7d,0x47,0x6e,0x98,
|
||||
0xde,0xa2,0x59,0x4a,0xc0,0x6f,0xd8,0x5d,0x6b,0xca,0xa4,0xcd,0x81,0xf3,0x2d,0x1b
|
||||
},
|
||||
{
|
||||
0x37,0x8e,0xe7,0x67,0xf1,0x16,0x31,0xba,0xd2,0x13,0x80,0xb0,0x04,0x49,0xb1,0x7a,
|
||||
0xcd,0xa4,0x3c,0x32,0xbc,0xdf,0x1d,0x77,0xf8,0x20,0x12,0xd4,0x30,0x21,0x9f,0x9b,
|
||||
0x5d,0x80,0xef,0x9d,0x18,0x91,0xcc,0x86,0xe7,0x1d,0xa4,0xaa,0x88,0xe1,0x28,0x52,
|
||||
0xfa,0xf4,0x17,0xd5,0xd9,0xb2,0x1b,0x99,0x48,0xbc,0x92,0x4a,0xf1,0x1b,0xd7,0x20
|
||||
}
|
||||
};
|
||||
|
||||
union GOST3411Block // 8 bytes aligned
|
||||
{
|
||||
uint8_t buf[64];
|
||||
uint64_t ll[8];
|
||||
|
||||
GOST3411Block operator^(const GOST3411Block& other) const
|
||||
{
|
||||
GOST3411Block ret;
|
||||
for (int i = 0; i < 8; i++)
|
||||
ret.ll[i] = ll[i]^other.ll[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
GOST3411Block operator^(const uint8_t * other) const
|
||||
{
|
||||
GOST3411Block ret;
|
||||
for (int i = 0; i < 64; i++)
|
||||
ret.buf[i] = buf[i]^other[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
GOST3411Block operator+(const GOST3411Block& other) const
|
||||
{
|
||||
GOST3411Block ret;
|
||||
uint8_t carry = 0;
|
||||
for (int i = 63; i >= 0; i--)
|
||||
{
|
||||
uint16_t sum = buf[i] + other.buf[i] + carry;
|
||||
ret.buf[i] = sum & 0xFF;
|
||||
carry = sum >> 8;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Add (uint32_t c)
|
||||
{
|
||||
for (int i = 63; i >= 0; i--)
|
||||
{
|
||||
c += buf[i];
|
||||
buf[i] = c;
|
||||
c >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
void SPL ()
|
||||
{
|
||||
uint8_t p[64];
|
||||
memcpy (p, buf, 64); // we need to copy it for P's transposition
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint64_t c = 0;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
uint8_t bit = 0x80;
|
||||
uint8_t byte = sbox_[p[j*8+i]]; // S - sbox_, P - transpose (i,j)
|
||||
for (int k = 0; k < 8; k++)
|
||||
{
|
||||
if (byte & bit) c ^= A_[j*8+k];
|
||||
bit >>= 1;
|
||||
}
|
||||
}
|
||||
ll[i] = htobe64 (c);
|
||||
}
|
||||
}
|
||||
|
||||
GOST3411Block E (const GOST3411Block& m)
|
||||
{
|
||||
GOST3411Block k = *this;
|
||||
GOST3411Block res = k^m;
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
res.SPL ();
|
||||
k = k^C_[i];
|
||||
k.SPL ();
|
||||
res = k^res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
static GOST3411Block gN (const GOST3411Block& N, const GOST3411Block& h, const GOST3411Block& m)
|
||||
{
|
||||
GOST3411Block res = N ^ h;
|
||||
res.SPL ();
|
||||
res = res.E (m);
|
||||
res = res^h;
|
||||
res = res^m;
|
||||
return res;
|
||||
}
|
||||
|
||||
static void H (const uint8_t * iv, const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
// stage 1
|
||||
GOST3411Block h, N, s, m;
|
||||
memcpy (h.buf, iv, 64);
|
||||
memset (N.buf, 0, 64);
|
||||
memset (s.buf, 0, 64);
|
||||
size_t l = len;
|
||||
// stage 2
|
||||
while (l >= 64)
|
||||
{
|
||||
memcpy (m.buf, buf + l - 64, 64); // TODO
|
||||
h= gN (N, h, m);
|
||||
N.Add (512);
|
||||
s = m + s;
|
||||
l -= 64;
|
||||
}
|
||||
// stage 3
|
||||
size_t padding = 64 - l;
|
||||
if (padding)
|
||||
{
|
||||
memset (m.buf, 0, padding - 1);
|
||||
m.buf[padding - 1] = 1;
|
||||
}
|
||||
memcpy (m.buf + padding, buf, l);
|
||||
|
||||
h = gN (N, h, m);
|
||||
N.Add (l*8);
|
||||
s = m + s;
|
||||
|
||||
GOST3411Block N0;
|
||||
memset (N0.buf, 0, 64);
|
||||
h = gN (N0, h, N);
|
||||
h = gN (N0, h, s);
|
||||
|
||||
memcpy (digest, h.buf, 64);
|
||||
}
|
||||
|
||||
void GOSTR3411_2012_256 (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
uint8_t iv[64];
|
||||
memset (iv, 1, 64);
|
||||
uint8_t h[64];
|
||||
H (iv, buf, len, h);
|
||||
memcpy (digest, h, 32); // first half
|
||||
}
|
||||
|
||||
void GOSTR3411_2012_512 (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
uint8_t iv[64];
|
||||
memset (iv, 0, 64);
|
||||
H (iv, buf, len, digest);
|
||||
}
|
||||
|
||||
// reverse order
|
||||
struct GOSTR3411_2012_CTX
|
||||
{
|
||||
GOST3411Block h, N, s, m;
|
||||
size_t len;
|
||||
bool is512;
|
||||
};
|
||||
|
||||
GOSTR3411_2012_CTX * GOSTR3411_2012_CTX_new ()
|
||||
{
|
||||
return new GOSTR3411_2012_CTX;
|
||||
}
|
||||
|
||||
void GOSTR3411_2012_CTX_free (GOSTR3411_2012_CTX * ctx)
|
||||
{
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
void GOSTR3411_2012_CTX_Init (GOSTR3411_2012_CTX * ctx, bool is512)
|
||||
{
|
||||
uint8_t iv[64];
|
||||
memset (iv, is512 ? 0 : 1, 64);
|
||||
memcpy (ctx->h.buf, iv, 64);
|
||||
memset (ctx->N.buf, 0, 64);
|
||||
memset (ctx->s.buf, 0, 64);
|
||||
ctx->len = 0;
|
||||
ctx->is512 = is512;
|
||||
}
|
||||
|
||||
void GOSTR3411_2012_CTX_Update (const uint8_t * buf, size_t len, GOSTR3411_2012_CTX * ctx)
|
||||
{
|
||||
if (!len) return;
|
||||
if (ctx->len > 0) // something left from buffer
|
||||
{
|
||||
size_t l = 64 - ctx->len;
|
||||
if (len < l) l = len;
|
||||
for (size_t i = 0; i < l; i++)
|
||||
ctx->m.buf[ctx->len + i] = buf[l-i-1]; // invert
|
||||
ctx->len += l; len -= l; buf += l;
|
||||
|
||||
ctx->h = gN (ctx->N, ctx->h, ctx->m);
|
||||
ctx->N.Add (512);
|
||||
ctx->s = ctx->m + ctx->s;
|
||||
}
|
||||
while (len >= 64)
|
||||
{
|
||||
for (size_t i = 0; i < 64; i++)
|
||||
ctx->m.buf[i] = buf[63-i]; // invert
|
||||
len -= 64; buf += 64;
|
||||
ctx->h = gN (ctx->N, ctx->h, ctx->m);
|
||||
ctx->N.Add (512);
|
||||
ctx->s = ctx->m + ctx->s;
|
||||
}
|
||||
if (len > 0) // carry remaining
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
ctx->m.buf[i] = buf[len-i-1]; // invert
|
||||
}
|
||||
ctx->len = len;
|
||||
}
|
||||
|
||||
void GOSTR3411_2012_CTX_Finish (uint8_t * digest, GOSTR3411_2012_CTX * ctx)
|
||||
{
|
||||
GOST3411Block m;
|
||||
size_t padding = 64 - ctx->len;
|
||||
if (padding)
|
||||
{
|
||||
memset (m.buf, 0, padding - 1);
|
||||
m.buf[padding - 1] = 1;
|
||||
}
|
||||
memcpy (m.buf + padding, ctx->m.buf, ctx->len);
|
||||
|
||||
ctx->h = gN (ctx->N, ctx->h, m);
|
||||
ctx->N.Add (ctx->len*8);
|
||||
ctx->s = m + ctx->s;
|
||||
|
||||
GOST3411Block N0;
|
||||
memset (N0.buf, 0, 64);
|
||||
ctx->h = gN (N0, ctx->h, ctx->N);
|
||||
ctx->h = gN (N0, ctx->h, ctx->s);
|
||||
|
||||
size_t sz = ctx->is512 ? 64 : 32;
|
||||
for (size_t i = 0; i < sz; i++)
|
||||
digest[i] = ctx->h.buf[sz - i - 1];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
62
libi2pd/Gost.h
Normal file
62
libi2pd/Gost.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef GOST_H__
|
||||
#define GOST_H__
|
||||
|
||||
#include <memory>
|
||||
#include <openssl/ec.h>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
|
||||
// ГОСТ Р 34.10
|
||||
|
||||
enum GOSTR3410ParamSet
|
||||
{
|
||||
eGOSTR3410CryptoProA = 0, // 1.2.643.2.2.35.1
|
||||
// XchA = A, XchB = C
|
||||
//eGOSTR3410CryptoProXchA, // 1.2.643.2.2.36.0
|
||||
//eGOSTR3410CryptoProXchB, // 1.2.643.2.2.36.1
|
||||
eGOSTR3410TC26A512, // 1.2.643.7.1.2.1.2.1
|
||||
eGOSTR3410NumParamSets
|
||||
};
|
||||
|
||||
class GOSTR3410Curve
|
||||
{
|
||||
public:
|
||||
|
||||
GOSTR3410Curve (BIGNUM * a, BIGNUM * b, BIGNUM * p, BIGNUM * q, BIGNUM * x, BIGNUM * y);
|
||||
~GOSTR3410Curve ();
|
||||
|
||||
size_t GetKeyLen () const { return m_KeyLen; };
|
||||
const EC_GROUP * GetGroup () const { return m_Group; };
|
||||
EC_POINT * MulP (const BIGNUM * n) const;
|
||||
bool GetXY (const EC_POINT * p, BIGNUM * x, BIGNUM * y) const;
|
||||
EC_POINT * CreatePoint (const BIGNUM * x, const BIGNUM * y) const;
|
||||
void Sign (const BIGNUM * priv, const BIGNUM * digest, BIGNUM * r, BIGNUM * s);
|
||||
bool Verify (const EC_POINT * pub, const BIGNUM * digest, const BIGNUM * r, const BIGNUM * s);
|
||||
EC_POINT * RecoverPublicKey (const BIGNUM * digest, const BIGNUM * r, const BIGNUM * s, bool isNegativeY = false) const;
|
||||
|
||||
private:
|
||||
|
||||
EC_GROUP * m_Group;
|
||||
size_t m_KeyLen; // in bytes
|
||||
};
|
||||
|
||||
std::unique_ptr<GOSTR3410Curve>& GetGOSTR3410Curve (GOSTR3410ParamSet paramSet);
|
||||
|
||||
// Big Endian
|
||||
void GOSTR3411_2012_256 (const uint8_t * buf, size_t len, uint8_t * digest);
|
||||
void GOSTR3411_2012_512 (const uint8_t * buf, size_t len, uint8_t * digest);
|
||||
|
||||
// Little Endian
|
||||
struct GOSTR3411_2012_CTX;
|
||||
GOSTR3411_2012_CTX * GOSTR3411_2012_CTX_new ();
|
||||
void GOSTR3411_2012_CTX_Init (GOSTR3411_2012_CTX * ctx, bool is512 = true);
|
||||
void GOSTR3411_2012_CTX_Update (const uint8_t * buf, size_t len, GOSTR3411_2012_CTX * ctx);
|
||||
void GOSTR3411_2012_CTX_Finish (uint8_t * digest, GOSTR3411_2012_CTX * ctx);
|
||||
void GOSTR3411_2012_CTX_free (GOSTR3411_2012_CTX * ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
115
libi2pd/Gzip.cpp
Normal file
115
libi2pd/Gzip.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2017, 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 <inttypes.h>
|
||||
#include <string.h> /* memset */
|
||||
#include <iostream>
|
||||
#include "Log.h"
|
||||
#include "Gzip.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
const size_t GZIP_CHUNK_SIZE = 16384;
|
||||
|
||||
GzipInflator::GzipInflator (): m_IsDirty (false)
|
||||
{
|
||||
memset (&m_Inflator, 0, sizeof (m_Inflator));
|
||||
inflateInit2 (&m_Inflator, MAX_WBITS + 16); // gzip
|
||||
}
|
||||
|
||||
GzipInflator::~GzipInflator ()
|
||||
{
|
||||
inflateEnd (&m_Inflator);
|
||||
}
|
||||
|
||||
size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen)
|
||||
{
|
||||
if (m_IsDirty) inflateReset (&m_Inflator);
|
||||
m_IsDirty = true;
|
||||
m_Inflator.next_in = const_cast<uint8_t *>(in);
|
||||
m_Inflator.avail_in = inLen;
|
||||
m_Inflator.next_out = out;
|
||||
m_Inflator.avail_out = outLen;
|
||||
int err;
|
||||
if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END)
|
||||
return outLen - m_Inflator.avail_out;
|
||||
// else
|
||||
LogPrint (eLogError, "Gzip: Inflate error ", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& os)
|
||||
{
|
||||
m_IsDirty = true;
|
||||
uint8_t * out = new uint8_t[GZIP_CHUNK_SIZE];
|
||||
m_Inflator.next_in = const_cast<uint8_t *>(in);
|
||||
m_Inflator.avail_in = inLen;
|
||||
int ret;
|
||||
do
|
||||
{
|
||||
m_Inflator.next_out = out;
|
||||
m_Inflator.avail_out = GZIP_CHUNK_SIZE;
|
||||
ret = inflate (&m_Inflator, Z_NO_FLUSH);
|
||||
if (ret < 0)
|
||||
{
|
||||
inflateEnd (&m_Inflator);
|
||||
os.setstate(std::ios_base::failbit);
|
||||
break;
|
||||
}
|
||||
os.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out);
|
||||
}
|
||||
while (!m_Inflator.avail_out); // more data to read
|
||||
delete[] out;
|
||||
}
|
||||
|
||||
void GzipInflator::Inflate (std::istream& in, std::ostream& out)
|
||||
{
|
||||
uint8_t * buf = new uint8_t[GZIP_CHUNK_SIZE];
|
||||
while (!in.eof ())
|
||||
{
|
||||
in.read ((char *) buf, GZIP_CHUNK_SIZE);
|
||||
Inflate (buf, in.gcount (), out);
|
||||
}
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
GzipDeflator::GzipDeflator (): m_IsDirty (false)
|
||||
{
|
||||
memset (&m_Deflator, 0, sizeof (m_Deflator));
|
||||
deflateInit2 (&m_Deflator, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); // 15 + 16 sets gzip
|
||||
}
|
||||
|
||||
GzipDeflator::~GzipDeflator ()
|
||||
{
|
||||
deflateEnd (&m_Deflator);
|
||||
}
|
||||
|
||||
void GzipDeflator::SetCompressionLevel (int level)
|
||||
{
|
||||
deflateParams (&m_Deflator, level, Z_DEFAULT_STRATEGY);
|
||||
}
|
||||
|
||||
size_t GzipDeflator::Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen)
|
||||
{
|
||||
if (m_IsDirty) deflateReset (&m_Deflator);
|
||||
m_IsDirty = true;
|
||||
m_Deflator.next_in = const_cast<uint8_t *>(in);
|
||||
m_Deflator.avail_in = inLen;
|
||||
m_Deflator.next_out = out;
|
||||
m_Deflator.avail_out = outLen;
|
||||
int err;
|
||||
if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END)
|
||||
return outLen - m_Deflator.avail_out;
|
||||
// else
|
||||
LogPrint (eLogError, "Gzip: Deflate error ", err);
|
||||
return 0;
|
||||
}
|
||||
} // data
|
||||
} // i2p
|
44
libi2pd/Gzip.h
Normal file
44
libi2pd/Gzip.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef GZIP_H__
|
||||
#define GZIP_H__
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace i2p {
|
||||
namespace data {
|
||||
class GzipInflator
|
||||
{
|
||||
public:
|
||||
|
||||
GzipInflator ();
|
||||
~GzipInflator ();
|
||||
|
||||
size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen);
|
||||
/** @note @a os failbit will be set in case of error */
|
||||
void Inflate (const uint8_t * in, size_t inLen, std::ostream& os);
|
||||
void Inflate (std::istream& in, std::ostream& out);
|
||||
|
||||
private:
|
||||
|
||||
z_stream m_Inflator;
|
||||
bool m_IsDirty;
|
||||
};
|
||||
|
||||
class GzipDeflator
|
||||
{
|
||||
public:
|
||||
|
||||
GzipDeflator ();
|
||||
~GzipDeflator ();
|
||||
|
||||
void SetCompressionLevel (int level);
|
||||
size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen);
|
||||
|
||||
private:
|
||||
|
||||
z_stream m_Deflator;
|
||||
bool m_IsDirty;
|
||||
};
|
||||
} // data
|
||||
} // i2p
|
||||
|
||||
#endif /* GZIP_H__ */
|
482
libi2pd/HTTP.cpp
Normal file
482
libi2pd/HTTP.cpp
Normal file
|
@ -0,0 +1,482 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2017, 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 <algorithm>
|
||||
#include <utility>
|
||||
#include "util.h"
|
||||
#include "HTTP.h"
|
||||
#include <ctime>
|
||||
|
||||
namespace i2p {
|
||||
namespace http {
|
||||
const std::vector<std::string> HTTP_METHODS = {
|
||||
"GET", "HEAD", "POST", "PUT", "PATCH",
|
||||
"DELETE", "OPTIONS", "CONNECT"
|
||||
};
|
||||
const std::vector<std::string> HTTP_VERSIONS = {
|
||||
"HTTP/1.0", "HTTP/1.1"
|
||||
};
|
||||
|
||||
inline bool is_http_version(const std::string & str) {
|
||||
return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS);
|
||||
}
|
||||
|
||||
inline bool is_http_method(const std::string & str) {
|
||||
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
|
||||
}
|
||||
|
||||
void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
|
||||
std::size_t count = 0;
|
||||
std::stringstream ss(line);
|
||||
std::string token;
|
||||
while (1) {
|
||||
count++;
|
||||
if (limit > 0 && count >= limit)
|
||||
delim = '\n'; /* reset delimiter */
|
||||
if (!std::getline(ss, token, delim))
|
||||
break;
|
||||
tokens.push_back(token);
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<std::string, std::string> parse_header_line(const std::string& line)
|
||||
{
|
||||
std::size_t pos = 0;
|
||||
std::size_t len = 2; /* strlen(": ") */
|
||||
std::size_t max = line.length();
|
||||
if ((pos = line.find(": ", pos)) == std::string::npos)
|
||||
return std::make_pair("", "");
|
||||
while ((pos + len) < max && isspace(line.at(pos + len)))
|
||||
len++;
|
||||
return std::make_pair(line.substr(0, pos), line.substr(pos + len));
|
||||
}
|
||||
|
||||
void gen_rfc1123_date(std::string & out) {
|
||||
std::time_t now = std::time(nullptr);
|
||||
char buf[128];
|
||||
std::strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&now));
|
||||
out = buf;
|
||||
}
|
||||
|
||||
bool URL::parse(const char *str, std::size_t len) {
|
||||
std::string url(str, len ? len : strlen(str));
|
||||
return parse(url);
|
||||
}
|
||||
|
||||
bool URL::parse(const std::string& url) {
|
||||
std::size_t pos_p = 0; /* < current parse position */
|
||||
std::size_t pos_c = 0; /* < work position */
|
||||
if(url.at(0) != '/' || pos_p > 0) {
|
||||
std::size_t pos_s = 0;
|
||||
/* schema */
|
||||
pos_c = url.find("://");
|
||||
if (pos_c != std::string::npos) {
|
||||
schema = url.substr(0, pos_c);
|
||||
pos_p = pos_c + 3;
|
||||
}
|
||||
/* user[:pass] */
|
||||
pos_s = url.find('/', pos_p); /* find first slash */
|
||||
pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */
|
||||
if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) {
|
||||
std::size_t delim = url.find(':', pos_p);
|
||||
if (delim != std::string::npos && delim < pos_c) {
|
||||
user = url.substr(pos_p, delim - pos_p);
|
||||
delim += 1;
|
||||
pass = url.substr(delim, pos_c - delim);
|
||||
} else {
|
||||
user = url.substr(pos_p, pos_c - pos_p);
|
||||
}
|
||||
pos_p = pos_c + 1;
|
||||
}
|
||||
/* hostname[:port][/path] */
|
||||
pos_c = url.find_first_of(":/", pos_p);
|
||||
if (pos_c == std::string::npos) {
|
||||
/* only hostname, without post and path */
|
||||
host = url.substr(pos_p, std::string::npos);
|
||||
return true;
|
||||
} else if (url.at(pos_c) == ':') {
|
||||
host = url.substr(pos_p, pos_c - pos_p);
|
||||
/* port[/path] */
|
||||
pos_p = pos_c + 1;
|
||||
pos_c = url.find('/', pos_p);
|
||||
std::string port_str = (pos_c == std::string::npos)
|
||||
? url.substr(pos_p, std::string::npos)
|
||||
: url.substr(pos_p, pos_c - pos_p);
|
||||
/* stoi throws exception on failure, we don't need it */
|
||||
for (char c : port_str) {
|
||||
if (c < '0' || c > '9')
|
||||
return false;
|
||||
port *= 10;
|
||||
port += c - '0';
|
||||
}
|
||||
if (pos_c == std::string::npos)
|
||||
return true; /* no path part */
|
||||
pos_p = pos_c;
|
||||
} else {
|
||||
/* start of path part found */
|
||||
host = url.substr(pos_p, pos_c - pos_p);
|
||||
pos_p = pos_c;
|
||||
}
|
||||
}
|
||||
|
||||
/* pos_p now at start of path part */
|
||||
pos_c = url.find_first_of("?#", pos_p);
|
||||
if (pos_c == std::string::npos) {
|
||||
/* only path, without fragment and query */
|
||||
path = url.substr(pos_p, std::string::npos);
|
||||
return true;
|
||||
} else if (url.at(pos_c) == '?') {
|
||||
/* found query part */
|
||||
path = url.substr(pos_p, pos_c - pos_p);
|
||||
pos_p = pos_c + 1;
|
||||
pos_c = url.find('#', pos_p);
|
||||
if (pos_c == std::string::npos) {
|
||||
/* no fragment */
|
||||
query = url.substr(pos_p, std::string::npos);
|
||||
return true;
|
||||
} else {
|
||||
query = url.substr(pos_p, pos_c - pos_p);
|
||||
pos_p = pos_c + 1;
|
||||
}
|
||||
} else {
|
||||
/* found fragment part */
|
||||
path = url.substr(pos_p, pos_c - pos_p);
|
||||
pos_p = pos_c + 1;
|
||||
}
|
||||
|
||||
/* pos_p now at start of fragment part */
|
||||
frag = url.substr(pos_p, std::string::npos);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool URL::parse_query(std::map<std::string, std::string> & params) {
|
||||
std::vector<std::string> tokens;
|
||||
strsplit(query, tokens, '&');
|
||||
|
||||
params.clear();
|
||||
for (const auto& it : tokens) {
|
||||
std::size_t eq = it.find ('=');
|
||||
if (eq != std::string::npos) {
|
||||
auto e = std::pair<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1));
|
||||
params.insert(e);
|
||||
} else {
|
||||
auto e = std::pair<std::string, std::string>(it, "");
|
||||
params.insert(e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string URL::to_string() {
|
||||
std::string out = "";
|
||||
if (schema != "") {
|
||||
out = schema + "://";
|
||||
if (user != "" && pass != "") {
|
||||
out += user + ":" + pass + "@";
|
||||
} else if (user != "") {
|
||||
out += user + "@";
|
||||
}
|
||||
if (port) {
|
||||
out += host + ":" + std::to_string(port);
|
||||
} else {
|
||||
out += host;
|
||||
}
|
||||
}
|
||||
out += path;
|
||||
if (query != "")
|
||||
out += "?" + query;
|
||||
if (frag != "")
|
||||
out += "#" + frag;
|
||||
return out;
|
||||
}
|
||||
|
||||
void HTTPMsg::add_header(const char *name, std::string & value, bool replace) {
|
||||
add_header(name, value.c_str(), replace);
|
||||
}
|
||||
|
||||
void HTTPMsg::add_header(const char *name, const char *value, bool replace) {
|
||||
std::size_t count = headers.count(name);
|
||||
if (count && !replace)
|
||||
return;
|
||||
if (count) {
|
||||
headers[name] = value;
|
||||
return;
|
||||
}
|
||||
headers.insert(std::pair<std::string, std::string>(name, value));
|
||||
}
|
||||
|
||||
void HTTPMsg::del_header(const char *name) {
|
||||
headers.erase(name);
|
||||
}
|
||||
|
||||
int HTTPReq::parse(const char *buf, size_t len) {
|
||||
std::string str(buf, len);
|
||||
return parse(str);
|
||||
}
|
||||
|
||||
int HTTPReq::parse(const std::string& str) {
|
||||
enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
|
||||
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
|
||||
std::size_t eol = 0, pos = 0;
|
||||
URL url;
|
||||
|
||||
if (eoh == std::string::npos)
|
||||
return 0; /* str not contains complete request */
|
||||
|
||||
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
|
||||
if (expect == REQ_LINE) {
|
||||
std::string line = str.substr(pos, eol - pos);
|
||||
std::vector<std::string> tokens;
|
||||
strsplit(line, tokens, ' ');
|
||||
if (tokens.size() != 3)
|
||||
return -1;
|
||||
if (!is_http_method(tokens[0]))
|
||||
return -1;
|
||||
if (!is_http_version(tokens[2]))
|
||||
return -1;
|
||||
if (!url.parse(tokens[1]))
|
||||
return -1;
|
||||
/* all ok */
|
||||
method = tokens[0];
|
||||
uri = tokens[1];
|
||||
version = tokens[2];
|
||||
expect = HEADER_LINE;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string line = str.substr(pos, eol - pos);
|
||||
auto p = parse_header_line(line);
|
||||
if (p.first.length () > 0)
|
||||
headers.push_back (p);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
pos = eol + strlen(CRLF);
|
||||
if (pos >= eoh)
|
||||
break;
|
||||
}
|
||||
return eoh + strlen(HTTP_EOH);
|
||||
}
|
||||
|
||||
void HTTPReq::write(std::ostream & o)
|
||||
{
|
||||
o << method << " " << uri << " " << version << CRLF;
|
||||
for (auto & h : headers)
|
||||
o << h.first << ": " << h.second << CRLF;
|
||||
o << CRLF;
|
||||
}
|
||||
|
||||
std::string HTTPReq::to_string()
|
||||
{
|
||||
std::stringstream ss;
|
||||
write(ss);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void HTTPReq::AddHeader (const std::string& name, const std::string& value)
|
||||
{
|
||||
headers.push_back (std::make_pair(name, value));
|
||||
}
|
||||
|
||||
void HTTPReq::UpdateHeader (const std::string& name, const std::string& value)
|
||||
{
|
||||
for (auto& it : headers)
|
||||
if (it.first == name)
|
||||
{
|
||||
it.second = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPReq::RemoveHeader (const std::string& name)
|
||||
{
|
||||
for (auto it = headers.begin (); it != headers.end ();)
|
||||
{
|
||||
if (!it->first.compare(0, name.length (), name))
|
||||
it = headers.erase (it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
std::string HTTPReq::GetHeader (const std::string& name) const
|
||||
{
|
||||
for (auto& it : headers)
|
||||
if (it.first == name)
|
||||
return it.second;
|
||||
return "";
|
||||
}
|
||||
|
||||
bool HTTPRes::is_chunked() const
|
||||
{
|
||||
auto it = headers.find("Transfer-Encoding");
|
||||
if (it == headers.end())
|
||||
return false;
|
||||
if (it->second.find("chunked") == std::string::npos)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HTTPRes::is_gzipped(bool includingI2PGzip) const
|
||||
{
|
||||
auto it = headers.find("Content-Encoding");
|
||||
if (it == headers.end())
|
||||
return false; /* no header */
|
||||
if (it->second.find("gzip") != std::string::npos)
|
||||
return true; /* gotcha! */
|
||||
if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
long int HTTPMsg::content_length() const
|
||||
{
|
||||
unsigned long int length = 0;
|
||||
auto it = headers.find("Content-Length");
|
||||
if (it == headers.end())
|
||||
return -1;
|
||||
errno = 0;
|
||||
length = std::strtoul(it->second.c_str(), (char **) NULL, 10);
|
||||
if (errno != 0)
|
||||
return -1;
|
||||
return length;
|
||||
}
|
||||
|
||||
int HTTPRes::parse(const char *buf, size_t len) {
|
||||
std::string str(buf, len);
|
||||
return parse(str);
|
||||
}
|
||||
|
||||
int HTTPRes::parse(const std::string& str) {
|
||||
enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
|
||||
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
|
||||
std::size_t eol = 0, pos = 0;
|
||||
|
||||
if (eoh == std::string::npos)
|
||||
return 0; /* str not contains complete request */
|
||||
|
||||
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
|
||||
if (expect == RES_LINE) {
|
||||
std::string line = str.substr(pos, eol - pos);
|
||||
std::vector<std::string> tokens;
|
||||
strsplit(line, tokens, ' ', 3);
|
||||
if (tokens.size() != 3)
|
||||
return -1;
|
||||
if (!is_http_version(tokens[0]))
|
||||
return -1;
|
||||
code = atoi(tokens[1].c_str());
|
||||
if (code < 100 || code >= 600)
|
||||
return -1;
|
||||
/* all ok */
|
||||
version = tokens[0];
|
||||
status = tokens[2];
|
||||
expect = HEADER_LINE;
|
||||
} else {
|
||||
std::string line = str.substr(pos, eol - pos);
|
||||
auto p = parse_header_line(line);
|
||||
if (p.first.length () > 0)
|
||||
headers.insert (p);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
pos = eol + strlen(CRLF);
|
||||
if (pos >= eoh)
|
||||
break;
|
||||
}
|
||||
|
||||
return eoh + strlen(HTTP_EOH);
|
||||
}
|
||||
|
||||
std::string HTTPRes::to_string() {
|
||||
if (version == "HTTP/1.1" && headers.count("Date") == 0) {
|
||||
std::string date;
|
||||
gen_rfc1123_date(date);
|
||||
add_header("Date", date.c_str());
|
||||
}
|
||||
if (status == "OK" && code != 200)
|
||||
status = HTTPCodeToStatus(code); // update
|
||||
if (body.length() > 0 && headers.count("Content-Length") == 0)
|
||||
add_header("Content-Length", std::to_string(body.length()).c_str());
|
||||
/* build response */
|
||||
std::stringstream ss;
|
||||
ss << version << " " << code << " " << status << CRLF;
|
||||
for (auto & h : headers) {
|
||||
ss << h.first << ": " << h.second << CRLF;
|
||||
}
|
||||
ss << CRLF;
|
||||
if (body.length() > 0)
|
||||
ss << body;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
const char * HTTPCodeToStatus(int code) {
|
||||
const char *ptr;
|
||||
switch (code) {
|
||||
case 105: ptr = "Name Not Resolved"; break;
|
||||
/* success */
|
||||
case 200: ptr = "OK"; break;
|
||||
case 206: ptr = "Partial Content"; break;
|
||||
/* redirect */
|
||||
case 301: ptr = "Moved Permanently"; break;
|
||||
case 302: ptr = "Found"; break;
|
||||
case 304: ptr = "Not Modified"; break;
|
||||
case 307: ptr = "Temporary Redirect"; break;
|
||||
/* client error */
|
||||
case 400: ptr = "Bad Request"; break;
|
||||
case 401: ptr = "Unauthorized"; break;
|
||||
case 403: ptr = "Forbidden"; break;
|
||||
case 404: ptr = "Not Found"; break;
|
||||
case 407: ptr = "Proxy Authentication Required"; break;
|
||||
case 408: ptr = "Request Timeout"; break;
|
||||
/* server error */
|
||||
case 500: ptr = "Internal Server Error"; break;
|
||||
case 502: ptr = "Bad Gateway"; break;
|
||||
case 503: ptr = "Not Implemented"; break;
|
||||
case 504: ptr = "Gateway Timeout"; break;
|
||||
default: ptr = "Unknown Status"; break;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
std::string UrlDecode(const std::string& data, bool allow_null) {
|
||||
std::string decoded(data);
|
||||
size_t pos = 0;
|
||||
while ((pos = decoded.find('%', pos)) != std::string::npos) {
|
||||
char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
|
||||
if (c == '\0' && !allow_null) {
|
||||
pos += 3;
|
||||
continue;
|
||||
}
|
||||
decoded.replace(pos, 3, 1, c);
|
||||
pos++;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
bool MergeChunkedResponse (std::istream& in, std::ostream& out) {
|
||||
std::string hexLen;
|
||||
while (!in.eof ()) {
|
||||
std::getline (in, hexLen);
|
||||
errno = 0;
|
||||
long int len = strtoul(hexLen.c_str(), (char **) NULL, 16);
|
||||
if (errno != 0)
|
||||
return false; /* conversion error */
|
||||
if (len == 0)
|
||||
return true; /* end of stream */
|
||||
if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */
|
||||
return false; /* too large chunk */
|
||||
char * buf = new char[len];
|
||||
in.read (buf, len);
|
||||
out.write (buf, len);
|
||||
delete[] buf;
|
||||
std::getline (in, hexLen); // read \r\n after chunk
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // http
|
||||
} // i2p
|
163
libi2pd/HTTP.h
Normal file
163
libi2pd/HTTP.h
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016, 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 HTTP_H__
|
||||
#define HTTP_H__
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace i2p {
|
||||
namespace http {
|
||||
const char CRLF[] = "\r\n"; /**< HTTP line terminator */
|
||||
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 {
|
||||
std::string schema;
|
||||
std::string user;
|
||||
std::string pass;
|
||||
std::string host;
|
||||
unsigned short int port;
|
||||
std::string path;
|
||||
std::string query;
|
||||
std::string frag;
|
||||
|
||||
URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {};
|
||||
|
||||
/**
|
||||
* @brief Tries to parse url from string
|
||||
* @return true on success, false on invalid url
|
||||
*/
|
||||
bool parse (const char *str, std::size_t len = 0);
|
||||
bool parse (const std::string& url);
|
||||
|
||||
/**
|
||||
* @brief Parse query part of url to key/value map
|
||||
* @note Honestly, this should be implemented with std::multimap
|
||||
*/
|
||||
bool parse_query(std::map<std::string, std::string> & params);
|
||||
|
||||
/**
|
||||
* @brief Serialize URL structure to url
|
||||
* @note Returns relative url if schema if empty, absolute url otherwise
|
||||
*/
|
||||
std::string to_string ();
|
||||
};
|
||||
|
||||
struct HTTPMsg
|
||||
{
|
||||
std::map<std::string, std::string> headers;
|
||||
|
||||
void add_header(const char *name, std::string & value, bool replace = false);
|
||||
void add_header(const char *name, const char *value, bool replace = false);
|
||||
void del_header(const char *name);
|
||||
|
||||
/** @brief Returns declared message length or -1 if unknown */
|
||||
long int content_length() const;
|
||||
};
|
||||
|
||||
struct HTTPReq
|
||||
{
|
||||
std::list<std::pair<std::string, std::string> > headers;
|
||||
std::string version;
|
||||
std::string method;
|
||||
std::string uri;
|
||||
|
||||
HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {};
|
||||
|
||||
/**
|
||||
* @brief Tries to parse HTTP request from string
|
||||
* @return -1 on error, 0 on incomplete query, >0 on success
|
||||
* @note Positive return value is a size of header
|
||||
*/
|
||||
int parse(const char *buf, size_t len);
|
||||
int parse(const std::string& buf);
|
||||
|
||||
/** @brief Serialize HTTP request to string */
|
||||
std::string to_string();
|
||||
void write(std::ostream & o);
|
||||
|
||||
void AddHeader (const std::string& name, const std::string& value);
|
||||
void UpdateHeader (const std::string& name, const std::string& value);
|
||||
void RemoveHeader (const std::string& name);
|
||||
std::string GetHeader (const std::string& name) const;
|
||||
};
|
||||
|
||||
struct HTTPRes : HTTPMsg {
|
||||
std::string version;
|
||||
std::string status;
|
||||
unsigned short int code;
|
||||
/**
|
||||
* @brief Simplifies response generation
|
||||
*
|
||||
* If this variable is set, on @a to_string() call:
|
||||
* * Content-Length header will be added if missing,
|
||||
* * contents of @a body will be included in generated response
|
||||
*/
|
||||
std::string body;
|
||||
|
||||
HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {}
|
||||
|
||||
/**
|
||||
* @brief Tries to parse HTTP response from string
|
||||
* @return -1 on error, 0 on incomplete query, >0 on success
|
||||
* @note Positive return value is a size of header
|
||||
*/
|
||||
int parse(const char *buf, size_t len);
|
||||
int parse(const std::string& buf);
|
||||
|
||||
/**
|
||||
* @brief Serialize HTTP response to string
|
||||
* @note If @a version is set to HTTP/1.1, and Date header is missing,
|
||||
* it will be generated based on current time and added to headers
|
||||
* @note If @a body is set and Content-Length header is missing,
|
||||
* this header will be added, based on body's length
|
||||
*/
|
||||
std::string to_string();
|
||||
|
||||
void write(std::ostream & o);
|
||||
|
||||
/** @brief Checks that response declared as chunked data */
|
||||
bool is_chunked() const ;
|
||||
|
||||
/** @brief Checks that response contains compressed data */
|
||||
bool is_gzipped(bool includingI2PGzip = true) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief returns HTTP status string by integer code
|
||||
* @param code HTTP code [100, 599]
|
||||
* @return Immutable string with status
|
||||
*/
|
||||
const char * HTTPCodeToStatus(int code);
|
||||
|
||||
/**
|
||||
* @brief Replaces %-encoded characters in string with their values
|
||||
* @param data Source string
|
||||
* @param null If set to true - decode also %00 sequence, otherwise - skip
|
||||
* @return Decoded string
|
||||
*/
|
||||
std::string UrlDecode(const std::string& data, bool null = false);
|
||||
|
||||
/**
|
||||
* @brief Merge HTTP response content with Transfer-Encoding: chunked
|
||||
* @param in Input stream
|
||||
* @param out Output stream
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool MergeChunkedResponse (std::istream& in, std::ostream& out);
|
||||
} // http
|
||||
} // i2p
|
||||
|
||||
#endif /* HTTP_H__ */
|
669
libi2pd/I2NPProtocol.cpp
Normal file
669
libi2pd/I2NPProtocol.cpp
Normal file
|
@ -0,0 +1,669 @@
|
|||
#include <string.h>
|
||||
#include <atomic>
|
||||
#include "Base.h"
|
||||
#include "Log.h"
|
||||
#include "Crypto.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Timestamp.h"
|
||||
#include "RouterContext.h"
|
||||
#include "NetDb.h"
|
||||
#include "Tunnel.h"
|
||||
#include "Transports.h"
|
||||
#include "Garlic.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "version.h"
|
||||
|
||||
using namespace i2p::transport;
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
std::shared_ptr<I2NPMessage> NewI2NPMessage ()
|
||||
{
|
||||
return std::make_shared<I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE> >();
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> NewI2NPShortMessage ()
|
||||
{
|
||||
return std::make_shared<I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE> >();
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage ()
|
||||
{
|
||||
auto msg = new I2NPMessageBuffer<i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34>(); // reserved for alignment and NTCP 16 + 6 + 12
|
||||
msg->Align (12);
|
||||
return std::shared_ptr<I2NPMessage>(msg);
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> NewI2NPMessage (size_t len)
|
||||
{
|
||||
return (len < I2NP_MAX_SHORT_MESSAGE_SIZE/2) ? NewI2NPShortMessage () : NewI2NPMessage ();
|
||||
}
|
||||
|
||||
void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID)
|
||||
{
|
||||
SetTypeID (msgType);
|
||||
if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4);
|
||||
SetMsgID (replyMsgID);
|
||||
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
|
||||
UpdateSize ();
|
||||
UpdateChks ();
|
||||
}
|
||||
|
||||
void I2NPMessage::RenewI2NPMessageHeader ()
|
||||
{
|
||||
uint32_t msgID;
|
||||
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||
SetMsgID (msgID);
|
||||
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
|
||||
}
|
||||
|
||||
bool I2NPMessage::IsExpired () const
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
auto exp = GetExpiration ();
|
||||
return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID)
|
||||
{
|
||||
auto msg = NewI2NPMessage (len);
|
||||
if (msg->Concat (buf, len) < len)
|
||||
LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length ", msg->maxLen);
|
||||
msg->FillI2NPMessageHeader (msgType, replyMsgID);
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||
{
|
||||
auto msg = NewI2NPMessage ();
|
||||
if (msg->offset + len < msg->maxLen)
|
||||
{
|
||||
memcpy (msg->GetBuffer (), buf, len);
|
||||
msg->len = msg->offset + len;
|
||||
msg->from = from;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length");
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (!msg) return nullptr;
|
||||
auto newMsg = NewI2NPMessage (msg->len);
|
||||
newMsg->offset = msg->offset;
|
||||
*newMsg = *msg;
|
||||
return newMsg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID)
|
||||
{
|
||||
auto m = NewI2NPShortMessage ();
|
||||
uint8_t * buf = m->GetPayload ();
|
||||
if (msgID)
|
||||
{
|
||||
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
|
||||
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, i2p::util::GetMillisecondsSinceEpoch ());
|
||||
}
|
||||
else // for SSU establishment
|
||||
{
|
||||
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
|
||||
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, i2p::context.GetNetID ());
|
||||
}
|
||||
m->len += DELIVERY_STATUS_SIZE;
|
||||
m->FillI2NPMessageHeader (eI2NPDeliveryStatus);
|
||||
return m;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
|
||||
uint32_t replyTunnelID, bool exploratory, std::set<i2p::data::IdentHash> * excludedPeers)
|
||||
{
|
||||
auto m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage ();
|
||||
uint8_t * buf = m->GetPayload ();
|
||||
memcpy (buf, key, 32); // key
|
||||
buf += 32;
|
||||
memcpy (buf, from, 32); // from
|
||||
buf += 32;
|
||||
uint8_t flag = exploratory ? DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP : DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP;
|
||||
if (replyTunnelID)
|
||||
{
|
||||
*buf = flag | DATABASE_LOOKUP_DELIVERY_FLAG; // set delivery flag
|
||||
htobe32buf (buf+1, replyTunnelID);
|
||||
buf += 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
*buf = flag; // flag
|
||||
buf++;
|
||||
}
|
||||
|
||||
if (excludedPeers)
|
||||
{
|
||||
int cnt = excludedPeers->size ();
|
||||
htobe16buf (buf, cnt);
|
||||
buf += 2;
|
||||
for (auto& it: *excludedPeers)
|
||||
{
|
||||
memcpy (buf, it, 32);
|
||||
buf += 32;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// nothing to exclude
|
||||
htobuf16 (buf, 0);
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
m->len += (buf - m->GetPayload ());
|
||||
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
|
||||
return m;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
|
||||
const std::set<i2p::data::IdentHash>& excludedFloodfills,
|
||||
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag)
|
||||
{
|
||||
int cnt = excludedFloodfills.size ();
|
||||
auto m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage ();
|
||||
uint8_t * buf = m->GetPayload ();
|
||||
memcpy (buf, dest, 32); // key
|
||||
buf += 32;
|
||||
memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW
|
||||
buf += 32;
|
||||
*buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags
|
||||
buf ++;
|
||||
htobe32buf (buf, replyTunnel->GetNextTunnelID ()); // reply tunnel ID
|
||||
buf += 4;
|
||||
|
||||
// excluded
|
||||
htobe16buf (buf, cnt);
|
||||
buf += 2;
|
||||
if (cnt > 0)
|
||||
{
|
||||
for (auto& it: excludedFloodfills)
|
||||
{
|
||||
memcpy (buf, it, 32);
|
||||
buf += 32;
|
||||
}
|
||||
}
|
||||
// encryption
|
||||
memcpy (buf, replyKey, 32);
|
||||
buf[32] = uint8_t( 1 ); // 1 tag
|
||||
memcpy (buf + 33, replyTag, 32);
|
||||
buf += 65;
|
||||
|
||||
m->len += (buf - m->GetPayload ());
|
||||
m->FillI2NPMessageHeader (eI2NPDatabaseLookup);
|
||||
return m;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident,
|
||||
std::vector<i2p::data::IdentHash> routers)
|
||||
{
|
||||
auto m = NewI2NPShortMessage ();
|
||||
uint8_t * buf = m->GetPayload ();
|
||||
size_t len = 0;
|
||||
memcpy (buf, ident, 32);
|
||||
len += 32;
|
||||
buf[len] = routers.size ();
|
||||
len++;
|
||||
for (const auto& it: routers)
|
||||
{
|
||||
memcpy (buf + len, it, 32);
|
||||
len += 32;
|
||||
}
|
||||
memcpy (buf + len, i2p::context.GetRouterInfo ().GetIdentHash (), 32);
|
||||
len += 32;
|
||||
m->len += len;
|
||||
m->FillI2NPMessageHeader (eI2NPDatabaseSearchReply);
|
||||
return m;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router, uint32_t replyToken)
|
||||
{
|
||||
if (!router) // we send own RouterInfo
|
||||
router = context.GetSharedRouterInfo ();
|
||||
|
||||
auto m = NewI2NPShortMessage ();
|
||||
uint8_t * payload = m->GetPayload ();
|
||||
|
||||
memcpy (payload + DATABASE_STORE_KEY_OFFSET, router->GetIdentHash (), 32);
|
||||
payload[DATABASE_STORE_TYPE_OFFSET] = 0; // RouterInfo
|
||||
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
|
||||
uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE;
|
||||
if (replyToken)
|
||||
{
|
||||
memset (buf, 0, 4); // zero tunnelID means direct reply
|
||||
buf += 4;
|
||||
memcpy (buf, router->GetIdentHash (), 32);
|
||||
buf += 32;
|
||||
}
|
||||
|
||||
uint8_t * sizePtr = buf;
|
||||
buf += 2;
|
||||
m->len += (buf - payload); // payload size
|
||||
i2p::data::GzipDeflator deflator;
|
||||
size_t size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len);
|
||||
if (size)
|
||||
{
|
||||
htobe16buf (sizePtr, size); // size
|
||||
m->len += size;
|
||||
}
|
||||
else
|
||||
m = nullptr;
|
||||
if (m)
|
||||
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
|
||||
return m;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
|
||||
{
|
||||
if (!leaseSet) return nullptr;
|
||||
auto m = NewI2NPShortMessage ();
|
||||
uint8_t * payload = m->GetPayload ();
|
||||
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
|
||||
payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet
|
||||
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0);
|
||||
size_t size = DATABASE_STORE_HEADER_SIZE;
|
||||
memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ());
|
||||
size += leaseSet->GetBufferLen ();
|
||||
m->len += size;
|
||||
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
|
||||
return m;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LocalLeaseSet> leaseSet, uint32_t replyToken, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
|
||||
{
|
||||
if (!leaseSet) return nullptr;
|
||||
auto m = NewI2NPShortMessage ();
|
||||
uint8_t * payload = m->GetPayload ();
|
||||
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
|
||||
payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet
|
||||
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken);
|
||||
size_t size = DATABASE_STORE_HEADER_SIZE;
|
||||
if (replyToken && replyTunnel)
|
||||
{
|
||||
if (replyTunnel)
|
||||
{
|
||||
htobe32buf (payload + size, replyTunnel->GetNextTunnelID ());
|
||||
size += 4; // reply tunnelID
|
||||
memcpy (payload + size, replyTunnel->GetNextIdentHash (), 32);
|
||||
size += 32; // reply tunnel gateway
|
||||
}
|
||||
else
|
||||
htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0);
|
||||
}
|
||||
memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ());
|
||||
size += leaseSet->GetBufferLen ();
|
||||
m->len += size;
|
||||
m->FillI2NPMessageHeader (eI2NPDatabaseStore);
|
||||
return m;
|
||||
}
|
||||
|
||||
bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (!msg || msg->GetTypeID () != eI2NPDatabaseStore) return false;
|
||||
return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo
|
||||
}
|
||||
|
||||
static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO:
|
||||
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels)
|
||||
{
|
||||
if (maxNumTransitTunnels > 0 && maxNumTransitTunnels <= 10000 && g_MaxNumTransitTunnels != maxNumTransitTunnels)
|
||||
{
|
||||
LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels);
|
||||
g_MaxNumTransitTunnels = maxNumTransitTunnels;
|
||||
}
|
||||
}
|
||||
|
||||
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
|
||||
{
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
|
||||
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
|
||||
{
|
||||
LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx);
|
||||
BN_CTX_free (ctx);
|
||||
// replace record to reply
|
||||
if (i2p::context.AcceptsTunnels () &&
|
||||
i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels &&
|
||||
!i2p::transport::transports.IsBandwidthExceeded ())
|
||||
{
|
||||
auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
|
||||
bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
|
||||
clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||
bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
|
||||
clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
|
||||
clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
|
||||
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80,
|
||||
clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40);
|
||||
i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel);
|
||||
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0;
|
||||
}
|
||||
else
|
||||
record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30)
|
||||
|
||||
//TODO: fill filler
|
||||
SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret
|
||||
record + BUILD_RESPONSE_RECORD_HASH_OFFSET);
|
||||
// encrypt reply
|
||||
i2p::crypto::CBCEncryption encryption;
|
||||
for (int j = 0; j < num; j++)
|
||||
{
|
||||
encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
|
||||
encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET);
|
||||
uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
|
||||
encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
|
||||
{
|
||||
int num = buf[0];
|
||||
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records");
|
||||
if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1)
|
||||
{
|
||||
LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len);
|
||||
return;
|
||||
}
|
||||
|
||||
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
|
||||
if (tunnel)
|
||||
{
|
||||
// endpoint of inbound tunnel
|
||||
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
|
||||
if (tunnel->HandleTunnelBuildResponse (buf, len))
|
||||
{
|
||||
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
|
||||
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
|
||||
if (HandleBuildRequestRecords (num, buf + 1, clearText))
|
||||
{
|
||||
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel
|
||||
{
|
||||
// so we send it to reply tunnel
|
||||
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||
CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
|
||||
eI2NPVariableTunnelBuildReply, buf, len,
|
||||
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||
}
|
||||
else
|
||||
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||
CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
|
||||
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
|
||||
{
|
||||
if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE)
|
||||
{
|
||||
LogPrint (eLogError, "TunnelBuild message is too short ", len);
|
||||
return;
|
||||
}
|
||||
uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
|
||||
if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText))
|
||||
{
|
||||
if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel
|
||||
{
|
||||
// so we send it to reply tunnel
|
||||
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||
CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
|
||||
eI2NPTunnelBuildReply, buf, len,
|
||||
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||
}
|
||||
else
|
||||
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
|
||||
CreateI2NPMessage (eI2NPTunnelBuild, buf, len,
|
||||
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
|
||||
}
|
||||
}
|
||||
|
||||
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
|
||||
{
|
||||
int num = buf[0];
|
||||
LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID);
|
||||
if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1)
|
||||
{
|
||||
LogPrint (eLogError, "VaribleTunnelBuildReply message of ", num, " records is too short ", len);
|
||||
return;
|
||||
}
|
||||
|
||||
auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
|
||||
if (tunnel)
|
||||
{
|
||||
// reply for outbound tunnel
|
||||
if (tunnel->HandleTunnelBuildResponse (buf, len))
|
||||
{
|
||||
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
|
||||
i2p::tunnel::tunnels.AddOutboundTunnel (tunnel);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
|
||||
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found");
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf)
|
||||
{
|
||||
auto msg = NewI2NPTunnelMessage ();
|
||||
msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE);
|
||||
msg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload)
|
||||
{
|
||||
auto msg = NewI2NPTunnelMessage ();
|
||||
htobe32buf (msg->GetPayload (), tunnelID);
|
||||
msg->len += 4; // tunnelID
|
||||
msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4);
|
||||
msg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ()
|
||||
{
|
||||
auto msg = NewI2NPTunnelMessage ();
|
||||
msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE;
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len)
|
||||
{
|
||||
auto msg = NewI2NPMessage (len);
|
||||
uint8_t * payload = msg->GetPayload ();
|
||||
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
|
||||
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
|
||||
msg->len += TUNNEL_GATEWAY_HEADER_SIZE;
|
||||
if (msg->Concat (buf, len) < len)
|
||||
LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen);
|
||||
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (msg->offset >= I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE)
|
||||
{
|
||||
// message is capable to be used without copying
|
||||
uint8_t * payload = msg->GetBuffer () - TUNNEL_GATEWAY_HEADER_SIZE;
|
||||
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
|
||||
int len = msg->GetLength ();
|
||||
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
|
||||
msg->offset -= (I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE);
|
||||
msg->len = msg->offset + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE +len;
|
||||
msg->FillI2NPMessageHeader (eI2NPTunnelGateway);
|
||||
return msg;
|
||||
}
|
||||
else
|
||||
return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
|
||||
const uint8_t * buf, size_t len, uint32_t replyMsgID)
|
||||
{
|
||||
auto msg = NewI2NPMessage (len);
|
||||
size_t gatewayMsgOffset = I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
|
||||
msg->offset += gatewayMsgOffset;
|
||||
msg->len += gatewayMsgOffset;
|
||||
if (msg->Concat (buf, len) < len)
|
||||
LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen);
|
||||
msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message
|
||||
len = msg->GetLength ();
|
||||
msg->offset -= gatewayMsgOffset;
|
||||
uint8_t * payload = msg->GetPayload ();
|
||||
htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID);
|
||||
htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len);
|
||||
msg->FillI2NPMessageHeader (eI2NPTunnelGateway); // gateway message
|
||||
return msg;
|
||||
}
|
||||
|
||||
size_t GetI2NPMessageLength (const uint8_t * msg)
|
||||
{
|
||||
return bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE;
|
||||
}
|
||||
|
||||
void HandleI2NPMessage (uint8_t * msg, size_t len)
|
||||
{
|
||||
uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET];
|
||||
uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET);
|
||||
LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
|
||||
uint8_t * buf = msg + I2NP_HEADER_SIZE;
|
||||
int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET);
|
||||
switch (typeID)
|
||||
{
|
||||
case eI2NPVariableTunnelBuild:
|
||||
HandleVariableTunnelBuildMsg (msgID, buf, size);
|
||||
break;
|
||||
case eI2NPVariableTunnelBuildReply:
|
||||
HandleVariableTunnelBuildReplyMsg (msgID, buf, size);
|
||||
break;
|
||||
case eI2NPTunnelBuild:
|
||||
HandleTunnelBuildMsg (buf, size);
|
||||
break;
|
||||
case eI2NPTunnelBuildReply:
|
||||
// TODO:
|
||||
break;
|
||||
default:
|
||||
LogPrint (eLogWarning, "I2NP: Unexpected message ", (int)typeID);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
uint8_t typeID = msg->GetTypeID ();
|
||||
LogPrint (eLogDebug, "I2NP: Handling message with type ", (int)typeID);
|
||||
switch (typeID)
|
||||
{
|
||||
case eI2NPTunnelData:
|
||||
i2p::tunnel::tunnels.PostTunnelData (msg);
|
||||
break;
|
||||
case eI2NPTunnelGateway:
|
||||
i2p::tunnel::tunnels.PostTunnelData (msg);
|
||||
break;
|
||||
case eI2NPGarlic:
|
||||
{
|
||||
if (msg->from)
|
||||
{
|
||||
if (msg->from->GetTunnelPool ())
|
||||
msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg);
|
||||
else
|
||||
LogPrint (eLogInfo, "I2NP: Local destination for garlic doesn't exist anymore");
|
||||
}
|
||||
else
|
||||
i2p::context.ProcessGarlicMessage (msg);
|
||||
break;
|
||||
}
|
||||
case eI2NPDatabaseStore:
|
||||
case eI2NPDatabaseSearchReply:
|
||||
case eI2NPDatabaseLookup:
|
||||
// forward to netDb
|
||||
i2p::data::netdb.PostI2NPMsg (msg);
|
||||
break;
|
||||
case eI2NPDeliveryStatus:
|
||||
{
|
||||
if (msg->from && msg->from->GetTunnelPool ())
|
||||
msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg);
|
||||
else
|
||||
i2p::context.ProcessDeliveryStatusMessage (msg);
|
||||
break;
|
||||
}
|
||||
case eI2NPVariableTunnelBuild:
|
||||
case eI2NPVariableTunnelBuildReply:
|
||||
case eI2NPTunnelBuild:
|
||||
case eI2NPTunnelBuildReply:
|
||||
// forward to tunnel thread
|
||||
i2p::tunnel::tunnels.PostTunnelData (msg);
|
||||
break;
|
||||
default:
|
||||
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
I2NPMessagesHandler::~I2NPMessagesHandler ()
|
||||
{
|
||||
Flush ();
|
||||
}
|
||||
|
||||
void I2NPMessagesHandler::PutNextMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
switch (msg->GetTypeID ())
|
||||
{
|
||||
case eI2NPTunnelData:
|
||||
m_TunnelMsgs.push_back (msg);
|
||||
break;
|
||||
case eI2NPTunnelGateway:
|
||||
m_TunnelGatewayMsgs.push_back (msg);
|
||||
break;
|
||||
default:
|
||||
HandleI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2NPMessagesHandler::Flush ()
|
||||
{
|
||||
if (!m_TunnelMsgs.empty ())
|
||||
{
|
||||
i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
|
||||
m_TunnelMsgs.clear ();
|
||||
}
|
||||
if (!m_TunnelGatewayMsgs.empty ())
|
||||
{
|
||||
i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
|
||||
m_TunnelGatewayMsgs.clear ();
|
||||
}
|
||||
}
|
||||
}
|
267
libi2pd/I2NPProtocol.h
Normal file
267
libi2pd/I2NPProtocol.h
Normal file
|
@ -0,0 +1,267 @@
|
|||
#ifndef I2NP_PROTOCOL_H__
|
||||
#define I2NP_PROTOCOL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include "Crypto.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "LeaseSet.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
// I2NP header
|
||||
const size_t I2NP_HEADER_TYPEID_OFFSET = 0;
|
||||
const size_t I2NP_HEADER_MSGID_OFFSET = I2NP_HEADER_TYPEID_OFFSET + 1;
|
||||
const size_t I2NP_HEADER_EXPIRATION_OFFSET = I2NP_HEADER_MSGID_OFFSET + 4;
|
||||
const size_t I2NP_HEADER_SIZE_OFFSET = I2NP_HEADER_EXPIRATION_OFFSET + 8;
|
||||
const size_t I2NP_HEADER_CHKS_OFFSET = I2NP_HEADER_SIZE_OFFSET + 2;
|
||||
const size_t I2NP_HEADER_SIZE = I2NP_HEADER_CHKS_OFFSET + 1;
|
||||
|
||||
// I2NP short header
|
||||
const size_t I2NP_SHORT_HEADER_TYPEID_OFFSET = 0;
|
||||
const size_t I2NP_SHORT_HEADER_EXPIRATION_OFFSET = I2NP_SHORT_HEADER_TYPEID_OFFSET + 1;
|
||||
const size_t I2NP_SHORT_HEADER_SIZE = I2NP_SHORT_HEADER_EXPIRATION_OFFSET + 4;
|
||||
|
||||
// Tunnel Gateway header
|
||||
const size_t TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET = 0;
|
||||
const size_t TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET = TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET + 4;
|
||||
const size_t TUNNEL_GATEWAY_HEADER_SIZE = TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET + 2;
|
||||
|
||||
// DeliveryStatus
|
||||
const size_t DELIVERY_STATUS_MSGID_OFFSET = 0;
|
||||
const size_t DELIVERY_STATUS_TIMESTAMP_OFFSET = DELIVERY_STATUS_MSGID_OFFSET + 4;
|
||||
const size_t DELIVERY_STATUS_SIZE = DELIVERY_STATUS_TIMESTAMP_OFFSET + 8;
|
||||
|
||||
// DatabaseStore
|
||||
const size_t DATABASE_STORE_KEY_OFFSET = 0;
|
||||
const size_t DATABASE_STORE_TYPE_OFFSET = DATABASE_STORE_KEY_OFFSET + 32;
|
||||
const size_t DATABASE_STORE_REPLY_TOKEN_OFFSET = DATABASE_STORE_TYPE_OFFSET + 1;
|
||||
const size_t DATABASE_STORE_HEADER_SIZE = DATABASE_STORE_REPLY_TOKEN_OFFSET + 4;
|
||||
|
||||
// TunnelBuild
|
||||
const size_t TUNNEL_BUILD_RECORD_SIZE = 528;
|
||||
|
||||
//BuildRequestRecordClearText
|
||||
const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0;
|
||||
const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4;
|
||||
const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32;
|
||||
const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4;
|
||||
const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32;
|
||||
const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32;
|
||||
const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32;
|
||||
const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32;
|
||||
const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16;
|
||||
const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1;
|
||||
const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4;
|
||||
const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4;
|
||||
const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222;
|
||||
|
||||
// BuildRequestRecordEncrypted
|
||||
const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0;
|
||||
const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16;
|
||||
|
||||
// BuildResponseRecord
|
||||
const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0;
|
||||
const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32;
|
||||
const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495;
|
||||
const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE;
|
||||
|
||||
enum I2NPMessageType
|
||||
{
|
||||
eI2NPDatabaseStore = 1,
|
||||
eI2NPDatabaseLookup = 2,
|
||||
eI2NPDatabaseSearchReply = 3,
|
||||
eI2NPDeliveryStatus = 10,
|
||||
eI2NPGarlic = 11,
|
||||
eI2NPTunnelData = 18,
|
||||
eI2NPTunnelGateway = 19,
|
||||
eI2NPData = 20,
|
||||
eI2NPTunnelBuild = 21,
|
||||
eI2NPTunnelBuildReply = 22,
|
||||
eI2NPVariableTunnelBuild = 23,
|
||||
eI2NPVariableTunnelBuildReply = 24
|
||||
};
|
||||
|
||||
const int NUM_TUNNEL_BUILD_RECORDS = 8;
|
||||
|
||||
// DatabaseLookup flags
|
||||
const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01;
|
||||
const uint8_t DATABASE_LOOKUP_ENCRYPTION_FLAG = 0x02;
|
||||
const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C;
|
||||
const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0;
|
||||
const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100
|
||||
const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000
|
||||
const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100
|
||||
|
||||
namespace tunnel
|
||||
{
|
||||
class InboundTunnel;
|
||||
class TunnelPool;
|
||||
}
|
||||
|
||||
const size_t I2NP_MAX_MESSAGE_SIZE = 32768;
|
||||
const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096;
|
||||
const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT)
|
||||
const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds
|
||||
|
||||
struct I2NPMessage
|
||||
{
|
||||
uint8_t * buf;
|
||||
size_t len, offset, maxLen;
|
||||
std::shared_ptr<i2p::tunnel::InboundTunnel> from;
|
||||
|
||||
I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2),
|
||||
offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header
|
||||
|
||||
// header accessors
|
||||
uint8_t * GetHeader () { return GetBuffer (); };
|
||||
const uint8_t * GetHeader () const { return GetBuffer (); };
|
||||
void SetTypeID (uint8_t typeID) { GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = typeID; };
|
||||
uint8_t GetTypeID () const { return GetHeader ()[I2NP_HEADER_TYPEID_OFFSET]; };
|
||||
void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + I2NP_HEADER_MSGID_OFFSET, msgID); };
|
||||
uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + I2NP_HEADER_MSGID_OFFSET); };
|
||||
void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET, expiration); };
|
||||
uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET); };
|
||||
void SetSize (uint16_t size) { htobe16buf (GetHeader () + I2NP_HEADER_SIZE_OFFSET, size); };
|
||||
uint16_t GetSize () const { return bufbe16toh (GetHeader () + I2NP_HEADER_SIZE_OFFSET); };
|
||||
void UpdateSize () { SetSize (GetPayloadLength ()); };
|
||||
void SetChks (uint8_t chks) { GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = chks; };
|
||||
void UpdateChks ()
|
||||
{
|
||||
uint8_t hash[32];
|
||||
SHA256(GetPayload (), GetPayloadLength (), hash);
|
||||
GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = hash[0];
|
||||
}
|
||||
|
||||
// payload
|
||||
uint8_t * GetPayload () { return GetBuffer () + I2NP_HEADER_SIZE; };
|
||||
const uint8_t * GetPayload () const { return GetBuffer () + I2NP_HEADER_SIZE; };
|
||||
uint8_t * GetBuffer () { return buf + offset; };
|
||||
const uint8_t * GetBuffer () const { return buf + offset; };
|
||||
size_t GetLength () const { return len - offset; };
|
||||
size_t GetPayloadLength () const { return GetLength () - I2NP_HEADER_SIZE; };
|
||||
|
||||
void Align (size_t alignment)
|
||||
{
|
||||
if (len + alignment > maxLen) return;
|
||||
size_t rem = ((size_t)GetBuffer ()) % alignment;
|
||||
if (rem)
|
||||
{
|
||||
offset += (alignment - rem);
|
||||
len += (alignment - rem);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Concat (const uint8_t * buf1, size_t len1)
|
||||
{
|
||||
// make sure with don't write beyond maxLen
|
||||
if (len + len1 > maxLen) len1 = maxLen - len;
|
||||
memcpy (buf + len, buf1, len1);
|
||||
len += len1;
|
||||
return len1;
|
||||
}
|
||||
|
||||
I2NPMessage& operator=(const I2NPMessage& other)
|
||||
{
|
||||
memcpy (buf + offset, other.buf + other.offset, other.GetLength ());
|
||||
len = offset + other.GetLength ();
|
||||
from = other.from;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// for SSU only
|
||||
uint8_t * GetSSUHeader () { return buf + offset + I2NP_HEADER_SIZE - I2NP_SHORT_HEADER_SIZE; };
|
||||
void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular
|
||||
{
|
||||
const uint8_t * ssu = GetSSUHeader ();
|
||||
GetHeader ()[I2NP_HEADER_TYPEID_OFFSET] = ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET]; // typeid
|
||||
SetMsgID (msgID);
|
||||
SetExpiration (bufbe32toh (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET)*1000LL);
|
||||
SetSize (len - offset - I2NP_HEADER_SIZE);
|
||||
SetChks (0);
|
||||
}
|
||||
uint32_t ToSSU () // return msgID
|
||||
{
|
||||
uint8_t header[I2NP_HEADER_SIZE];
|
||||
memcpy (header, GetHeader (), I2NP_HEADER_SIZE);
|
||||
uint8_t * ssu = GetSSUHeader ();
|
||||
ssu[I2NP_SHORT_HEADER_TYPEID_OFFSET] = header[I2NP_HEADER_TYPEID_OFFSET]; // typeid
|
||||
htobe32buf (ssu + I2NP_SHORT_HEADER_EXPIRATION_OFFSET, bufbe64toh (header + I2NP_HEADER_EXPIRATION_OFFSET)/1000LL);
|
||||
len = offset + I2NP_SHORT_HEADER_SIZE + bufbe16toh (header + I2NP_HEADER_SIZE_OFFSET);
|
||||
return bufbe32toh (header + I2NP_HEADER_MSGID_OFFSET);
|
||||
}
|
||||
|
||||
void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0);
|
||||
void RenewI2NPMessageHeader ();
|
||||
bool IsExpired () const;
|
||||
};
|
||||
|
||||
template<int sz>
|
||||
struct I2NPMessageBuffer: public I2NPMessage
|
||||
{
|
||||
I2NPMessageBuffer () { buf = m_Buffer; maxLen = sz; };
|
||||
uint8_t m_Buffer[sz + 32]; // 16 alignment + 16 padding
|
||||
};
|
||||
|
||||
std::shared_ptr<I2NPMessage> NewI2NPMessage ();
|
||||
std::shared_ptr<I2NPMessage> NewI2NPShortMessage ();
|
||||
std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage ();
|
||||
std::shared_ptr<I2NPMessage> NewI2NPMessage (size_t len);
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
|
||||
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from = nullptr);
|
||||
std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID);
|
||||
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
|
||||
uint32_t replyTunnelID, bool exploratory = false, std::set<i2p::data::IdentHash> * excludedPeers = nullptr);
|
||||
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
|
||||
const std::set<i2p::data::IdentHash>& excludedFloodfills,
|
||||
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag);
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector<i2p::data::IdentHash> routers);
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, uint32_t replyToken = 0);
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LeaseSet> leaseSet); // for floodfill only
|
||||
std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LocalLeaseSet> leaseSet, uint32_t replyToken = 0, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel = nullptr);
|
||||
bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText);
|
||||
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
|
||||
void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len);
|
||||
void HandleTunnelBuildMsg (uint8_t * buf, size_t len);
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf);
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload);
|
||||
std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg ();
|
||||
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len);
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
|
||||
const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
|
||||
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
size_t GetI2NPMessageLength (const uint8_t * msg);
|
||||
void HandleI2NPMessage (uint8_t * msg, size_t len);
|
||||
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
class I2NPMessagesHandler
|
||||
{
|
||||
public:
|
||||
|
||||
~I2NPMessagesHandler ();
|
||||
void PutNextMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
void Flush ();
|
||||
|
||||
private:
|
||||
|
||||
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
|
||||
};
|
||||
|
||||
const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500;
|
||||
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels);
|
||||
}
|
||||
|
||||
#endif
|
83
libi2pd/I2PEndian.cpp
Normal file
83
libi2pd/I2PEndian.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include "I2PEndian.h"
|
||||
|
||||
// http://habrahabr.ru/post/121811/
|
||||
// http://codepad.org/2ycmkz2y
|
||||
|
||||
#include "LittleBigEndian.h"
|
||||
|
||||
#ifdef NEEDS_LOCAL_ENDIAN
|
||||
uint16_t htobe16(uint16_t int16)
|
||||
{
|
||||
BigEndian<uint16_t> u16(int16);
|
||||
return u16.raw_value;
|
||||
}
|
||||
|
||||
uint32_t htobe32(uint32_t int32)
|
||||
{
|
||||
BigEndian<uint32_t> u32(int32);
|
||||
return u32.raw_value;
|
||||
}
|
||||
|
||||
uint64_t htobe64(uint64_t int64)
|
||||
{
|
||||
BigEndian<uint64_t> u64(int64);
|
||||
return u64.raw_value;
|
||||
}
|
||||
|
||||
uint16_t be16toh(uint16_t big16)
|
||||
{
|
||||
LittleEndian<uint16_t> u16(big16);
|
||||
return u16.raw_value;
|
||||
}
|
||||
|
||||
uint32_t be32toh(uint32_t big32)
|
||||
{
|
||||
LittleEndian<uint32_t> u32(big32);
|
||||
return u32.raw_value;
|
||||
}
|
||||
|
||||
uint64_t be64toh(uint64_t big64)
|
||||
{
|
||||
LittleEndian<uint64_t> u64(big64);
|
||||
return u64.raw_value;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* it can be used in Windows 8
|
||||
#include <Winsock2.h>
|
||||
|
||||
uint16_t htobe16(uint16_t int16)
|
||||
{
|
||||
return htons(int16);
|
||||
}
|
||||
|
||||
uint32_t htobe32(uint32_t int32)
|
||||
{
|
||||
return htonl(int32);
|
||||
}
|
||||
|
||||
uint64_t htobe64(uint64_t int64)
|
||||
{
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
|
||||
//return htonll(int64);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
uint16_t be16toh(uint16_t big16)
|
||||
{
|
||||
return ntohs(big16);
|
||||
}
|
||||
|
||||
uint32_t be32toh(uint32_t big32)
|
||||
{
|
||||
return ntohl(big32);
|
||||
}
|
||||
|
||||
uint64_t be64toh(uint64_t big64)
|
||||
{
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx
|
||||
//return ntohll(big64);
|
||||
return 0;
|
||||
}
|
||||
*/
|
119
libi2pd/I2PEndian.h
Normal file
119
libi2pd/I2PEndian.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
#ifndef I2PENDIAN_H__
|
||||
#define I2PENDIAN_H__
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__)
|
||||
#include <endian.h>
|
||||
#elif __FreeBSD__
|
||||
#include <sys/endian.h>
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
|
||||
#include <libkern/OSByteOrder.h>
|
||||
|
||||
#define htobe16(x) OSSwapHostToBigInt16(x)
|
||||
#define htole16(x) OSSwapHostToLittleInt16(x)
|
||||
#define be16toh(x) OSSwapBigToHostInt16(x)
|
||||
#define le16toh(x) OSSwapLittleToHostInt16(x)
|
||||
|
||||
#define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
#define htole32(x) OSSwapHostToLittleInt32(x)
|
||||
#define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
#define le32toh(x) OSSwapLittleToHostInt32(x)
|
||||
|
||||
#define htobe64(x) OSSwapHostToBigInt64(x)
|
||||
#define htole64(x) OSSwapHostToLittleInt64(x)
|
||||
#define be64toh(x) OSSwapBigToHostInt64(x)
|
||||
#define le64toh(x) OSSwapLittleToHostInt64(x)
|
||||
|
||||
#else
|
||||
#define NEEDS_LOCAL_ENDIAN
|
||||
#include <cstdint>
|
||||
uint16_t htobe16(uint16_t int16);
|
||||
uint32_t htobe32(uint32_t int32);
|
||||
uint64_t htobe64(uint64_t int64);
|
||||
|
||||
uint16_t be16toh(uint16_t big16);
|
||||
uint32_t be32toh(uint32_t big32);
|
||||
uint64_t be64toh(uint64_t big64);
|
||||
|
||||
// assume LittleEndine
|
||||
#define htole16
|
||||
#define htole32
|
||||
#define htole64
|
||||
#define le16toh
|
||||
#define le32toh
|
||||
#define le64toh
|
||||
|
||||
#endif
|
||||
|
||||
inline uint16_t buf16toh(const void *buf)
|
||||
{
|
||||
uint16_t b16;
|
||||
memcpy(&b16, buf, sizeof(uint16_t));
|
||||
return b16;
|
||||
}
|
||||
|
||||
inline uint32_t buf32toh(const void *buf)
|
||||
{
|
||||
uint32_t b32;
|
||||
memcpy(&b32, buf, sizeof(uint32_t));
|
||||
return b32;
|
||||
}
|
||||
|
||||
inline uint64_t buf64toh(const void *buf)
|
||||
{
|
||||
uint64_t b64;
|
||||
memcpy(&b64, buf, sizeof(uint64_t));
|
||||
return b64;
|
||||
}
|
||||
|
||||
inline uint16_t bufbe16toh(const void *buf)
|
||||
{
|
||||
return be16toh(buf16toh(buf));
|
||||
}
|
||||
|
||||
inline uint32_t bufbe32toh(const void *buf)
|
||||
{
|
||||
return be32toh(buf32toh(buf));
|
||||
}
|
||||
|
||||
inline uint64_t bufbe64toh(const void *buf)
|
||||
{
|
||||
return be64toh(buf64toh(buf));
|
||||
}
|
||||
|
||||
inline void htobuf16(void *buf, uint16_t b16)
|
||||
{
|
||||
memcpy(buf, &b16, sizeof(uint16_t));
|
||||
}
|
||||
|
||||
inline void htobuf32(void *buf, uint32_t b32)
|
||||
{
|
||||
memcpy(buf, &b32, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
inline void htobuf64(void *buf, uint64_t b64)
|
||||
{
|
||||
memcpy(buf, &b64, sizeof(uint64_t));
|
||||
}
|
||||
|
||||
inline void htobe16buf(void *buf, uint16_t big16)
|
||||
{
|
||||
htobuf16(buf, htobe16(big16));
|
||||
}
|
||||
|
||||
inline void htobe32buf(void *buf, uint32_t big32)
|
||||
{
|
||||
htobuf32(buf, htobe32(big32));
|
||||
}
|
||||
|
||||
inline void htobe64buf(void *buf, uint64_t big64)
|
||||
{
|
||||
htobuf64(buf, htobe64(big64));
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // I2PENDIAN_H__
|
||||
|
666
libi2pd/Identity.cpp
Normal file
666
libi2pd/Identity.cpp
Normal file
|
@ -0,0 +1,666 @@
|
|||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include "Crypto.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Log.h"
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
Identity& Identity::operator=(const Keys& keys)
|
||||
{
|
||||
// copy public and signing keys together
|
||||
memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey));
|
||||
memset (certificate, 0, sizeof (certificate));
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
|
||||
{
|
||||
if ( len < DEFAULT_IDENTITY_SIZE ) {
|
||||
// buffer too small, don't overflow
|
||||
return 0;
|
||||
}
|
||||
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
|
||||
return DEFAULT_IDENTITY_SIZE;
|
||||
}
|
||||
|
||||
IdentHash Identity::Hash () const
|
||||
{
|
||||
IdentHash hash;
|
||||
SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
IdentityEx::IdentityEx ():
|
||||
m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type):
|
||||
m_IsVerifierCreated (false)
|
||||
{
|
||||
memcpy (m_StandardIdentity.publicKey, publicKey, sizeof (m_StandardIdentity.publicKey));
|
||||
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
|
||||
{
|
||||
size_t excessLen = 0;
|
||||
uint8_t * excessBuf = nullptr;
|
||||
switch (type)
|
||||
{
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||
{
|
||||
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
|
||||
RAND_bytes (m_StandardIdentity.signingKey, padding);
|
||||
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||
{
|
||||
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
|
||||
RAND_bytes (m_StandardIdentity.signingKey, padding);
|
||||
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP384_KEY_LENGTH);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||
{
|
||||
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||
excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132 - 128
|
||||
excessBuf = new uint8_t[excessLen];
|
||||
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||
{
|
||||
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||
excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256 - 128
|
||||
excessBuf = new uint8_t[excessLen];
|
||||
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||
{
|
||||
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||
excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384 - 128
|
||||
excessBuf = new uint8_t[excessLen];
|
||||
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||
{
|
||||
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
|
||||
excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512 - 128
|
||||
excessBuf = new uint8_t[excessLen];
|
||||
memcpy (excessBuf, signingKey + 128, excessLen);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||
{
|
||||
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
|
||||
RAND_bytes (m_StandardIdentity.signingKey, padding);
|
||||
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST:
|
||||
{
|
||||
// 256
|
||||
size_t padding = 128 - i2p::crypto::GOSTR3410_256_PUBLIC_KEY_LENGTH; // 64 = 128 - 64
|
||||
RAND_bytes (m_StandardIdentity.signingKey, padding);
|
||||
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::GOSTR3410_256_PUBLIC_KEY_LENGTH);
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512_TEST:
|
||||
{
|
||||
// 512
|
||||
// no padding, key length is 128
|
||||
memcpy (m_StandardIdentity.signingKey, signingKey, i2p::crypto::GOSTR3410_512_PUBLIC_KEY_LENGTH);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported");
|
||||
}
|
||||
m_ExtendedLen = 4 + excessLen; // 4 bytes extra + excess length
|
||||
// fill certificate
|
||||
m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY;
|
||||
htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen);
|
||||
// fill extended buffer
|
||||
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||
htobe16buf (m_ExtendedBuffer, type);
|
||||
htobe16buf (m_ExtendedBuffer + 2, CRYPTO_KEY_TYPE_ELGAMAL);
|
||||
if (excessLen && excessBuf)
|
||||
{
|
||||
memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
|
||||
delete[] excessBuf;
|
||||
}
|
||||
// calculate ident hash
|
||||
uint8_t * buf = new uint8_t[GetFullLen ()];
|
||||
ToBuffer (buf, GetFullLen ());
|
||||
SHA256(buf, GetFullLen (), m_IdentHash);
|
||||
delete[] buf;
|
||||
}
|
||||
else // DSA-SHA1
|
||||
{
|
||||
memcpy (m_StandardIdentity.signingKey, signingKey, sizeof (m_StandardIdentity.signingKey));
|
||||
memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate));
|
||||
m_IdentHash = m_StandardIdentity.Hash ();
|
||||
m_ExtendedLen = 0;
|
||||
m_ExtendedBuffer = nullptr;
|
||||
}
|
||||
CreateVerifier ();
|
||||
}
|
||||
|
||||
IdentityEx::IdentityEx (const uint8_t * buf, size_t len):
|
||||
m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||
{
|
||||
FromBuffer (buf, len);
|
||||
}
|
||||
|
||||
IdentityEx::IdentityEx (const IdentityEx& other):
|
||||
m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
||||
IdentityEx::IdentityEx (const Identity& standard):
|
||||
m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr)
|
||||
{
|
||||
*this = standard;
|
||||
}
|
||||
|
||||
IdentityEx::~IdentityEx ()
|
||||
{
|
||||
delete[] m_ExtendedBuffer;
|
||||
}
|
||||
|
||||
IdentityEx& IdentityEx::operator=(const IdentityEx& other)
|
||||
{
|
||||
memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
|
||||
m_IdentHash = other.m_IdentHash;
|
||||
|
||||
delete[] m_ExtendedBuffer;
|
||||
m_ExtendedLen = other.m_ExtendedLen;
|
||||
if (m_ExtendedLen > 0)
|
||||
{
|
||||
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||
memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen);
|
||||
}
|
||||
else
|
||||
m_ExtendedBuffer = nullptr;
|
||||
|
||||
m_Verifier = nullptr;
|
||||
m_IsVerifierCreated = false;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
IdentityEx& IdentityEx::operator=(const Identity& standard)
|
||||
{
|
||||
m_StandardIdentity = standard;
|
||||
m_IdentHash = m_StandardIdentity.Hash ();
|
||||
|
||||
delete[] m_ExtendedBuffer;
|
||||
m_ExtendedBuffer = nullptr;
|
||||
m_ExtendedLen = 0;
|
||||
|
||||
m_Verifier = nullptr;
|
||||
m_IsVerifierCreated = false;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t IdentityEx::FromBuffer (const uint8_t * buf, size_t len)
|
||||
{
|
||||
if (len < DEFAULT_IDENTITY_SIZE)
|
||||
{
|
||||
LogPrint (eLogError, "Identity: buffer length ", len, " is too small");
|
||||
return 0;
|
||||
}
|
||||
memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE);
|
||||
|
||||
if(m_ExtendedBuffer) delete[] m_ExtendedBuffer;
|
||||
m_ExtendedBuffer = nullptr;
|
||||
|
||||
m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1);
|
||||
if (m_ExtendedLen)
|
||||
{
|
||||
if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len)
|
||||
{
|
||||
m_ExtendedBuffer = new uint8_t[m_ExtendedLen];
|
||||
memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "Identity: Certificate length ", m_ExtendedLen, " exceeds buffer length ", len - DEFAULT_IDENTITY_SIZE);
|
||||
m_ExtendedLen = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ExtendedLen = 0;
|
||||
m_ExtendedBuffer = nullptr;
|
||||
}
|
||||
SHA256(buf, GetFullLen (), m_IdentHash);
|
||||
|
||||
m_Verifier = nullptr;
|
||||
|
||||
return GetFullLen ();
|
||||
}
|
||||
|
||||
size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const
|
||||
{
|
||||
const size_t fullLen = GetFullLen();
|
||||
if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else
|
||||
memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE);
|
||||
if (m_ExtendedLen > 0 && m_ExtendedBuffer)
|
||||
memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen);
|
||||
return fullLen;
|
||||
}
|
||||
|
||||
size_t IdentityEx::FromBase64(const std::string& s)
|
||||
{
|
||||
const size_t slen = s.length();
|
||||
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);
|
||||
}
|
||||
|
||||
std::string IdentityEx::ToBase64 () const
|
||||
{
|
||||
const size_t bufLen = GetFullLen();
|
||||
const size_t strLen = Base64EncodingBufferSize(bufLen);
|
||||
std::vector<uint8_t> buf(bufLen);
|
||||
std::vector<char> str(strLen);
|
||||
size_t l = ToBuffer (buf.data(), bufLen);
|
||||
size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen);
|
||||
return std::string (str.data(), l1);
|
||||
}
|
||||
|
||||
size_t IdentityEx::GetSigningPublicKeyLen () const
|
||||
{
|
||||
if (!m_Verifier) CreateVerifier ();
|
||||
if (m_Verifier)
|
||||
return m_Verifier->GetPublicKeyLen ();
|
||||
return 128;
|
||||
}
|
||||
|
||||
size_t IdentityEx::GetSigningPrivateKeyLen () const
|
||||
{
|
||||
if (!m_Verifier) CreateVerifier ();
|
||||
if (m_Verifier)
|
||||
return m_Verifier->GetPrivateKeyLen ();
|
||||
return GetSignatureLen ()/2;
|
||||
}
|
||||
|
||||
size_t IdentityEx::GetSignatureLen () const
|
||||
{
|
||||
if (!m_Verifier) CreateVerifier ();
|
||||
if (m_Verifier)
|
||||
return m_Verifier->GetSignatureLen ();
|
||||
return i2p::crypto::DSA_SIGNATURE_LENGTH;
|
||||
}
|
||||
bool IdentityEx::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||
{
|
||||
if (!m_Verifier) CreateVerifier ();
|
||||
if (m_Verifier)
|
||||
return m_Verifier->Verify (buf, len, signature);
|
||||
return false;
|
||||
}
|
||||
|
||||
SigningKeyType IdentityEx::GetSigningKeyType () const
|
||||
{
|
||||
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 2)
|
||||
return bufbe16toh (m_ExtendedBuffer); // signing key
|
||||
return SIGNING_KEY_TYPE_DSA_SHA1;
|
||||
}
|
||||
|
||||
CryptoKeyType IdentityEx::GetCryptoKeyType () const
|
||||
{
|
||||
if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 4)
|
||||
return bufbe16toh (m_ExtendedBuffer + 2); // crypto key
|
||||
return CRYPTO_KEY_TYPE_ELGAMAL;
|
||||
}
|
||||
|
||||
void IdentityEx::CreateVerifier () const
|
||||
{
|
||||
if (m_Verifier) return; // don't create again
|
||||
auto keyType = GetSigningKeyType ();
|
||||
switch (keyType)
|
||||
{
|
||||
case SIGNING_KEY_TYPE_DSA_SHA1:
|
||||
UpdateVerifier (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||
{
|
||||
size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
|
||||
UpdateVerifier (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||
{
|
||||
size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
|
||||
UpdateVerifier (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||
{
|
||||
uint8_t signingKey[i2p::crypto::ECDSAP521_KEY_LENGTH];
|
||||
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||
size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128
|
||||
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||
UpdateVerifier (new i2p::crypto::ECDSAP521Verifier (signingKey));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||
{
|
||||
uint8_t signingKey[i2p::crypto::RSASHA2562048_KEY_LENGTH];
|
||||
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||
size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128
|
||||
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||
UpdateVerifier (new i2p::crypto:: RSASHA2562048Verifier (signingKey));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||
{
|
||||
uint8_t signingKey[i2p::crypto::RSASHA3843072_KEY_LENGTH];
|
||||
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||
size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128
|
||||
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||
UpdateVerifier (new i2p::crypto:: RSASHA3843072Verifier (signingKey));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||
{
|
||||
uint8_t signingKey[i2p::crypto::RSASHA5124096_KEY_LENGTH];
|
||||
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
|
||||
size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128
|
||||
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
|
||||
UpdateVerifier (new i2p::crypto:: RSASHA5124096Verifier (signingKey));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||
{
|
||||
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
|
||||
UpdateVerifier (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST:
|
||||
{
|
||||
size_t padding = 128 - i2p::crypto::GOSTR3410_256_PUBLIC_KEY_LENGTH; // 64 = 128 - 64
|
||||
UpdateVerifier (new i2p::crypto::GOSTR3410_256_Verifier (i2p::crypto::eGOSTR3410CryptoProA, m_StandardIdentity.signingKey + padding));
|
||||
break;
|
||||
}
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512_TEST:
|
||||
{
|
||||
// zero padding
|
||||
UpdateVerifier (new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512, m_StandardIdentity.signingKey));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const
|
||||
{
|
||||
if (!m_Verifier)
|
||||
{
|
||||
auto created = m_IsVerifierCreated.exchange (true);
|
||||
if (!created)
|
||||
m_Verifier.reset (verifier);
|
||||
else
|
||||
{
|
||||
delete verifier;
|
||||
int count = 0;
|
||||
while (!m_Verifier && count < 500) // 5 seconds
|
||||
{
|
||||
std::this_thread::sleep_for (std::chrono::milliseconds(10));
|
||||
count++;
|
||||
}
|
||||
if (!m_Verifier)
|
||||
LogPrint (eLogError, "Identity: couldn't get verifier in 5 seconds");
|
||||
}
|
||||
}
|
||||
else
|
||||
delete verifier;
|
||||
}
|
||||
|
||||
void IdentityEx::DropVerifier () const
|
||||
{
|
||||
// TODO: potential race condition with Verify
|
||||
m_IsVerifierCreated = false;
|
||||
m_Verifier = nullptr;
|
||||
}
|
||||
|
||||
PrivateKeys& PrivateKeys::operator=(const Keys& keys)
|
||||
{
|
||||
m_Public = std::make_shared<IdentityEx>(Identity (keys));
|
||||
memcpy (m_PrivateKey, keys.privateKey, 256); // 256
|
||||
memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ());
|
||||
m_Signer = nullptr;
|
||||
CreateSigner ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
PrivateKeys& PrivateKeys::operator=(const PrivateKeys& other)
|
||||
{
|
||||
m_Public = std::make_shared<IdentityEx>(*other.m_Public);
|
||||
memcpy (m_PrivateKey, other.m_PrivateKey, 256); // 256
|
||||
memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_Public->GetSigningPrivateKeyLen ());
|
||||
m_Signer = nullptr;
|
||||
CreateSigner ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len)
|
||||
{
|
||||
m_Public = std::make_shared<IdentityEx>();
|
||||
size_t ret = m_Public->FromBuffer (buf, len);
|
||||
if (!ret || ret + 256 > len) return 0; // overflow
|
||||
memcpy (m_PrivateKey, buf + ret, 256); // private key always 256
|
||||
ret += 256;
|
||||
size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
|
||||
if(signingPrivateKeySize + ret > len) return 0; // overflow
|
||||
memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
|
||||
ret += signingPrivateKeySize;
|
||||
m_Signer = nullptr;
|
||||
CreateSigner ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const
|
||||
{
|
||||
size_t ret = m_Public->ToBuffer (buf, len);
|
||||
memcpy (buf + ret, m_PrivateKey, 256); // private key always 256
|
||||
ret += 256;
|
||||
size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen ();
|
||||
if(ret + signingPrivateKeySize > len) return 0; // overflow
|
||||
memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
|
||||
ret += signingPrivateKeySize;
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PrivateKeys::FromBase64(const std::string& s)
|
||||
{
|
||||
uint8_t * buf = new uint8_t[s.length ()];
|
||||
size_t l = i2p::data::Base64ToByteStream (s.c_str (), s.length (), buf, s.length ());
|
||||
size_t ret = FromBuffer (buf, l);
|
||||
delete[] buf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string PrivateKeys::ToBase64 () const
|
||||
{
|
||||
uint8_t * buf = new uint8_t[GetFullLen ()];
|
||||
char * str = new char[GetFullLen ()*2];
|
||||
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
|
||||
{
|
||||
if (!m_Signer)
|
||||
CreateSigner();
|
||||
m_Signer->Sign (buf, len, signature);
|
||||
}
|
||||
|
||||
void PrivateKeys::CreateSigner () const
|
||||
{
|
||||
if (m_Signer) return;
|
||||
switch (m_Public->GetSigningKeyType ())
|
||||
{
|
||||
case SIGNING_KEY_TYPE_DSA_SHA1:
|
||||
m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||
m_Signer.reset (new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||
m_Signer.reset (new i2p::crypto::ECDSAP384Signer (m_SigningPrivateKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||
m_Signer.reset (new i2p::crypto::ECDSAP521Signer (m_SigningPrivateKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||
m_Signer.reset (new i2p::crypto::RSASHA2562048Signer (m_SigningPrivateKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||
m_Signer.reset (new i2p::crypto::RSASHA3843072Signer (m_SigningPrivateKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||
m_Signer.reset (new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||
m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().certificate - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST:
|
||||
m_Signer.reset (new i2p::crypto::GOSTR3410_256_Signer (i2p::crypto::eGOSTR3410CryptoProA, m_SigningPrivateKey));
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512_TEST:
|
||||
m_Signer.reset (new i2p::crypto::GOSTR3410_512_Signer (i2p::crypto::eGOSTR3410TC26A512, m_SigningPrivateKey));
|
||||
break;
|
||||
default:
|
||||
LogPrint (eLogError, "Identity: Signing key type ", (int)m_Public->GetSigningKeyType (), " is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type)
|
||||
{
|
||||
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
|
||||
{
|
||||
PrivateKeys keys;
|
||||
// signature
|
||||
uint8_t signingPublicKey[512]; // signing public key is 512 bytes max
|
||||
switch (type)
|
||||
{
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
|
||||
i2p::crypto::CreateECDSAP256RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
|
||||
i2p::crypto::CreateECDSAP384RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
|
||||
i2p::crypto::CreateECDSAP521RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
|
||||
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA2562048_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
|
||||
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA3843072_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
|
||||
i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA5124096_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
|
||||
i2p::crypto::CreateEDDSA25519RandomKeys (keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST:
|
||||
i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
|
||||
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512_TEST:
|
||||
i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, keys.m_SigningPrivateKey, signingPublicKey);
|
||||
break;
|
||||
default:
|
||||
LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
|
||||
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
|
||||
}
|
||||
// encryption
|
||||
uint8_t publicKey[256];
|
||||
i2p::crypto::GenerateElGamalKeyPair (keys.m_PrivateKey, publicKey);
|
||||
// identity
|
||||
keys.m_Public = std::make_shared<IdentityEx> (publicKey, signingPublicKey, type);
|
||||
|
||||
keys.CreateSigner ();
|
||||
return keys;
|
||||
}
|
||||
return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1
|
||||
}
|
||||
|
||||
Keys CreateRandomKeys ()
|
||||
{
|
||||
Keys keys;
|
||||
// encryption
|
||||
i2p::crypto::GenerateElGamalKeyPair(keys.privateKey, keys.publicKey);
|
||||
// signing
|
||||
i2p::crypto::CreateDSARandomKeys (keys.signingPrivateKey, keys.signingKey);
|
||||
return keys;
|
||||
}
|
||||
|
||||
IdentHash CreateRoutingKey (const IdentHash& ident)
|
||||
{
|
||||
uint8_t buf[41]; // ident + yyyymmdd
|
||||
memcpy (buf, (const uint8_t *)ident, 32);
|
||||
time_t t = time (nullptr);
|
||||
struct tm tm;
|
||||
#ifdef _WIN32
|
||||
gmtime_s(&tm, &t);
|
||||
sprintf_s((char *)(buf + 32), 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||
#else
|
||||
gmtime_r(&t, &tm);
|
||||
sprintf((char *)(buf + 32), "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||
#endif
|
||||
IdentHash key;
|
||||
SHA256(buf, 40, key);
|
||||
return key;
|
||||
}
|
||||
|
||||
XORMetric operator^(const IdentHash& key1, const IdentHash& key2)
|
||||
{
|
||||
XORMetric m;
|
||||
#if defined(__AVX__) // for AVX
|
||||
__asm__
|
||||
(
|
||||
"vmovups %1, %%ymm0 \n"
|
||||
"vmovups %2, %%ymm1 \n"
|
||||
"vxorps %%ymm0, %%ymm1, %%ymm1 \n"
|
||||
"vmovups %%ymm1, %0 \n"
|
||||
: "=m"(*m.metric)
|
||||
: "m"(*key1), "m"(*key2)
|
||||
: "memory", "%xmm0", "%xmm1" // should be replaced by %ymm0/1 once supported by compiler
|
||||
);
|
||||
#else
|
||||
const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL ();
|
||||
m.metric_ll[0] = hash1[0] ^ hash2[0];
|
||||
m.metric_ll[1] = hash1[1] ^ hash2[1];
|
||||
m.metric_ll[2] = hash1[2] ^ hash2[2];
|
||||
m.metric_ll[3] = hash1[3] ^ hash2[3];
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
202
libi2pd/Identity.h
Normal file
202
libi2pd/Identity.h
Normal file
|
@ -0,0 +1,202 @@
|
|||
#ifndef IDENTITY_H__
|
||||
#define IDENTITY_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include "Base.h"
|
||||
#include "Signature.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
typedef Tag<32> IdentHash;
|
||||
inline std::string GetIdentHashAbbreviation (const IdentHash& ident)
|
||||
{
|
||||
return ident.ToBase64 ().substr (0, 4);
|
||||
}
|
||||
|
||||
struct Keys
|
||||
{
|
||||
uint8_t privateKey[256];
|
||||
uint8_t signingPrivateKey[20];
|
||||
uint8_t publicKey[256];
|
||||
uint8_t signingKey[128];
|
||||
};
|
||||
|
||||
const uint8_t CERTIFICATE_TYPE_NULL = 0;
|
||||
const uint8_t CERTIFICATE_TYPE_HASHCASH = 1;
|
||||
const uint8_t CERTIFICATE_TYPE_HIDDEN = 2;
|
||||
const uint8_t CERTIFICATE_TYPE_SIGNED = 3;
|
||||
const uint8_t CERTIFICATE_TYPE_MULTIPLE = 4;
|
||||
const uint8_t CERTIFICATE_TYPE_KEY = 5;
|
||||
|
||||
struct Identity
|
||||
{
|
||||
uint8_t publicKey[256];
|
||||
uint8_t signingKey[128];
|
||||
uint8_t certificate[3]; // byte 1 - type, bytes 2-3 - length
|
||||
|
||||
Identity () = default;
|
||||
Identity (const Keys& keys) { *this = keys; };
|
||||
Identity& operator=(const Keys& keys);
|
||||
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||
IdentHash Hash () const;
|
||||
};
|
||||
|
||||
Keys CreateRandomKeys ();
|
||||
|
||||
const size_t DEFAULT_IDENTITY_SIZE = sizeof (Identity); // 387 bytes
|
||||
|
||||
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 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_SHA384_P384 = 2;
|
||||
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA512_P521 = 3;
|
||||
const uint16_t SIGNING_KEY_TYPE_RSA_SHA256_2048 = 4;
|
||||
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_EDDSA_SHA512_ED25519 = 7;
|
||||
const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph = 8; // not implemented
|
||||
// following signature type should never appear in netid=2
|
||||
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
|
||||
// TODO: remove later
|
||||
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST = 65281;
|
||||
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512_TEST = 65282;
|
||||
|
||||
typedef uint16_t SigningKeyType;
|
||||
typedef uint16_t CryptoKeyType;
|
||||
|
||||
class IdentityEx
|
||||
{
|
||||
public:
|
||||
|
||||
IdentityEx ();
|
||||
IdentityEx (const uint8_t * publicKey, const uint8_t * signingKey,
|
||||
SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
|
||||
IdentityEx (const uint8_t * buf, size_t len);
|
||||
IdentityEx (const IdentityEx& other);
|
||||
IdentityEx (const Identity& standard);
|
||||
~IdentityEx ();
|
||||
IdentityEx& operator=(const IdentityEx& other);
|
||||
IdentityEx& operator=(const Identity& standard);
|
||||
|
||||
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
||||
size_t FromBase64(const std::string& s);
|
||||
std::string ToBase64 () const;
|
||||
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
|
||||
const IdentHash& GetIdentHash () const { return m_IdentHash; };
|
||||
const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; };
|
||||
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
|
||||
size_t GetSigningPublicKeyLen () const;
|
||||
size_t GetSigningPrivateKeyLen () const;
|
||||
size_t GetSignatureLen () const;
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
|
||||
SigningKeyType GetSigningKeyType () const;
|
||||
CryptoKeyType GetCryptoKeyType () const;
|
||||
void DropVerifier () const; // to save memory
|
||||
|
||||
bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); }
|
||||
|
||||
private:
|
||||
|
||||
void CreateVerifier () const;
|
||||
void UpdateVerifier (i2p::crypto::Verifier * verifier) const;
|
||||
|
||||
private:
|
||||
|
||||
Identity m_StandardIdentity;
|
||||
IdentHash m_IdentHash;
|
||||
mutable std::unique_ptr<i2p::crypto::Verifier> m_Verifier;
|
||||
mutable std::atomic_bool m_IsVerifierCreated; // make sure we don't create twice
|
||||
size_t m_ExtendedLen;
|
||||
uint8_t * m_ExtendedBuffer;
|
||||
};
|
||||
|
||||
class PrivateKeys // for eepsites
|
||||
{
|
||||
public:
|
||||
|
||||
PrivateKeys () = default;
|
||||
PrivateKeys (const PrivateKeys& other) { *this = other; };
|
||||
PrivateKeys (const Keys& keys) { *this = keys; };
|
||||
PrivateKeys& operator=(const Keys& keys);
|
||||
PrivateKeys& operator=(const PrivateKeys& other);
|
||||
~PrivateKeys () = default;
|
||||
|
||||
std::shared_ptr<const IdentityEx> GetPublic () const { return m_Public; };
|
||||
const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
|
||||
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
|
||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
|
||||
|
||||
size_t GetFullLen () const { return m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); };
|
||||
size_t FromBuffer (const uint8_t * buf, size_t len);
|
||||
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
||||
|
||||
size_t FromBase64(const std::string& s);
|
||||
std::string ToBase64 () const;
|
||||
|
||||
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1);
|
||||
|
||||
private:
|
||||
|
||||
void CreateSigner () const;
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<IdentityEx> m_Public;
|
||||
uint8_t m_PrivateKey[256];
|
||||
uint8_t m_SigningPrivateKey[1024]; // assume private key doesn't exceed 1024 bytes
|
||||
mutable std::unique_ptr<i2p::crypto::Signer> m_Signer;
|
||||
};
|
||||
|
||||
// kademlia
|
||||
struct XORMetric
|
||||
{
|
||||
union
|
||||
{
|
||||
uint8_t metric[32];
|
||||
uint64_t metric_ll[4];
|
||||
};
|
||||
|
||||
void SetMin () { memset (metric, 0, 32); };
|
||||
void SetMax () { memset (metric, 0xFF, 32); };
|
||||
bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; };
|
||||
};
|
||||
|
||||
IdentHash CreateRoutingKey (const IdentHash& ident);
|
||||
XORMetric operator^(const IdentHash& key1, const IdentHash& key2);
|
||||
|
||||
// destination for delivery instuctions
|
||||
class RoutingDestination
|
||||
{
|
||||
public:
|
||||
|
||||
RoutingDestination () {};
|
||||
virtual ~RoutingDestination () {};
|
||||
|
||||
virtual const IdentHash& GetIdentHash () const = 0;
|
||||
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
|
||||
virtual bool IsDestination () const = 0; // for garlic
|
||||
};
|
||||
|
||||
class LocalDestination
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~LocalDestination() {};
|
||||
virtual const uint8_t * GetEncryptionPrivateKey () const = 0;
|
||||
virtual std::shared_ptr<const IdentityEx> GetIdentity () const = 0;
|
||||
|
||||
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
262
libi2pd/LeaseSet.cpp
Normal file
262
libi2pd/LeaseSet.cpp
Normal file
|
@ -0,0 +1,262 @@
|
|||
#include <string.h>
|
||||
#include "I2PEndian.h"
|
||||
#include "Crypto.h"
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "NetDb.h"
|
||||
#include "Tunnel.h"
|
||||
#include "LeaseSet.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
|
||||
LeaseSet::LeaseSet (const uint8_t * buf, size_t len, bool storeLeases):
|
||||
m_IsValid (true), m_StoreLeases (storeLeases), m_ExpirationTime (0)
|
||||
{
|
||||
m_Buffer = new uint8_t[len];
|
||||
memcpy (m_Buffer, buf, len);
|
||||
m_BufferLen = len;
|
||||
ReadFromBuffer ();
|
||||
}
|
||||
|
||||
void LeaseSet::Update (const uint8_t * buf, size_t len)
|
||||
{
|
||||
if (len > m_BufferLen)
|
||||
{
|
||||
auto oldBuffer = m_Buffer;
|
||||
m_Buffer = new uint8_t[len];
|
||||
delete[] oldBuffer;
|
||||
}
|
||||
memcpy (m_Buffer, buf, len);
|
||||
m_BufferLen = len;
|
||||
ReadFromBuffer (false);
|
||||
}
|
||||
|
||||
void LeaseSet::PopulateLeases ()
|
||||
{
|
||||
m_StoreLeases = true;
|
||||
ReadFromBuffer (false);
|
||||
}
|
||||
|
||||
void LeaseSet::ReadFromBuffer (bool readIdentity)
|
||||
{
|
||||
if (readIdentity || !m_Identity)
|
||||
m_Identity = std::make_shared<IdentityEx>(m_Buffer, m_BufferLen);
|
||||
size_t size = m_Identity->GetFullLen ();
|
||||
if (size > m_BufferLen)
|
||||
{
|
||||
LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", m_BufferLen);
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
memcpy (m_EncryptionKey, m_Buffer + size, 256);
|
||||
size += 256; // encryption key
|
||||
size += m_Identity->GetSigningPublicKeyLen (); // unused signing key
|
||||
uint8_t num = m_Buffer[size];
|
||||
size++; // num
|
||||
LogPrint (eLogDebug, "LeaseSet: read num=", (int)num);
|
||||
if (!num || num > MAX_NUM_LEASES)
|
||||
{
|
||||
LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)num);
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// reset existing leases
|
||||
if (m_StoreLeases)
|
||||
for (auto& it: m_Leases)
|
||||
it->isUpdated = false;
|
||||
else
|
||||
m_Leases.clear ();
|
||||
|
||||
// process leases
|
||||
m_ExpirationTime = 0;
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
const uint8_t * leases = m_Buffer + size;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
Lease lease;
|
||||
lease.tunnelGateway = leases;
|
||||
leases += 32; // gateway
|
||||
lease.tunnelID = bufbe32toh (leases);
|
||||
leases += 4; // tunnel ID
|
||||
lease.endDate = bufbe64toh (leases);
|
||||
leases += 8; // end date
|
||||
if (ts < lease.endDate + LEASE_ENDDATE_THRESHOLD)
|
||||
{
|
||||
if (lease.endDate > m_ExpirationTime)
|
||||
m_ExpirationTime = lease.endDate;
|
||||
if (m_StoreLeases)
|
||||
{
|
||||
auto ret = m_Leases.insert (std::make_shared<Lease>(lease));
|
||||
if (!ret.second) (*ret.first)->endDate = lease.endDate; // update existing
|
||||
(*ret.first)->isUpdated = true;
|
||||
// check if lease's gateway is in our netDb
|
||||
if (!netdb.FindRouter (lease.tunnelGateway))
|
||||
{
|
||||
// if not found request it
|
||||
LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting");
|
||||
netdb.RequestDestination (lease.tunnelGateway);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "LeaseSet: Lease is expired already ");
|
||||
}
|
||||
if (!m_ExpirationTime)
|
||||
{
|
||||
LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped");
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
m_ExpirationTime += LEASE_ENDDATE_THRESHOLD;
|
||||
// delete old leases
|
||||
if (m_StoreLeases)
|
||||
{
|
||||
for (auto it = m_Leases.begin (); it != m_Leases.end ();)
|
||||
{
|
||||
if (!(*it)->isUpdated)
|
||||
{
|
||||
(*it)->endDate = 0; // somebody might still hold it
|
||||
m_Leases.erase (it++);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// verify
|
||||
if (!m_Identity->Verify (m_Buffer, leases - m_Buffer, leases))
|
||||
{
|
||||
LogPrint (eLogWarning, "LeaseSet: verification failed");
|
||||
m_IsValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t LeaseSet::ExtractTimestamp (const uint8_t * buf, size_t len) const
|
||||
{
|
||||
if (!m_Identity) return 0;
|
||||
size_t size = m_Identity->GetFullLen ();
|
||||
if (size > len) return 0;
|
||||
size += 256; // encryption key
|
||||
size += m_Identity->GetSigningPublicKeyLen (); // unused signing key
|
||||
if (size > len) return 0;
|
||||
uint8_t num = buf[size];
|
||||
size++; // num
|
||||
if (size + num*LEASE_SIZE > len) return 0;
|
||||
uint64_t timestamp= 0 ;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
size += 36; // gateway (32) + tunnelId(4)
|
||||
auto endDate = bufbe64toh (buf + size);
|
||||
size += 8; // end date
|
||||
if (!timestamp || endDate < timestamp)
|
||||
timestamp = endDate;
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const
|
||||
{
|
||||
return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen);
|
||||
}
|
||||
|
||||
bool LeaseSet::ExpiresSoon(const uint64_t dlt, const uint64_t fudge) const
|
||||
{
|
||||
auto now = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
if (fudge) now += rand() % fudge;
|
||||
if (now >= m_ExpirationTime) return true;
|
||||
return m_ExpirationTime - now <= dlt;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<const Lease> > LeaseSet::GetNonExpiredLeases (bool withThreshold) const
|
||||
{
|
||||
return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold);
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<const Lease> > LeaseSet::GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold) const
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
std::vector<std::shared_ptr<const Lease> > leases;
|
||||
for (const auto& it: m_Leases)
|
||||
{
|
||||
auto endDate = it->endDate;
|
||||
if (withThreshold)
|
||||
endDate += LEASE_ENDDATE_THRESHOLD;
|
||||
else
|
||||
endDate -= LEASE_ENDDATE_THRESHOLD;
|
||||
if (ts < endDate && !exclude(*it))
|
||||
leases.push_back (it);
|
||||
}
|
||||
return leases;
|
||||
}
|
||||
|
||||
bool LeaseSet::HasExpiredLeases () const
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
for (const auto& it: m_Leases)
|
||||
if (ts >= it->endDate) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LeaseSet::IsExpired () const
|
||||
{
|
||||
if (m_StoreLeases && IsEmpty ()) return true;
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
return ts > m_ExpirationTime;
|
||||
}
|
||||
|
||||
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels):
|
||||
m_ExpirationTime (0), m_Identity (identity)
|
||||
{
|
||||
int num = tunnels.size ();
|
||||
if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES;
|
||||
// identity
|
||||
auto signingKeyLen = m_Identity->GetSigningPublicKeyLen ();
|
||||
m_BufferLen = m_Identity->GetFullLen () + 256 + signingKeyLen + 1 + num*LEASE_SIZE + m_Identity->GetSignatureLen ();
|
||||
m_Buffer = new uint8_t[m_BufferLen];
|
||||
auto offset = m_Identity->ToBuffer (m_Buffer, m_BufferLen);
|
||||
memcpy (m_Buffer + offset, encryptionPublicKey, 256);
|
||||
offset += 256;
|
||||
memset (m_Buffer + offset, 0, signingKeyLen);
|
||||
offset += signingKeyLen;
|
||||
// num leases
|
||||
m_Buffer[offset] = num;
|
||||
offset++;
|
||||
// leases
|
||||
m_Leases = m_Buffer + offset;
|
||||
auto currentTime = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
|
||||
offset += 32; // gateway id
|
||||
htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
|
||||
offset += 4; // tunnel id
|
||||
uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
|
||||
ts *= 1000; // in milliseconds
|
||||
if (ts > m_ExpirationTime) m_ExpirationTime = ts;
|
||||
// make sure leaseset is newer than previous, but adding some time to expiration date
|
||||
ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
|
||||
htobe64buf (m_Buffer + offset, ts);
|
||||
offset += 8; // end date
|
||||
}
|
||||
// we don't sign it yet. must be signed later on
|
||||
}
|
||||
|
||||
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len):
|
||||
m_ExpirationTime (0), m_Identity (identity)
|
||||
{
|
||||
m_BufferLen = len;
|
||||
m_Buffer = new uint8_t[m_BufferLen];
|
||||
memcpy (m_Buffer, buf, len);
|
||||
}
|
||||
|
||||
bool LocalLeaseSet::IsExpired () const
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
return ts > m_ExpirationTime;
|
||||
}
|
||||
}
|
||||
}
|
131
libi2pd/LeaseSet.h
Normal file
131
libi2pd/LeaseSet.h
Normal file
|
@ -0,0 +1,131 @@
|
|||
#ifndef LEASE_SET_H__
|
||||
#define LEASE_SET_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include "Identity.h"
|
||||
#include "Timestamp.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
|
||||
namespace tunnel
|
||||
{
|
||||
class InboundTunnel;
|
||||
}
|
||||
|
||||
namespace data
|
||||
{
|
||||
const int LEASE_ENDDATE_THRESHOLD = 51000; // in milliseconds
|
||||
struct Lease
|
||||
{
|
||||
IdentHash tunnelGateway;
|
||||
uint32_t tunnelID;
|
||||
uint64_t endDate; // 0 means invalid
|
||||
bool isUpdated; // trasient
|
||||
/* return true if this lease expires within t millisecond + fudge factor */
|
||||
bool ExpiresWithin( const uint64_t t, const uint64_t fudge = 1000 ) const {
|
||||
auto expire = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
if(fudge) expire += rand() % fudge;
|
||||
if (endDate < expire) return true;
|
||||
return (endDate - expire) < t;
|
||||
}
|
||||
};
|
||||
|
||||
struct LeaseCmp
|
||||
{
|
||||
bool operator() (std::shared_ptr<const Lease> l1, std::shared_ptr<const Lease> l2) const
|
||||
{
|
||||
if (l1->tunnelID != l2->tunnelID)
|
||||
return l1->tunnelID < l2->tunnelID;
|
||||
else
|
||||
return l1->tunnelGateway < l2->tunnelGateway;
|
||||
};
|
||||
};
|
||||
|
||||
typedef std::function<bool(const Lease & l)> LeaseInspectFunc;
|
||||
|
||||
const size_t MAX_LS_BUFFER_SIZE = 3072;
|
||||
const size_t LEASE_SIZE = 44; // 32 + 4 + 8
|
||||
const uint8_t MAX_NUM_LEASES = 16;
|
||||
class LeaseSet: public RoutingDestination
|
||||
{
|
||||
public:
|
||||
|
||||
LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true);
|
||||
~LeaseSet () { delete[] m_Buffer; };
|
||||
void Update (const uint8_t * buf, size_t len);
|
||||
bool IsNewer (const uint8_t * buf, size_t len) const;
|
||||
void PopulateLeases (); // from buffer
|
||||
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
|
||||
|
||||
const uint8_t * GetBuffer () const { return m_Buffer; };
|
||||
size_t GetBufferLen () const { return m_BufferLen; };
|
||||
bool IsValid () const { return m_IsValid; };
|
||||
const std::vector<std::shared_ptr<const Lease> > GetNonExpiredLeases (bool withThreshold = true) const;
|
||||
const std::vector<std::shared_ptr<const Lease> > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const;
|
||||
bool HasExpiredLeases () const;
|
||||
bool IsExpired () const;
|
||||
bool IsEmpty () const { return m_Leases.empty (); };
|
||||
uint64_t GetExpirationTime () const { return m_ExpirationTime; };
|
||||
bool ExpiresSoon(const uint64_t dlt=1000 * 5, const uint64_t fudge = 0) const ;
|
||||
bool operator== (const LeaseSet& other) const
|
||||
{ return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); };
|
||||
|
||||
// implements RoutingDestination
|
||||
const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); };
|
||||
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; };
|
||||
bool IsDestination () const { return true; };
|
||||
|
||||
private:
|
||||
|
||||
void ReadFromBuffer (bool readIdentity = true);
|
||||
uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // min expiration time
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsValid, m_StoreLeases; // we don't need to store leases for floodfill
|
||||
std::set<std::shared_ptr<Lease>, LeaseCmp> m_Leases;
|
||||
uint64_t m_ExpirationTime; // in milliseconds
|
||||
std::shared_ptr<const IdentityEx> m_Identity;
|
||||
uint8_t m_EncryptionKey[256];
|
||||
uint8_t * m_Buffer;
|
||||
size_t m_BufferLen;
|
||||
};
|
||||
|
||||
class LocalLeaseSet
|
||||
{
|
||||
public:
|
||||
|
||||
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels);
|
||||
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len);
|
||||
~LocalLeaseSet () { delete[] m_Buffer; };
|
||||
|
||||
const uint8_t * GetBuffer () const { return m_Buffer; };
|
||||
uint8_t * GetSignature () { return m_Buffer + m_BufferLen - GetSignatureLen (); };
|
||||
size_t GetBufferLen () const { return m_BufferLen; };
|
||||
size_t GetSignatureLen () const { return m_Identity->GetSignatureLen (); };
|
||||
uint8_t * GetLeases () { return m_Leases; };
|
||||
|
||||
const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); };
|
||||
bool IsExpired () const;
|
||||
uint64_t GetExpirationTime () const { return m_ExpirationTime; };
|
||||
void SetExpirationTime (uint64_t expirationTime) { m_ExpirationTime = expirationTime; };
|
||||
bool operator== (const LeaseSet& other) const
|
||||
{ return m_BufferLen == other.GetBufferLen () && !memcmp (other.GetBuffer (), other.GetBuffer (), m_BufferLen); };
|
||||
|
||||
|
||||
private:
|
||||
|
||||
uint64_t m_ExpirationTime; // in milliseconds
|
||||
std::shared_ptr<const IdentityEx> m_Identity;
|
||||
uint8_t * m_Buffer, * m_Leases;
|
||||
size_t m_BufferLen;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
242
libi2pd/LittleBigEndian.h
Normal file
242
libi2pd/LittleBigEndian.h
Normal file
|
@ -0,0 +1,242 @@
|
|||
// LittleBigEndian.h fixed for 64-bits added union
|
||||
//
|
||||
|
||||
#ifndef LITTLEBIGENDIAN_H
|
||||
#define LITTLEBIGENDIAN_H
|
||||
|
||||
// Determine Little-Endian or Big-Endian
|
||||
|
||||
#define CURRENT_BYTE_ORDER (*(int *)"\x01\x02\x03\x04")
|
||||
#define LITTLE_ENDIAN_BYTE_ORDER 0x04030201
|
||||
#define BIG_ENDIAN_BYTE_ORDER 0x01020304
|
||||
#define PDP_ENDIAN_BYTE_ORDER 0x02010403
|
||||
|
||||
#define IS_LITTLE_ENDIAN (CURRENT_BYTE_ORDER == LITTLE_ENDIAN_BYTE_ORDER)
|
||||
#define IS_BIG_ENDIAN (CURRENT_BYTE_ORDER == BIG_ENDIAN_BYTE_ORDER)
|
||||
#define IS_PDP_ENDIAN (CURRENT_BYTE_ORDER == PDP_ENDIAN_BYTE_ORDER)
|
||||
|
||||
// Forward declaration
|
||||
|
||||
template<typename T>
|
||||
struct LittleEndian;
|
||||
|
||||
template<typename T>
|
||||
struct BigEndian;
|
||||
|
||||
// Little-Endian template
|
||||
|
||||
#pragma pack(push,1)
|
||||
template<typename T>
|
||||
struct LittleEndian
|
||||
{
|
||||
union
|
||||
{
|
||||
unsigned char bytes[sizeof(T)];
|
||||
T raw_value;
|
||||
};
|
||||
|
||||
LittleEndian(T t = T())
|
||||
{
|
||||
operator =(t);
|
||||
}
|
||||
|
||||
LittleEndian(const LittleEndian<T> & t)
|
||||
{
|
||||
raw_value = t.raw_value;
|
||||
}
|
||||
|
||||
LittleEndian(const BigEndian<T> & t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[i] = t.bytes[sizeof(T)-1-i];
|
||||
}
|
||||
|
||||
operator const T() const
|
||||
{
|
||||
T t = T();
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
t |= T(bytes[i]) << (i << 3);
|
||||
return t;
|
||||
}
|
||||
|
||||
const T operator = (const T t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[sizeof(T)-1 - i] = static_cast<unsigned char>(t >> (i << 3));
|
||||
return t;
|
||||
}
|
||||
|
||||
// operators
|
||||
|
||||
const T operator += (const T t)
|
||||
{
|
||||
return (*this = *this + t);
|
||||
}
|
||||
|
||||
const T operator -= (const T t)
|
||||
{
|
||||
return (*this = *this - t);
|
||||
}
|
||||
|
||||
const T operator *= (const T t)
|
||||
{
|
||||
return (*this = *this * t);
|
||||
}
|
||||
|
||||
const T operator /= (const T t)
|
||||
{
|
||||
return (*this = *this / t);
|
||||
}
|
||||
|
||||
const T operator %= (const T t)
|
||||
{
|
||||
return (*this = *this % t);
|
||||
}
|
||||
|
||||
LittleEndian<T> operator ++ (int)
|
||||
{
|
||||
LittleEndian<T> tmp(*this);
|
||||
operator ++ ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
LittleEndian<T> & operator ++ ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
++bytes[i];
|
||||
if (bytes[i] != 0)
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
|
||||
LittleEndian<T> operator -- (int)
|
||||
{
|
||||
LittleEndian<T> tmp(*this);
|
||||
operator -- ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
LittleEndian<T> & operator -- ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
--bytes[i];
|
||||
if (bytes[i] != (T)(-1))
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Big-Endian template
|
||||
|
||||
#pragma pack(push,1)
|
||||
template<typename T>
|
||||
struct BigEndian
|
||||
{
|
||||
union
|
||||
{
|
||||
unsigned char bytes[sizeof(T)];
|
||||
T raw_value;
|
||||
};
|
||||
|
||||
BigEndian(T t = T())
|
||||
{
|
||||
operator =(t);
|
||||
}
|
||||
|
||||
BigEndian(const BigEndian<T> & t)
|
||||
{
|
||||
raw_value = t.raw_value;
|
||||
}
|
||||
|
||||
BigEndian(const LittleEndian<T> & t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[i] = t.bytes[sizeof(T)-1-i];
|
||||
}
|
||||
|
||||
operator const T() const
|
||||
{
|
||||
T t = T();
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
t |= T(bytes[sizeof(T) - 1 - i]) << (i << 3);
|
||||
return t;
|
||||
}
|
||||
|
||||
const T operator = (const T t)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
bytes[sizeof(T) - 1 - i] = t >> (i << 3);
|
||||
return t;
|
||||
}
|
||||
|
||||
// operators
|
||||
|
||||
const T operator += (const T t)
|
||||
{
|
||||
return (*this = *this + t);
|
||||
}
|
||||
|
||||
const T operator -= (const T t)
|
||||
{
|
||||
return (*this = *this - t);
|
||||
}
|
||||
|
||||
const T operator *= (const T t)
|
||||
{
|
||||
return (*this = *this * t);
|
||||
}
|
||||
|
||||
const T operator /= (const T t)
|
||||
{
|
||||
return (*this = *this / t);
|
||||
}
|
||||
|
||||
const T operator %= (const T t)
|
||||
{
|
||||
return (*this = *this % t);
|
||||
}
|
||||
|
||||
BigEndian<T> operator ++ (int)
|
||||
{
|
||||
BigEndian<T> tmp(*this);
|
||||
operator ++ ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
BigEndian<T> & operator ++ ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
++bytes[sizeof(T) - 1 - i];
|
||||
if (bytes[sizeof(T) - 1 - i] != 0)
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
|
||||
BigEndian<T> operator -- (int)
|
||||
{
|
||||
BigEndian<T> tmp(*this);
|
||||
operator -- ();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
BigEndian<T> & operator -- ()
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(T); i++)
|
||||
{
|
||||
--bytes[sizeof(T) - 1 - i];
|
||||
if (bytes[sizeof(T) - 1 - i] != (T)(-1))
|
||||
break;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif // LITTLEBIGENDIAN_H
|
221
libi2pd/Log.cpp
Normal file
221
libi2pd/Log.cpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016, 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"
|
||||
|
||||
namespace i2p {
|
||||
namespace log {
|
||||
static Log logger;
|
||||
/**
|
||||
* @brief Maps our loglevel to their symbolic name
|
||||
*/
|
||||
static const char * g_LogLevelStr[eNumLogLevels] =
|
||||
{
|
||||
"error", // eLogError
|
||||
"warn", // eLogWarn
|
||||
"info", // eLogInfo
|
||||
"debug" // eLogDebug
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Colorize log output -- array of terminal control sequences
|
||||
* @note Using ISO 6429 (ANSI) color sequences
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
static const char *LogMsgColors[] = { "", "", "", "", "" };
|
||||
#else /* UNIX */
|
||||
static const char *LogMsgColors[] = {
|
||||
[eLogError] = "\033[1;31m", /* red */
|
||||
[eLogWarning] = "\033[1;33m", /* yellow */
|
||||
[eLogInfo] = "\033[1;36m", /* cyan */
|
||||
[eLogDebug] = "\033[1;34m", /* blue */
|
||||
[eNumLogLevels] = "\033[0m", /* reset */
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
/**
|
||||
* @brief Maps our log levels to syslog one
|
||||
* @return syslog priority LOG_*, as defined in syslog.h
|
||||
*/
|
||||
static inline int GetSyslogPrio (enum LogLevel l) {
|
||||
int priority = LOG_DEBUG;
|
||||
switch (l) {
|
||||
case eLogError : priority = LOG_ERR; break;
|
||||
case eLogWarning : priority = LOG_WARNING; break;
|
||||
case eLogInfo : priority = LOG_INFO; break;
|
||||
case eLogDebug : priority = LOG_DEBUG; break;
|
||||
default : priority = LOG_DEBUG; break;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
#endif
|
||||
|
||||
Log::Log():
|
||||
m_Destination(eLogStdout), m_MinLevel(eLogInfo),
|
||||
m_LogStream (nullptr), m_Logfile(""), m_HasColors(true),
|
||||
m_IsRunning (false), m_Thread (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Log::~Log ()
|
||||
{
|
||||
delete m_Thread;
|
||||
}
|
||||
|
||||
void Log::Start ()
|
||||
{
|
||||
if (!m_IsRunning)
|
||||
{
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&Log::Run, this));
|
||||
}
|
||||
}
|
||||
|
||||
void Log::Stop ()
|
||||
{
|
||||
switch (m_Destination)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
case eLogSyslog :
|
||||
closelog();
|
||||
break;
|
||||
#endif
|
||||
case eLogFile:
|
||||
case eLogStream:
|
||||
if (m_LogStream) m_LogStream->flush();
|
||||
break;
|
||||
default:
|
||||
/* do nothing */
|
||||
break;
|
||||
}
|
||||
m_IsRunning = false;
|
||||
m_Queue.WakeUp ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Log::SetLogLevel (const std::string& level) {
|
||||
if (level == "error") { m_MinLevel = eLogError; }
|
||||
else if (level == "warn") { m_MinLevel = eLogWarning; }
|
||||
else if (level == "info") { m_MinLevel = eLogInfo; }
|
||||
else if (level == "debug") { m_MinLevel = eLogDebug; }
|
||||
else {
|
||||
LogPrint(eLogError, "Log: unknown loglevel: ", level);
|
||||
return;
|
||||
}
|
||||
LogPrint(eLogInfo, "Log: min messages level set to ", level);
|
||||
}
|
||||
|
||||
const char * Log::TimeAsString(std::time_t t) {
|
||||
if (t != m_LastTimestamp) {
|
||||
strftime(m_LastDateTime, sizeof(m_LastDateTime), "%H:%M:%S", localtime(&t));
|
||||
m_LastTimestamp = t;
|
||||
}
|
||||
return m_LastDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @note This function better to be run in separate thread due to disk i/o.
|
||||
* Unfortunately, with current startup process with late fork() this
|
||||
* will give us nothing but pain. Maybe later. See in NetDb as example.
|
||||
*/
|
||||
void Log::Process(std::shared_ptr<LogMsg> msg)
|
||||
{
|
||||
if (!msg) return;
|
||||
std::hash<std::thread::id> hasher;
|
||||
unsigned short short_tid;
|
||||
short_tid = (short) (hasher(msg->tid) % 1000);
|
||||
switch (m_Destination) {
|
||||
#ifndef _WIN32
|
||||
case eLogSyslog:
|
||||
syslog(GetSyslogPrio(msg->level), "[%03u] %s", short_tid, msg->text.c_str());
|
||||
break;
|
||||
#endif
|
||||
case eLogFile:
|
||||
case eLogStream:
|
||||
if (m_LogStream)
|
||||
*m_LogStream << TimeAsString(msg->timestamp)
|
||||
<< "@" << short_tid
|
||||
<< "/" << g_LogLevelStr[msg->level]
|
||||
<< " - " << msg->text << std::endl;
|
||||
break;
|
||||
case eLogStdout:
|
||||
default:
|
||||
std::cout << TimeAsString(msg->timestamp)
|
||||
<< "@" << short_tid
|
||||
<< "/" << LogMsgColors[msg->level] << g_LogLevelStr[msg->level] << LogMsgColors[eNumLogLevels]
|
||||
<< " - " << msg->text << std::endl;
|
||||
break;
|
||||
} // switch
|
||||
}
|
||||
|
||||
void Log::Run ()
|
||||
{
|
||||
Reopen ();
|
||||
while (m_IsRunning)
|
||||
{
|
||||
std::shared_ptr<LogMsg> msg;
|
||||
while (msg = m_Queue.Get ())
|
||||
Process (msg);
|
||||
if (m_LogStream) m_LogStream->flush();
|
||||
if (m_IsRunning)
|
||||
m_Queue.Wait ();
|
||||
}
|
||||
}
|
||||
|
||||
void Log::Append(std::shared_ptr<i2p::log::LogMsg> & msg)
|
||||
{
|
||||
m_Queue.Put(msg);
|
||||
}
|
||||
|
||||
void Log::SendTo (const std::string& path)
|
||||
{
|
||||
if (m_LogStream) m_LogStream = nullptr; // close previous
|
||||
auto flags = std::ofstream::out | std::ofstream::app;
|
||||
auto os = std::make_shared<std::ofstream> (path, flags);
|
||||
if (os->is_open ())
|
||||
{
|
||||
m_HasColors = false;
|
||||
m_Logfile = path;
|
||||
m_Destination = eLogFile;
|
||||
m_LogStream = os;
|
||||
return;
|
||||
}
|
||||
LogPrint(eLogError, "Log: can't open file ", path);
|
||||
}
|
||||
|
||||
void Log::SendTo (std::shared_ptr<std::ostream> os) {
|
||||
m_HasColors = false;
|
||||
m_Destination = eLogStream;
|
||||
m_LogStream = os;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
void Log::SendTo(const char *name, int facility) {
|
||||
m_HasColors = false;
|
||||
m_Destination = eLogSyslog;
|
||||
m_LogStream = nullptr;
|
||||
openlog(name, LOG_CONS | LOG_PID, facility);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Log::Reopen() {
|
||||
if (m_Destination == eLogFile)
|
||||
SendTo(m_Logfile);
|
||||
}
|
||||
|
||||
Log & Logger() {
|
||||
return logger;
|
||||
}
|
||||
} // log
|
||||
} // i2p
|
186
libi2pd/Log.h
Normal file
186
libi2pd/Log.h
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016, 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 LOG_H__
|
||||
#define LOG_H__
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "Queue.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <syslog.h>
|
||||
#endif
|
||||
|
||||
enum LogLevel
|
||||
{
|
||||
eLogError = 0,
|
||||
eLogWarning,
|
||||
eLogInfo,
|
||||
eLogDebug,
|
||||
eNumLogLevels
|
||||
};
|
||||
|
||||
enum LogType {
|
||||
eLogStdout = 0,
|
||||
eLogStream,
|
||||
eLogFile,
|
||||
#ifndef _WIN32
|
||||
eLogSyslog,
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace i2p {
|
||||
namespace log {
|
||||
|
||||
struct LogMsg; /* forward declaration */
|
||||
|
||||
class Log
|
||||
{
|
||||
private:
|
||||
|
||||
enum LogType m_Destination;
|
||||
enum LogLevel m_MinLevel;
|
||||
std::shared_ptr<std::ostream> m_LogStream;
|
||||
std::string m_Logfile;
|
||||
std::time_t m_LastTimestamp;
|
||||
char m_LastDateTime[64];
|
||||
i2p::util::Queue<std::shared_ptr<LogMsg> > m_Queue;
|
||||
bool m_HasColors;
|
||||
volatile bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
|
||||
private:
|
||||
|
||||
/** prevent making copies */
|
||||
Log (const Log &);
|
||||
const Log& operator=(const Log&);
|
||||
|
||||
void Run ();
|
||||
void Process (std::shared_ptr<LogMsg> msg);
|
||||
|
||||
/**
|
||||
* @brief Makes formatted string from unix timestamp
|
||||
* @param ts Second since epoch
|
||||
*
|
||||
* This function internally caches the result for last provided value
|
||||
*/
|
||||
const char * TimeAsString(std::time_t ts);
|
||||
|
||||
public:
|
||||
|
||||
Log ();
|
||||
~Log ();
|
||||
|
||||
LogType GetLogType () { return m_Destination; };
|
||||
LogLevel GetLogLevel () { return m_MinLevel; };
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
/**
|
||||
* @brief Sets minimal allowed level for log messages
|
||||
* @param level String with wanted minimal msg level
|
||||
*/
|
||||
void SetLogLevel (const std::string& level);
|
||||
|
||||
/**
|
||||
* @brief Sets log destination to logfile
|
||||
* @param path Path to logfile
|
||||
*/
|
||||
void SendTo (const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief Sets log destination to given output stream
|
||||
* @param os Output stream
|
||||
*/
|
||||
void SendTo (std::shared_ptr<std::ostream> os);
|
||||
|
||||
#ifndef _WIN32
|
||||
/**
|
||||
* @brief Sets log destination to syslog
|
||||
* @param name Wanted program name
|
||||
* @param facility Wanted log category
|
||||
*/
|
||||
void SendTo (const char *name, int facility);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Format log message and write to output stream/syslog
|
||||
* @param msg Pointer to processed message
|
||||
*/
|
||||
void Append(std::shared_ptr<i2p::log::LogMsg> &);
|
||||
|
||||
/** @brief Reopen log file */
|
||||
void Reopen();
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct LogMsg
|
||||
* @brief Log message container
|
||||
*
|
||||
* We creating it somewhere with LogPrint(),
|
||||
* then put in MsgQueue for later processing.
|
||||
*/
|
||||
struct LogMsg {
|
||||
std::time_t timestamp;
|
||||
std::string text; /**< message text as single string */
|
||||
LogLevel level; /**< message level */
|
||||
std::thread::id tid; /**< id of thread that generated message */
|
||||
|
||||
LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {};
|
||||
};
|
||||
|
||||
Log & Logger();
|
||||
} // log
|
||||
}
|
||||
|
||||
/** internal usage only -- folding args array to single string */
|
||||
template<typename TValue>
|
||||
void LogPrint (std::stringstream& s, TValue&& arg) noexcept
|
||||
{
|
||||
s << std::forward<TValue>(arg);
|
||||
}
|
||||
|
||||
/** internal usage only -- folding args array to single string */
|
||||
template<typename TValue, typename... TArgs>
|
||||
void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept
|
||||
{
|
||||
LogPrint (s, std::forward<TValue>(arg));
|
||||
LogPrint (s, std::forward<TArgs>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create log message and send it to queue
|
||||
* @param level Message level (eLogError, eLogInfo, ...)
|
||||
* @param args Array of message parts
|
||||
*/
|
||||
template<typename... TArgs>
|
||||
void LogPrint (LogLevel level, TArgs&&... args) noexcept
|
||||
{
|
||||
i2p::log::Log &log = i2p::log::Logger();
|
||||
if (level > log.GetLogLevel ())
|
||||
return;
|
||||
|
||||
// fold message to single string
|
||||
std::stringstream ss("");
|
||||
|
||||
LogPrint (ss, std::forward<TArgs>(args)...);
|
||||
|
||||
auto msg = std::make_shared<i2p::log::LogMsg>(level, std::time(nullptr), ss.str());
|
||||
msg->tid = std::this_thread::get_id();
|
||||
log.Append(msg);
|
||||
}
|
||||
|
||||
#endif // LOG_H__
|
1076
libi2pd/NTCPSession.cpp
Normal file
1076
libi2pd/NTCPSession.cpp
Normal file
File diff suppressed because it is too large
Load diff
181
libi2pd/NTCPSession.h
Normal file
181
libi2pd/NTCPSession.h
Normal file
|
@ -0,0 +1,181 @@
|
|||
#ifndef NTCP_SESSION_H__
|
||||
#define NTCP_SESSION_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Crypto.h"
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TransportSession.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
struct NTCPPhase1
|
||||
{
|
||||
uint8_t pubKey[256];
|
||||
uint8_t HXxorHI[32];
|
||||
};
|
||||
|
||||
struct NTCPPhase2
|
||||
{
|
||||
uint8_t pubKey[256];
|
||||
struct
|
||||
{
|
||||
uint8_t hxy[32];
|
||||
uint8_t timestamp[4];
|
||||
uint8_t filler[12];
|
||||
} encrypted;
|
||||
};
|
||||
|
||||
const size_t NTCP_MAX_MESSAGE_SIZE = 16384;
|
||||
const size_t NTCP_BUFFER_SIZE = 1028; // fits 1 tunnel data message
|
||||
const int NTCP_CONNECT_TIMEOUT = 5; // 5 seconds
|
||||
const int NTCP_ESTABLISH_TIMEOUT = 10; // 10 seconds
|
||||
const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes
|
||||
const int NTCP_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
|
||||
const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448
|
||||
const int NTCP_CLOCK_SKEW = 60; // in seconds
|
||||
const int NTCP_MAX_OUTGOING_QUEUE_SIZE = 200; // how many messages we can queue up
|
||||
|
||||
class NTCPServer;
|
||||
class NTCPSession: public TransportSession, public std::enable_shared_from_this<NTCPSession>
|
||||
{
|
||||
public:
|
||||
|
||||
NTCPSession (NTCPServer& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter = nullptr);
|
||||
~NTCPSession ();
|
||||
void Terminate ();
|
||||
void Done ();
|
||||
|
||||
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
|
||||
bool IsEstablished () const { return m_IsEstablished; };
|
||||
bool IsTerminated () const { return m_IsTerminated; };
|
||||
|
||||
void ClientLogin ();
|
||||
void ServerLogin ();
|
||||
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||
|
||||
private:
|
||||
|
||||
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
|
||||
void Connected ();
|
||||
void SendTimeSyncMessage ();
|
||||
void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; }
|
||||
|
||||
void CreateAESKey (uint8_t * pubKey);
|
||||
|
||||
// client
|
||||
void SendPhase3 ();
|
||||
void HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandlePhase2 ();
|
||||
void HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
|
||||
void HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA);
|
||||
|
||||
//server
|
||||
void SendPhase2 ();
|
||||
void SendPhase4 (uint32_t tsA, uint32_t tsB);
|
||||
void HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
void HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
|
||||
void HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB);
|
||||
void HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen);
|
||||
void HandlePhase3 (uint32_t tsB, size_t paddingLen);
|
||||
void HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
|
||||
// common
|
||||
void Receive ();
|
||||
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
|
||||
bool DecryptNextBlock (const uint8_t * encrypted);
|
||||
|
||||
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr<I2NPMessage> msg);
|
||||
void Send (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<I2NPMessage> > msgs);
|
||||
|
||||
private:
|
||||
|
||||
NTCPServer& m_Server;
|
||||
boost::asio::ip::tcp::socket m_Socket;
|
||||
bool m_IsEstablished, m_IsTerminated;
|
||||
|
||||
i2p::crypto::CBCDecryption m_Decryption;
|
||||
i2p::crypto::CBCEncryption m_Encryption;
|
||||
|
||||
struct Establisher
|
||||
{
|
||||
NTCPPhase1 phase1;
|
||||
NTCPPhase2 phase2;
|
||||
} * m_Establisher;
|
||||
|
||||
i2p::crypto::AESAlignedBuffer<NTCP_BUFFER_SIZE + 16> m_ReceiveBuffer;
|
||||
i2p::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer;
|
||||
int m_ReceiveBufferOffset;
|
||||
|
||||
std::shared_ptr<I2NPMessage> m_NextMessage;
|
||||
size_t m_NextMessageOffset;
|
||||
i2p::I2NPMessagesHandler m_Handler;
|
||||
|
||||
bool m_IsSending;
|
||||
std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
|
||||
};
|
||||
|
||||
// TODO: move to NTCP.h/.cpp
|
||||
class NTCPServer
|
||||
{
|
||||
public:
|
||||
|
||||
NTCPServer ();
|
||||
~NTCPServer ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
bool AddNTCPSession (std::shared_ptr<NTCPSession> session);
|
||||
void RemoveNTCPSession (std::shared_ptr<NTCPSession> session);
|
||||
std::shared_ptr<NTCPSession> FindNTCPSession (const i2p::data::IdentHash& ident);
|
||||
void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn);
|
||||
|
||||
bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; };
|
||||
bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; };
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void HandleAccept (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
|
||||
void HandleAcceptV6 (std::shared_ptr<NTCPSession> conn, const boost::system::error_code& error);
|
||||
|
||||
void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn, std::shared_ptr<boost::asio::deadline_timer> timer);
|
||||
|
||||
// timer
|
||||
void ScheduleTermination ();
|
||||
void HandleTerminationTimer (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service m_Service;
|
||||
boost::asio::io_service::work m_Work;
|
||||
boost::asio::deadline_timer m_TerminationTimer;
|
||||
boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor;
|
||||
std::map<i2p::data::IdentHash, std::shared_ptr<NTCPSession> > m_NTCPSessions; // access from m_Thread only
|
||||
std::list<std::shared_ptr<NTCPSession> > m_PendingIncomingSessions;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP/I2PControl
|
||||
const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
1174
libi2pd/NetDb.cpp
Normal file
1174
libi2pd/NetDb.cpp
Normal file
File diff suppressed because it is too large
Load diff
155
libi2pd/NetDb.h
Normal file
155
libi2pd/NetDb.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
#ifndef NETDB_H__
|
||||
#define NETDB_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include "Base.h"
|
||||
#include "Gzip.h"
|
||||
#include "FS.h"
|
||||
#include "Queue.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "Tunnel.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "Reseed.h"
|
||||
#include "NetDbRequests.h"
|
||||
#include "Family.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
const int NETDB_MIN_ROUTERS = 90;
|
||||
const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60*60; // 1 hour, in seconds
|
||||
const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60;
|
||||
const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours
|
||||
const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours
|
||||
const int NETDB_PUBLISH_INTERVAL = 60*40;
|
||||
|
||||
/** function for visiting a leaseset stored in a floodfill */
|
||||
typedef std::function<void(const IdentHash, std::shared_ptr<LeaseSet>)> LeaseSetVisitor;
|
||||
|
||||
/** function for visiting a router info we have locally */
|
||||
typedef std::function<void(std::shared_ptr<const i2p::data::RouterInfo>)> RouterInfoVisitor;
|
||||
|
||||
/** function for visiting a router info and determining if we want to use it */
|
||||
typedef std::function<bool(std::shared_ptr<const i2p::data::RouterInfo>)> RouterInfoFilter;
|
||||
|
||||
class NetDb
|
||||
{
|
||||
public:
|
||||
|
||||
NetDb ();
|
||||
~NetDb ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
bool AddRouterInfo (const uint8_t * buf, int len);
|
||||
bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len);
|
||||
bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||
std::shared_ptr<RouterInfo> FindRouter (const IdentHash& ident) const;
|
||||
std::shared_ptr<LeaseSet> FindLeaseSet (const IdentHash& destination) const;
|
||||
std::shared_ptr<RouterProfile> FindRouterProfile (const IdentHash& ident) const;
|
||||
|
||||
void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr);
|
||||
void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr);
|
||||
|
||||
void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||
void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||
void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||
|
||||
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
|
||||
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
|
||||
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith) const;
|
||||
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter (bool v4only = true) const;
|
||||
std::shared_ptr<const RouterInfo> GetRandomIntroducer () const;
|
||||
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
|
||||
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
|
||||
std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
|
||||
std::shared_ptr<const RouterInfo> GetClosestNonFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
|
||||
std::shared_ptr<const RouterInfo> GetRandomRouterInFamily(const std::string & fam) const;
|
||||
void SetUnreachable (const IdentHash& ident, bool unreachable);
|
||||
|
||||
void PostI2NPMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||
|
||||
/** set hidden mode, aka don't publish our RI to netdb and don't explore */
|
||||
void SetHidden(bool hide);
|
||||
|
||||
void Reseed ();
|
||||
Families& GetFamilies () { return m_Families; };
|
||||
|
||||
// for web interface
|
||||
int GetNumRouters () const { return m_RouterInfos.size (); };
|
||||
int GetNumFloodfills () const { return m_Floodfills.size (); };
|
||||
int GetNumLeaseSets () const { return m_LeaseSets.size (); };
|
||||
|
||||
/** visit all lease sets we currently store */
|
||||
void VisitLeaseSets(LeaseSetVisitor v);
|
||||
/** visit all router infos we have currently on disk, usually insanely expensive, does not access in memory RI */
|
||||
void VisitStoredRouterInfos(RouterInfoVisitor v);
|
||||
/** visit all router infos we have loaded in memory, cheaper than VisitLocalRouterInfos but locks access while visiting */
|
||||
void VisitRouterInfos(RouterInfoVisitor v);
|
||||
/** visit N random router that match using filter, then visit them with a visitor, return number of RouterInfos that were visited */
|
||||
size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n);
|
||||
|
||||
void ClearRouterInfos () { m_RouterInfos.clear (); };
|
||||
|
||||
private:
|
||||
|
||||
void Load ();
|
||||
bool LoadRouterInfo (const std::string & path);
|
||||
void SaveUpdated ();
|
||||
void Run (); // exploratory thread
|
||||
void Explore (int numDestinations);
|
||||
void Publish ();
|
||||
void ManageLeaseSets ();
|
||||
void ManageRequests ();
|
||||
|
||||
void ReseedFromFloodfill(const RouterInfo & ri, int numRouters=40, int numFloodfills=20);
|
||||
|
||||
template<typename Filter>
|
||||
std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
|
||||
|
||||
private:
|
||||
|
||||
mutable std::mutex m_LeaseSetsMutex;
|
||||
std::map<IdentHash, std::shared_ptr<LeaseSet> > m_LeaseSets;
|
||||
mutable std::mutex m_RouterInfosMutex;
|
||||
std::map<IdentHash, std::shared_ptr<RouterInfo> > m_RouterInfos;
|
||||
mutable std::mutex m_FloodfillsMutex;
|
||||
std::list<std::shared_ptr<RouterInfo> > m_Floodfills;
|
||||
|
||||
bool m_IsRunning;
|
||||
uint64_t m_LastLoad;
|
||||
std::thread * m_Thread;
|
||||
i2p::util::Queue<std::shared_ptr<const I2NPMessage> > m_Queue; // of I2NPDatabaseStoreMsg
|
||||
|
||||
GzipInflator m_Inflator;
|
||||
Reseeder * m_Reseeder;
|
||||
Families m_Families;
|
||||
i2p::fs::HashedStorage m_Storage;
|
||||
|
||||
friend class NetDbRequests;
|
||||
NetDbRequests m_Requests;
|
||||
|
||||
/** router info we are bootstrapping from or nullptr if we are not currently doing that*/
|
||||
std::shared_ptr<RouterInfo> m_FloodfillBootstrap;
|
||||
|
||||
|
||||
/** true if in hidden mode */
|
||||
bool m_HiddenMode;
|
||||
};
|
||||
|
||||
extern NetDb netdb;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
161
libi2pd/NetDbRequests.cpp
Normal file
161
libi2pd/NetDbRequests.cpp
Normal file
|
@ -0,0 +1,161 @@
|
|||
#include "Log.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Transports.h"
|
||||
#include "NetDb.h"
|
||||
#include "NetDbRequests.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router,
|
||||
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
|
||||
{
|
||||
std::shared_ptr<I2NPMessage> msg;
|
||||
if(replyTunnel)
|
||||
msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
|
||||
replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory,
|
||||
&m_ExcludedPeers);
|
||||
else
|
||||
msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers);
|
||||
if(router)
|
||||
m_ExcludedPeers.insert (router->GetIdentHash ());
|
||||
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (const IdentHash& floodfill)
|
||||
{
|
||||
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
|
||||
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
|
||||
m_ExcludedPeers.insert (floodfill);
|
||||
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
return msg;
|
||||
}
|
||||
|
||||
void RequestedDestination::ClearExcludedPeers ()
|
||||
{
|
||||
m_ExcludedPeers.clear ();
|
||||
}
|
||||
|
||||
void RequestedDestination::Success (std::shared_ptr<RouterInfo> r)
|
||||
{
|
||||
if (m_RequestComplete)
|
||||
{
|
||||
m_RequestComplete (r);
|
||||
m_RequestComplete = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RequestedDestination::Fail ()
|
||||
{
|
||||
if (m_RequestComplete)
|
||||
{
|
||||
m_RequestComplete (nullptr);
|
||||
m_RequestComplete = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void NetDbRequests::Start ()
|
||||
{
|
||||
}
|
||||
|
||||
void NetDbRequests::Stop ()
|
||||
{
|
||||
m_RequestedDestinations.clear ();
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete)
|
||||
{
|
||||
// request RouterInfo directly
|
||||
auto dest = std::make_shared<RequestedDestination> (destination, isExploratory);
|
||||
dest->SetRequestComplete (requestComplete);
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||
if (!m_RequestedDestinations.insert (std::make_pair (destination, dest)).second) // not inserted
|
||||
return nullptr;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
|
||||
{
|
||||
std::shared_ptr<RequestedDestination> request;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||
auto it = m_RequestedDestinations.find (ident);
|
||||
if (it != m_RequestedDestinations.end ())
|
||||
{
|
||||
request = it->second;
|
||||
m_RequestedDestinations.erase (it);
|
||||
}
|
||||
}
|
||||
if (request)
|
||||
{
|
||||
if (r)
|
||||
request->Success (r);
|
||||
else
|
||||
request->Fail ();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<RequestedDestination> NetDbRequests::FindRequest (const IdentHash& ident) const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||
auto it = m_RequestedDestinations.find (ident);
|
||||
if (it != m_RequestedDestinations.end ())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NetDbRequests::ManageRequests ()
|
||||
{
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
|
||||
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
|
||||
{
|
||||
auto& dest = it->second;
|
||||
bool done = false;
|
||||
if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute
|
||||
{
|
||||
if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds
|
||||
{
|
||||
auto count = dest->GetExcludedPeers ().size ();
|
||||
if (!dest->IsExploratory () && count < 7)
|
||||
{
|
||||
auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
|
||||
auto outbound = pool->GetNextOutboundTunnel ();
|
||||
auto inbound = pool->GetNextInboundTunnel ();
|
||||
auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
|
||||
if (nextFloodfill && outbound && inbound)
|
||||
outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0,
|
||||
dest->CreateRequestMessage (nextFloodfill, inbound));
|
||||
else
|
||||
{
|
||||
done = true;
|
||||
if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels");
|
||||
if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels");
|
||||
if (!nextFloodfill) LogPrint (eLogWarning, "NetDbReq: No more floodfills");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!dest->IsExploratory ())
|
||||
LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts");
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // delete obsolete request
|
||||
done = true;
|
||||
|
||||
if (done)
|
||||
it = m_RequestedDestinations.erase (it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
69
libi2pd/NetDbRequests.h
Normal file
69
libi2pd/NetDbRequests.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifndef NETDB_REQUESTS_H__
|
||||
#define NETDB_REQUESTS_H__
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
class RequestedDestination
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::function<void (std::shared_ptr<RouterInfo>)> RequestComplete;
|
||||
|
||||
RequestedDestination (const IdentHash& destination, bool isExploratory = false):
|
||||
m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {};
|
||||
~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); };
|
||||
|
||||
const IdentHash& GetDestination () const { return m_Destination; };
|
||||
int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); };
|
||||
const std::set<IdentHash>& GetExcludedPeers () { return m_ExcludedPeers; };
|
||||
void ClearExcludedPeers ();
|
||||
bool IsExploratory () const { return m_IsExploratory; };
|
||||
bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); };
|
||||
uint64_t GetCreationTime () const { return m_CreationTime; };
|
||||
std::shared_ptr<I2NPMessage> CreateRequestMessage (std::shared_ptr<const RouterInfo>, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel);
|
||||
std::shared_ptr<I2NPMessage> CreateRequestMessage (const IdentHash& floodfill);
|
||||
|
||||
void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; };
|
||||
bool IsRequestComplete () const { return m_RequestComplete != nullptr; };
|
||||
void Success (std::shared_ptr<RouterInfo> r);
|
||||
void Fail ();
|
||||
|
||||
private:
|
||||
|
||||
IdentHash m_Destination;
|
||||
bool m_IsExploratory;
|
||||
std::set<IdentHash> m_ExcludedPeers;
|
||||
uint64_t m_CreationTime;
|
||||
RequestComplete m_RequestComplete;
|
||||
};
|
||||
|
||||
class NetDbRequests
|
||||
{
|
||||
public:
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr);
|
||||
void RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r);
|
||||
std::shared_ptr<RequestedDestination> FindRequest (const IdentHash& ident) const;
|
||||
void ManageRequests ();
|
||||
|
||||
private:
|
||||
|
||||
mutable std::mutex m_RequestedDestinationsMutex;
|
||||
std::map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
195
libi2pd/Profiling.cpp
Normal file
195
libi2pd/Profiling.cpp
Normal file
|
@ -0,0 +1,195 @@
|
|||
#include <sys/stat.h>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include "Base.h"
|
||||
#include "FS.h"
|
||||
#include "Log.h"
|
||||
#include "Profiling.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
|
||||
|
||||
RouterProfile::RouterProfile ():
|
||||
m_LastUpdateTime (boost::posix_time::second_clock::local_time()),
|
||||
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
|
||||
m_NumTimesTaken (0), m_NumTimesRejected (0)
|
||||
{
|
||||
}
|
||||
|
||||
boost::posix_time::ptime RouterProfile::GetTime () const
|
||||
{
|
||||
return boost::posix_time::second_clock::local_time();
|
||||
}
|
||||
|
||||
void RouterProfile::UpdateTime ()
|
||||
{
|
||||
m_LastUpdateTime = GetTime ();
|
||||
}
|
||||
|
||||
void RouterProfile::Save (const IdentHash& identHash)
|
||||
{
|
||||
// fill sections
|
||||
boost::property_tree::ptree participation;
|
||||
participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed);
|
||||
participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined);
|
||||
participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied);
|
||||
boost::property_tree::ptree usage;
|
||||
usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken);
|
||||
usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected);
|
||||
// fill property tree
|
||||
boost::property_tree::ptree pt;
|
||||
pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime));
|
||||
pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation);
|
||||
pt.put_child (PEER_PROFILE_SECTION_USAGE, usage);
|
||||
|
||||
// save to file
|
||||
std::string ident = identHash.ToBase64 ();
|
||||
std::string path = m_ProfilesStorage.Path(ident);
|
||||
|
||||
try {
|
||||
boost::property_tree::write_ini (path, pt);
|
||||
} catch (std::exception& ex) {
|
||||
/* boost exception verbose enough */
|
||||
LogPrint (eLogError, "Profiling: ", ex.what ());
|
||||
}
|
||||
}
|
||||
|
||||
void RouterProfile::Load (const IdentHash& identHash)
|
||||
{
|
||||
std::string ident = identHash.ToBase64 ();
|
||||
std::string path = m_ProfilesStorage.Path(ident);
|
||||
boost::property_tree::ptree pt;
|
||||
|
||||
if (!i2p::fs::Exists(path))
|
||||
{
|
||||
LogPrint(eLogWarning, "Profiling: no profile yet for ", ident);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
boost::property_tree::read_ini (path, pt);
|
||||
} catch (std::exception& ex)
|
||||
{
|
||||
/* boost exception verbose enough */
|
||||
LogPrint (eLogError, "Profiling: ", ex.what ());
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
|
||||
if (t.length () > 0)
|
||||
m_LastUpdateTime = boost::posix_time::time_from_string (t);
|
||||
if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
try
|
||||
{
|
||||
// read participations
|
||||
auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION);
|
||||
m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0);
|
||||
m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0);
|
||||
m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0);
|
||||
}
|
||||
catch (boost::property_tree::ptree_bad_path& ex)
|
||||
{
|
||||
LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_PARTICIPATION, " in profile for ", ident);
|
||||
}
|
||||
try
|
||||
{
|
||||
// read usage
|
||||
auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE);
|
||||
m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0);
|
||||
m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0);
|
||||
}
|
||||
catch (boost::property_tree::ptree_bad_path& ex)
|
||||
{
|
||||
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident);
|
||||
}
|
||||
}
|
||||
else
|
||||
*this = RouterProfile ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "Profiling: Can't read profile ", ident, " :", ex.what ());
|
||||
}
|
||||
}
|
||||
|
||||
void RouterProfile::TunnelBuildResponse (uint8_t ret)
|
||||
{
|
||||
UpdateTime ();
|
||||
if (ret > 0)
|
||||
m_NumTunnelsDeclined++;
|
||||
else
|
||||
m_NumTunnelsAgreed++;
|
||||
}
|
||||
|
||||
void RouterProfile::TunnelNonReplied ()
|
||||
{
|
||||
m_NumTunnelsNonReplied++;
|
||||
UpdateTime ();
|
||||
}
|
||||
|
||||
bool RouterProfile::IsLowPartcipationRate () const
|
||||
{
|
||||
return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate
|
||||
}
|
||||
|
||||
bool RouterProfile::IsLowReplyRate () const
|
||||
{
|
||||
auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined;
|
||||
return m_NumTunnelsNonReplied > 10*(total + 1);
|
||||
}
|
||||
|
||||
bool RouterProfile::IsBad ()
|
||||
{
|
||||
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
|
||||
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
|
||||
{
|
||||
// reset profile
|
||||
m_NumTunnelsAgreed = 0;
|
||||
m_NumTunnelsDeclined = 0;
|
||||
m_NumTunnelsNonReplied = 0;
|
||||
isBad = false;
|
||||
}
|
||||
if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++;
|
||||
return isBad;
|
||||
}
|
||||
|
||||
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash)
|
||||
{
|
||||
auto profile = std::make_shared<RouterProfile> ();
|
||||
profile->Load (identHash); // if possible
|
||||
return profile;
|
||||
}
|
||||
|
||||
void InitProfilesStorage ()
|
||||
{
|
||||
m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
|
||||
m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
|
||||
}
|
||||
|
||||
void DeleteObsoleteProfiles ()
|
||||
{
|
||||
struct stat st;
|
||||
std::time_t now = std::time(nullptr);
|
||||
|
||||
std::vector<std::string> files;
|
||||
m_ProfilesStorage.Traverse(files);
|
||||
for (const auto& path: files) {
|
||||
if (stat(path.c_str(), &st) != 0) {
|
||||
LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
|
||||
continue;
|
||||
}
|
||||
if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) {
|
||||
LogPrint(eLogDebug, "Profiling: removing expired peer profile: ", path);
|
||||
i2p::fs::Remove(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
67
libi2pd/Profiling.h
Normal file
67
libi2pd/Profiling.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
#ifndef PROFILING_H__
|
||||
#define PROFILING_H__
|
||||
|
||||
#include <memory>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
// sections
|
||||
const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation";
|
||||
const char PEER_PROFILE_SECTION_USAGE[] = "usage";
|
||||
// params
|
||||
const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime";
|
||||
const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed";
|
||||
const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined";
|
||||
const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied";
|
||||
const char PEER_PROFILE_USAGE_TAKEN[] = "taken";
|
||||
const char PEER_PROFILE_USAGE_REJECTED[] = "rejected";
|
||||
|
||||
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days)
|
||||
|
||||
class RouterProfile
|
||||
{
|
||||
public:
|
||||
|
||||
RouterProfile ();
|
||||
RouterProfile& operator= (const RouterProfile& ) = default;
|
||||
|
||||
void Save (const IdentHash& identHash);
|
||||
void Load (const IdentHash& identHash);
|
||||
|
||||
bool IsBad ();
|
||||
|
||||
void TunnelBuildResponse (uint8_t ret);
|
||||
void TunnelNonReplied ();
|
||||
|
||||
private:
|
||||
|
||||
boost::posix_time::ptime GetTime () const;
|
||||
void UpdateTime ();
|
||||
|
||||
bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
|
||||
bool IsLowPartcipationRate () const;
|
||||
bool IsLowReplyRate () const;
|
||||
|
||||
private:
|
||||
|
||||
boost::posix_time::ptime m_LastUpdateTime;
|
||||
// participation
|
||||
uint32_t m_NumTunnelsAgreed;
|
||||
uint32_t m_NumTunnelsDeclined;
|
||||
uint32_t m_NumTunnelsNonReplied;
|
||||
// usage
|
||||
uint32_t m_NumTimesTaken;
|
||||
uint32_t m_NumTimesRejected;
|
||||
};
|
||||
|
||||
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
|
||||
void InitProfilesStorage ();
|
||||
void DeleteObsoleteProfiles ();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
125
libi2pd/Queue.h
Normal file
125
libi2pd/Queue.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
#ifndef QUEUE_H__
|
||||
#define QUEUE_H__
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
template<typename Element>
|
||||
class Queue
|
||||
{
|
||||
public:
|
||||
|
||||
void Put (Element e)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
m_Queue.push (std::move(e));
|
||||
m_NonEmpty.notify_one ();
|
||||
}
|
||||
|
||||
template<template<typename, typename...>class Container, typename... R>
|
||||
void Put (const Container<Element, R...>& vec)
|
||||
{
|
||||
if (!vec.empty ())
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
for (const auto& it: vec)
|
||||
m_Queue.push (it);
|
||||
m_NonEmpty.notify_one ();
|
||||
}
|
||||
}
|
||||
|
||||
Element GetNext ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
auto el = GetNonThreadSafe ();
|
||||
if (!el)
|
||||
{
|
||||
m_NonEmpty.wait (l);
|
||||
el = GetNonThreadSafe ();
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
Element GetNextWithTimeout (int usec)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
auto el = GetNonThreadSafe ();
|
||||
if (!el)
|
||||
{
|
||||
m_NonEmpty.wait_for (l, std::chrono::milliseconds (usec));
|
||||
el = GetNonThreadSafe ();
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
void Wait ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
m_NonEmpty.wait (l);
|
||||
}
|
||||
|
||||
bool Wait (int sec, int usec)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return m_NonEmpty.wait_for (l, std::chrono::seconds (sec) + std::chrono::milliseconds (usec)) != std::cv_status::timeout;
|
||||
}
|
||||
|
||||
bool IsEmpty ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return m_Queue.empty ();
|
||||
}
|
||||
|
||||
int GetSize ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return m_Queue.size ();
|
||||
}
|
||||
|
||||
void WakeUp () { m_NonEmpty.notify_all (); };
|
||||
|
||||
Element Get ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return GetNonThreadSafe ();
|
||||
}
|
||||
|
||||
Element Peek ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_QueueMutex);
|
||||
return GetNonThreadSafe (true);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Element GetNonThreadSafe (bool peek = false)
|
||||
{
|
||||
if (!m_Queue.empty ())
|
||||
{
|
||||
auto el = m_Queue.front ();
|
||||
if (!peek)
|
||||
m_Queue.pop ();
|
||||
return el;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::queue<Element> m_Queue;
|
||||
std::mutex m_QueueMutex;
|
||||
std::condition_variable m_NonEmpty;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
567
libi2pd/Reseed.cpp
Normal file
567
libi2pd/Reseed.cpp
Normal file
|
@ -0,0 +1,567 @@
|
|||
#include <string.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "Crypto.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Reseed.h"
|
||||
#include "FS.h"
|
||||
#include "Log.h"
|
||||
#include "Identity.h"
|
||||
#include "NetDb.h"
|
||||
#include "HTTP.h"
|
||||
#include "util.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
|
||||
Reseeder::Reseeder()
|
||||
{
|
||||
}
|
||||
|
||||
Reseeder::~Reseeder()
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief tries to bootstrap into I2P network (from local files and servers, with respect of options)
|
||||
*/
|
||||
void Reseeder::Bootstrap ()
|
||||
{
|
||||
std::string su3FileName; i2p::config::GetOption("reseed.file", su3FileName);
|
||||
std::string zipFileName; i2p::config::GetOption("reseed.zipfile", zipFileName);
|
||||
|
||||
if (su3FileName.length() > 0) // bootstrap from SU3 file or URL
|
||||
{
|
||||
int num;
|
||||
if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://")
|
||||
{
|
||||
num = ReseedFromSU3Url (su3FileName); // from https URL
|
||||
}
|
||||
else
|
||||
{
|
||||
num = ProcessSU3File (su3FileName.c_str ());
|
||||
}
|
||||
if (num == 0)
|
||||
LogPrint (eLogWarning, "Reseed: failed to reseed from ", su3FileName);
|
||||
}
|
||||
else if (zipFileName.length() > 0) // bootstrap from ZIP file
|
||||
{
|
||||
int num = ProcessZIPFile (zipFileName.c_str ());
|
||||
if (num == 0)
|
||||
LogPrint (eLogWarning, "Reseed: failed to reseed from ", zipFileName);
|
||||
}
|
||||
else // bootstrap from reseed servers
|
||||
{
|
||||
int num = ReseedFromServers ();
|
||||
if (num == 0)
|
||||
LogPrint (eLogWarning, "Reseed: failed to reseed from servers");
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief bootstrap from random server, retry 10 times
|
||||
* @return number of entries added to netDb
|
||||
*/
|
||||
int Reseeder::ReseedFromServers ()
|
||||
{
|
||||
std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs);
|
||||
std::vector<std::string> httpsReseedHostList;
|
||||
boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on);
|
||||
|
||||
if (reseedURLs.length () == 0)
|
||||
{
|
||||
LogPrint (eLogWarning, "Reseed: No reseed servers specified");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reseedRetries = 0;
|
||||
while (reseedRetries < 10)
|
||||
{
|
||||
auto ind = rand () % httpsReseedHostList.size ();
|
||||
std::string reseedUrl = httpsReseedHostList[ind] + "i2pseeds.su3";
|
||||
auto num = ReseedFromSU3Url (reseedUrl);
|
||||
if (num > 0) return num; // success
|
||||
reseedRetries++;
|
||||
}
|
||||
LogPrint (eLogWarning, "Reseed: failed to reseed from servers after 10 attempts");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @brief bootstrap from HTTPS URL with SU3 file
|
||||
* @param url
|
||||
* @return number of entries added to netDb
|
||||
*/
|
||||
int Reseeder::ReseedFromSU3Url (const std::string& url)
|
||||
{
|
||||
LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", url);
|
||||
std::string su3 = HttpsRequest (url);
|
||||
if (su3.length () > 0)
|
||||
{
|
||||
std::stringstream s(su3);
|
||||
return ProcessSU3Stream (s);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Reseed: SU3 download failed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Reseeder::ProcessSU3File (const char * filename)
|
||||
{
|
||||
std::ifstream s(filename, std::ifstream::binary);
|
||||
if (s.is_open ())
|
||||
return ProcessSU3Stream (s);
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: Can't open file ", filename);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Reseeder::ProcessZIPFile (const char * filename)
|
||||
{
|
||||
std::ifstream s(filename, std::ifstream::binary);
|
||||
if (s.is_open ())
|
||||
{
|
||||
s.seekg (0, std::ios::end);
|
||||
auto len = s.tellg ();
|
||||
s.seekg (0, std::ios::beg);
|
||||
return ProcessZIPStream (s, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: Can't open file ", filename);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char SU3_MAGIC_NUMBER[]="I2Psu3";
|
||||
int Reseeder::ProcessSU3Stream (std::istream& s)
|
||||
{
|
||||
char magicNumber[7];
|
||||
s.read (magicNumber, 7); // magic number and zero byte 6
|
||||
if (strcmp (magicNumber, SU3_MAGIC_NUMBER))
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: Unexpected SU3 magic number");
|
||||
return 0;
|
||||
}
|
||||
s.seekg (1, std::ios::cur); // su3 file format version
|
||||
SigningKeyType signatureType;
|
||||
s.read ((char *)&signatureType, 2); // signature type
|
||||
signatureType = be16toh (signatureType);
|
||||
uint16_t signatureLength;
|
||||
s.read ((char *)&signatureLength, 2); // signature length
|
||||
signatureLength = be16toh (signatureLength);
|
||||
s.seekg (1, std::ios::cur); // unused
|
||||
uint8_t versionLength;
|
||||
s.read ((char *)&versionLength, 1); // version length
|
||||
s.seekg (1, std::ios::cur); // unused
|
||||
uint8_t signerIDLength;
|
||||
s.read ((char *)&signerIDLength, 1); // signer ID length
|
||||
uint64_t contentLength;
|
||||
s.read ((char *)&contentLength, 8); // content length
|
||||
contentLength = be64toh (contentLength);
|
||||
s.seekg (1, std::ios::cur); // unused
|
||||
uint8_t fileType;
|
||||
s.read ((char *)&fileType, 1); // file type
|
||||
if (fileType != 0x00) // zip file
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType);
|
||||
return 0;
|
||||
}
|
||||
s.seekg (1, std::ios::cur); // unused
|
||||
uint8_t contentType;
|
||||
s.read ((char *)&contentType, 1); // content type
|
||||
if (contentType != 0x03) // reseed data
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType);
|
||||
return 0;
|
||||
}
|
||||
s.seekg (12, std::ios::cur); // unused
|
||||
|
||||
s.seekg (versionLength, std::ios::cur); // skip version
|
||||
char signerID[256];
|
||||
s.read (signerID, signerIDLength); // signerID
|
||||
signerID[signerIDLength] = 0;
|
||||
|
||||
bool verify; i2p::config::GetOption("reseed.verify", verify);
|
||||
if (verify)
|
||||
{
|
||||
//try to verify signature
|
||||
auto it = m_SigningKeys.find (signerID);
|
||||
if (it != m_SigningKeys.end ())
|
||||
{
|
||||
// TODO: implement all signature types
|
||||
if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096)
|
||||
{
|
||||
size_t pos = s.tellg ();
|
||||
size_t tbsLen = pos + contentLength;
|
||||
uint8_t * tbs = new uint8_t[tbsLen];
|
||||
s.seekg (0, std::ios::beg);
|
||||
s.read ((char *)tbs, tbsLen);
|
||||
uint8_t * signature = new uint8_t[signatureLength];
|
||||
s.read ((char *)signature, signatureLength);
|
||||
// RSA-raw
|
||||
{
|
||||
// calculate digest
|
||||
uint8_t digest[64];
|
||||
SHA512 (tbs, tbsLen, digest);
|
||||
// encrypt signature
|
||||
BN_CTX * bnctx = BN_CTX_new ();
|
||||
BIGNUM * s = BN_new (), * n = BN_new ();
|
||||
BN_bin2bn (signature, signatureLength, s);
|
||||
BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n);
|
||||
BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n
|
||||
uint8_t * enSigBuf = new uint8_t[signatureLength];
|
||||
i2p::crypto::bn2buf (s, enSigBuf, signatureLength);
|
||||
// digest is right aligned
|
||||
// we can't use RSA_verify due wrong padding in SU3
|
||||
if (memcmp (enSigBuf + (signatureLength - 64), digest, 64))
|
||||
LogPrint (eLogWarning, "Reseed: SU3 signature verification failed");
|
||||
else
|
||||
verify = false; // verified
|
||||
delete[] enSigBuf;
|
||||
BN_free (s); BN_free (n);
|
||||
BN_CTX_free (bnctx);
|
||||
}
|
||||
|
||||
delete[] signature;
|
||||
delete[] tbs;
|
||||
s.seekg (pos, std::ios::beg);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported");
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded");
|
||||
}
|
||||
|
||||
if (verify) // not verified
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: SU3 verification failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// handle content
|
||||
return ProcessZIPStream (s, contentLength);
|
||||
}
|
||||
|
||||
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50;
|
||||
const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50;
|
||||
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008;
|
||||
int Reseeder::ProcessZIPStream (std::istream& s, uint64_t contentLength)
|
||||
{
|
||||
int numFiles = 0;
|
||||
size_t contentPos = s.tellg ();
|
||||
while (!s.eof ())
|
||||
{
|
||||
uint32_t signature;
|
||||
s.read ((char *)&signature, 4);
|
||||
signature = le32toh (signature);
|
||||
if (signature == ZIP_HEADER_SIGNATURE)
|
||||
{
|
||||
// next local file
|
||||
s.seekg (2, std::ios::cur); // version
|
||||
uint16_t bitFlag;
|
||||
s.read ((char *)&bitFlag, 2);
|
||||
bitFlag = le16toh (bitFlag);
|
||||
uint16_t compressionMethod;
|
||||
s.read ((char *)&compressionMethod, 2);
|
||||
compressionMethod = le16toh (compressionMethod);
|
||||
s.seekg (4, std::ios::cur); // skip fields we don't care about
|
||||
uint32_t compressedSize, uncompressedSize;
|
||||
uint32_t crc_32;
|
||||
s.read ((char *)&crc_32, 4);
|
||||
crc_32 = le32toh (crc_32);
|
||||
s.read ((char *)&compressedSize, 4);
|
||||
compressedSize = le32toh (compressedSize);
|
||||
s.read ((char *)&uncompressedSize, 4);
|
||||
uncompressedSize = le32toh (uncompressedSize);
|
||||
uint16_t fileNameLength, extraFieldLength;
|
||||
s.read ((char *)&fileNameLength, 2);
|
||||
fileNameLength = le16toh (fileNameLength);
|
||||
if ( fileNameLength > 255 ) {
|
||||
// too big
|
||||
LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength);
|
||||
return numFiles;
|
||||
}
|
||||
s.read ((char *)&extraFieldLength, 2);
|
||||
extraFieldLength = le16toh (extraFieldLength);
|
||||
char localFileName[255];
|
||||
s.read (localFileName, fileNameLength);
|
||||
localFileName[fileNameLength] = 0;
|
||||
s.seekg (extraFieldLength, std::ios::cur);
|
||||
// take care about data desriptor if presented
|
||||
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
|
||||
{
|
||||
size_t pos = s.tellg ();
|
||||
if (!FindZipDataDescriptor (s))
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found");
|
||||
return numFiles;
|
||||
}
|
||||
s.read ((char *)&crc_32, 4);
|
||||
crc_32 = le32toh (crc_32);
|
||||
s.read ((char *)&compressedSize, 4);
|
||||
compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data
|
||||
s.read ((char *)&uncompressedSize, 4);
|
||||
uncompressedSize = le32toh (uncompressedSize);
|
||||
|
||||
// now we know compressed and uncompressed size
|
||||
s.seekg (pos, std::ios::beg); // back to compressed data
|
||||
}
|
||||
|
||||
LogPrint (eLogDebug, "Reseed: Proccessing file ", localFileName, " ", compressedSize, " bytes");
|
||||
if (!compressedSize)
|
||||
{
|
||||
LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped");
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t * compressed = new uint8_t[compressedSize];
|
||||
s.read ((char *)compressed, compressedSize);
|
||||
if (compressionMethod) // we assume Deflate
|
||||
{
|
||||
z_stream inflator;
|
||||
memset (&inflator, 0, sizeof (inflator));
|
||||
inflateInit2 (&inflator, -MAX_WBITS); // no zlib header
|
||||
uint8_t * uncompressed = new uint8_t[uncompressedSize];
|
||||
inflator.next_in = compressed;
|
||||
inflator.avail_in = compressedSize;
|
||||
inflator.next_out = uncompressed;
|
||||
inflator.avail_out = uncompressedSize;
|
||||
int err;
|
||||
if ((err = inflate (&inflator, Z_SYNC_FLUSH)) >= 0)
|
||||
{
|
||||
uncompressedSize -= inflator.avail_out;
|
||||
if (crc32 (0, uncompressed, uncompressedSize) == crc_32)
|
||||
{
|
||||
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize);
|
||||
numFiles++;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Reseed: CRC32 verification failed");
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Reseed: SU3 decompression error ", err);
|
||||
delete[] uncompressed;
|
||||
inflateEnd (&inflator);
|
||||
}
|
||||
else // no compression
|
||||
{
|
||||
i2p::data::netdb.AddRouterInfo (compressed, compressedSize);
|
||||
numFiles++;
|
||||
}
|
||||
delete[] compressed;
|
||||
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
|
||||
s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE)
|
||||
LogPrint (eLogWarning, "Reseed: Missing zip central directory header");
|
||||
break; // no more files
|
||||
}
|
||||
size_t end = s.tellg ();
|
||||
if (end - contentPos >= contentLength)
|
||||
break; // we are beyond contentLength
|
||||
}
|
||||
if (numFiles) // check if routers are not outdated
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
int numOutdated = 0;
|
||||
i2p::data::netdb.VisitRouterInfos (
|
||||
[&numOutdated, ts](std::shared_ptr<const RouterInfo> r)
|
||||
{
|
||||
if (r && ts > r->GetTimestamp () + 10*i2p::data::NETDB_MAX_EXPIRATION_TIMEOUT*1000LL) // 270 hours
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: router ", r->GetIdentHash().ToBase64 (), " is outdated by ", (ts - r->GetTimestamp ())/1000LL/3600LL, " hours");
|
||||
numOutdated++;
|
||||
}
|
||||
});
|
||||
if (numOutdated > numFiles/2) // more than half
|
||||
{
|
||||
LogPrint (eLogError, "Reseed: mammoth's shit\n"
|
||||
" *_____*\n"
|
||||
" *_*****_*\n"
|
||||
" *_(O)_(O)_*\n"
|
||||
" **____V____**\n"
|
||||
" **_________**\n"
|
||||
" **_________**\n"
|
||||
" *_________*\n"
|
||||
" ***___***");
|
||||
i2p::data::netdb.ClearRouterInfos ();
|
||||
numFiles = 0;
|
||||
}
|
||||
}
|
||||
return numFiles;
|
||||
}
|
||||
|
||||
const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = { 0x50, 0x4B, 0x07, 0x08 };
|
||||
bool Reseeder::FindZipDataDescriptor (std::istream& s)
|
||||
{
|
||||
size_t nextInd = 0;
|
||||
while (!s.eof ())
|
||||
{
|
||||
uint8_t nextByte;
|
||||
s.read ((char *)&nextByte, 1);
|
||||
if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd])
|
||||
{
|
||||
nextInd++;
|
||||
if (nextInd >= sizeof (ZIP_DATA_DESCRIPTOR_SIGNATURE))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
nextInd = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Reseeder::LoadCertificate (const std::string& filename)
|
||||
{
|
||||
SSL_CTX * ctx = SSL_CTX_new (TLS_method ());
|
||||
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
|
||||
if (ret)
|
||||
{
|
||||
SSL * ssl = SSL_new (ctx);
|
||||
X509 * cert = SSL_get_certificate (ssl);
|
||||
// verify
|
||||
if (cert)
|
||||
{
|
||||
// extract issuer name
|
||||
char name[100];
|
||||
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
|
||||
char * cn = strstr (name, "CN=");
|
||||
if (cn)
|
||||
{
|
||||
cn += 3;
|
||||
char * terminator = strchr (cn, '/');
|
||||
if (terminator) terminator[0] = 0;
|
||||
}
|
||||
// extract RSA key (we need n only, e = 65537)
|
||||
RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert));
|
||||
const BIGNUM * n, * e, * d;
|
||||
RSA_get0_key(key, &n, &e, &d);
|
||||
PublicKey value;
|
||||
i2p::crypto::bn2buf (n, value, 512);
|
||||
if (cn)
|
||||
m_SigningKeys[cn] = value;
|
||||
else
|
||||
LogPrint (eLogError, "Reseed: Can't find CN field in ", filename);
|
||||
}
|
||||
SSL_free (ssl);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Reseed: Can't open certificate file ", filename);
|
||||
SSL_CTX_free (ctx);
|
||||
}
|
||||
|
||||
void Reseeder::LoadCertificates ()
|
||||
{
|
||||
std::string certDir = i2p::fs::DataDirPath("certificates", "reseed");
|
||||
std::vector<std::string> files;
|
||||
int numCertificates = 0;
|
||||
|
||||
if (!i2p::fs::ReadDir(certDir, files)) {
|
||||
LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const std::string & file : files) {
|
||||
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
|
||||
LogPrint(eLogWarning, "Reseed: ignoring file ", file);
|
||||
continue;
|
||||
}
|
||||
LoadCertificate (file);
|
||||
numCertificates++;
|
||||
}
|
||||
LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded");
|
||||
}
|
||||
|
||||
std::string Reseeder::HttpsRequest (const std::string& address)
|
||||
{
|
||||
i2p::http::URL url;
|
||||
if (!url.parse(address)) {
|
||||
LogPrint(eLogError, "Reseed: failed to parse url: ", address);
|
||||
return "";
|
||||
}
|
||||
url.schema = "https";
|
||||
if (!url.port)
|
||||
url.port = 443;
|
||||
|
||||
boost::asio::io_service service;
|
||||
boost::system::error_code ecode;
|
||||
auto it = boost::asio::ip::tcp::resolver(service).resolve (
|
||||
boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
|
||||
if (!ecode)
|
||||
{
|
||||
boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23);
|
||||
ctx.set_verify_mode(boost::asio::ssl::context::verify_none);
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> s(service, ctx);
|
||||
s.lowest_layer().connect (*it, ecode);
|
||||
if (!ecode)
|
||||
{
|
||||
SSL_set_tlsext_host_name(s.native_handle(), url.host.c_str ());
|
||||
s.handshake (boost::asio::ssl::stream_base::client, ecode);
|
||||
if (!ecode)
|
||||
{
|
||||
LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port);
|
||||
i2p::http::HTTPReq req;
|
||||
req.uri = url.to_string();
|
||||
req.AddHeader("User-Agent", "Wget/1.11.4");
|
||||
req.AddHeader("Connection", "close");
|
||||
s.write_some (boost::asio::buffer (req.to_string()));
|
||||
// read response
|
||||
std::stringstream rs;
|
||||
char recv_buf[1024]; size_t l = 0;
|
||||
do {
|
||||
l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode);
|
||||
if (l) rs.write (recv_buf, l);
|
||||
} while (!ecode && l);
|
||||
// process response
|
||||
std::string data = rs.str();
|
||||
i2p::http::HTTPRes res;
|
||||
int len = res.parse(data);
|
||||
if (len <= 0) {
|
||||
LogPrint(eLogWarning, "Reseed: incomplete/broken response from ", url.host);
|
||||
return "";
|
||||
}
|
||||
if (res.code != 200) {
|
||||
LogPrint(eLogError, "Reseed: failed to reseed from ", url.host, ", http code ", res.code);
|
||||
return "";
|
||||
}
|
||||
data.erase(0, len); /* drop http headers from response */
|
||||
LogPrint(eLogDebug, "Reseed: got ", data.length(), " bytes of data from ", url.host);
|
||||
if (res.is_chunked()) {
|
||||
std::stringstream in(data), out;
|
||||
if (!i2p::http::MergeChunkedResponse(in, out)) {
|
||||
LogPrint(eLogWarning, "Reseed: failed to merge chunked response from ", url.host);
|
||||
return "";
|
||||
}
|
||||
LogPrint(eLogDebug, "Reseed: got ", data.length(), "(", out.tellg(), ") bytes of data from ", url.host);
|
||||
data = out.str();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ());
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ());
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Reseed: Couldn't resolve address ", url.host, ": ", ecode.message ());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
libi2pd/Reseed.h
Normal file
50
libi2pd/Reseed.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef RESEED_H
|
||||
#define RESEED_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Identity.h"
|
||||
#include "Crypto.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
|
||||
class Reseeder
|
||||
{
|
||||
typedef Tag<512> PublicKey;
|
||||
|
||||
public:
|
||||
|
||||
Reseeder();
|
||||
~Reseeder();
|
||||
void Bootstrap ();
|
||||
int ReseedFromServers ();
|
||||
int ReseedFromSU3Url (const std::string& url);
|
||||
int ProcessSU3File (const char * filename);
|
||||
int ProcessZIPFile (const char * filename);
|
||||
|
||||
void LoadCertificates ();
|
||||
|
||||
private:
|
||||
|
||||
void LoadCertificate (const std::string& filename);
|
||||
|
||||
int ProcessSU3Stream (std::istream& s);
|
||||
int ProcessZIPStream (std::istream& s, uint64_t contentLength);
|
||||
|
||||
bool FindZipDataDescriptor (std::istream& s);
|
||||
|
||||
std::string HttpsRequest (const std::string& address);
|
||||
|
||||
private:
|
||||
|
||||
std::map<std::string, PublicKey> m_SigningKeys;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
473
libi2pd/RouterContext.cpp
Normal file
473
libi2pd/RouterContext.cpp
Normal file
|
@ -0,0 +1,473 @@
|
|||
#include <fstream>
|
||||
#include "Config.h"
|
||||
#include "Crypto.h"
|
||||
#include "Timestamp.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "NetDb.h"
|
||||
#include "FS.h"
|
||||
#include "util.h"
|
||||
#include "version.h"
|
||||
#include "Log.h"
|
||||
#include "Family.h"
|
||||
#include "RouterContext.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
RouterContext context;
|
||||
|
||||
RouterContext::RouterContext ():
|
||||
m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false),
|
||||
m_StartupTime (0), m_Status (eRouterStatusOK), m_Error (eRouterErrorNone),
|
||||
m_NetID (I2PD_NET_ID)
|
||||
{
|
||||
}
|
||||
|
||||
void RouterContext::Init ()
|
||||
{
|
||||
srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
|
||||
m_StartupTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (!Load ())
|
||||
CreateNewRouter ();
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::CreateNewRouter ()
|
||||
{
|
||||
#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
|
||||
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519);
|
||||
#else
|
||||
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1);
|
||||
#endif
|
||||
SaveKeys ();
|
||||
NewRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::NewRouterInfo ()
|
||||
{
|
||||
i2p::data::RouterInfo routerInfo;
|
||||
routerInfo.SetRouterIdentity (GetIdentity ());
|
||||
uint16_t port; i2p::config::GetOption("port", port);
|
||||
if (!port)
|
||||
port = rand () % (30777 - 9111) + 9111; // I2P network ports range
|
||||
bool ipv4; i2p::config::GetOption("ipv4", ipv4);
|
||||
bool ipv6; i2p::config::GetOption("ipv6", ipv6);
|
||||
bool nat; i2p::config::GetOption("nat", nat);
|
||||
std::string ifname; i2p::config::GetOption("ifname", ifname);
|
||||
std::string ifname4; i2p::config::GetOption("ifname4", ifname4);
|
||||
std::string ifname6; i2p::config::GetOption("ifname6", ifname6);
|
||||
if (ipv4)
|
||||
{
|
||||
std::string host = "127.0.0.1";
|
||||
if (!i2p::config::IsDefault("host"))
|
||||
i2p::config::GetOption("host", host);
|
||||
else if (!nat && !ifname.empty())
|
||||
/* bind to interface, we have no NAT so set external address too */
|
||||
host = i2p::util::net::GetInterfaceAddress(ifname, false).to_string(); // v4
|
||||
|
||||
if(ifname4.size())
|
||||
host = i2p::util::net::GetInterfaceAddress(ifname4, false).to_string();
|
||||
|
||||
routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ());
|
||||
routerInfo.AddNTCPAddress (host.c_str(), port);
|
||||
}
|
||||
if (ipv6)
|
||||
{
|
||||
std::string host = "::";
|
||||
if (!i2p::config::IsDefault("host") && !ipv4) // override if v6 only
|
||||
i2p::config::GetOption("host", host);
|
||||
else if (!ifname.empty())
|
||||
host = i2p::util::net::GetInterfaceAddress(ifname, true).to_string(); // v6
|
||||
|
||||
if(ifname6.size())
|
||||
host = i2p::util::net::GetInterfaceAddress(ifname6, true).to_string();
|
||||
|
||||
routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ());
|
||||
routerInfo.AddNTCPAddress (host.c_str(), port);
|
||||
}
|
||||
|
||||
routerInfo.SetCaps (i2p::data::RouterInfo::eReachable |
|
||||
i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC
|
||||
routerInfo.SetProperty ("netId", std::to_string (m_NetID));
|
||||
routerInfo.SetProperty ("router.version", I2P_VERSION);
|
||||
routerInfo.CreateBuffer (m_Keys);
|
||||
m_RouterInfo.SetRouterIdentity (GetIdentity ());
|
||||
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
|
||||
}
|
||||
|
||||
void RouterContext::UpdateRouterInfo ()
|
||||
{
|
||||
m_RouterInfo.CreateBuffer (m_Keys);
|
||||
m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
|
||||
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
}
|
||||
|
||||
void RouterContext::SetStatus (RouterStatus status)
|
||||
{
|
||||
if (status != m_Status)
|
||||
{
|
||||
m_Status = status;
|
||||
m_Error = eRouterErrorNone;
|
||||
switch (m_Status)
|
||||
{
|
||||
case eRouterStatusOK:
|
||||
SetReachable ();
|
||||
break;
|
||||
case eRouterStatusFirewalled:
|
||||
SetUnreachable ();
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RouterContext::UpdatePort (int port)
|
||||
{
|
||||
bool updated = false;
|
||||
for (auto& address : m_RouterInfo.GetAddresses ())
|
||||
{
|
||||
if (address->port != port)
|
||||
{
|
||||
address->port = port;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated)
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::UpdateAddress (const boost::asio::ip::address& host)
|
||||
{
|
||||
bool updated = false;
|
||||
for (auto& address : m_RouterInfo.GetAddresses ())
|
||||
{
|
||||
if (address->host != host && address->IsCompatible (host))
|
||||
{
|
||||
address->host = host;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL)
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer)
|
||||
{
|
||||
bool ret = m_RouterInfo.AddIntroducer (introducer);
|
||||
if (ret)
|
||||
UpdateRouterInfo ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
|
||||
{
|
||||
if (m_RouterInfo.RemoveIntroducer (e))
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::SetFloodfill (bool floodfill)
|
||||
{
|
||||
m_IsFloodfill = floodfill;
|
||||
if (floodfill)
|
||||
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill);
|
||||
else
|
||||
{
|
||||
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
|
||||
// we don't publish number of routers and leaseset for non-floodfill
|
||||
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS);
|
||||
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS);
|
||||
}
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
std::string RouterContext::GetFamily () const
|
||||
{
|
||||
return m_RouterInfo.GetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY);
|
||||
}
|
||||
|
||||
void RouterContext::SetFamily (const std::string& family)
|
||||
{
|
||||
std::string signature;
|
||||
if (family.length () > 0)
|
||||
signature = i2p::data::CreateFamilySignature (family, GetIdentHash ());
|
||||
if (signature.length () > 0)
|
||||
{
|
||||
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY, family);
|
||||
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG, signature);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY);
|
||||
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG);
|
||||
}
|
||||
}
|
||||
|
||||
void RouterContext::SetBandwidth (char L) {
|
||||
uint16_t limit = 0;
|
||||
enum { low, high, extra, unlim } type = high;
|
||||
/* detect parameters */
|
||||
switch (L)
|
||||
{
|
||||
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break;
|
||||
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = 48; type = low; break;
|
||||
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break;
|
||||
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break;
|
||||
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = 256; type = high; break;
|
||||
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break;
|
||||
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 9999; type = unlim; break;
|
||||
default:
|
||||
limit = 48; type = low;
|
||||
}
|
||||
/* update caps & flags in RI */
|
||||
auto caps = m_RouterInfo.GetCaps ();
|
||||
caps &= ~i2p::data::RouterInfo::eHighBandwidth;
|
||||
caps &= ~i2p::data::RouterInfo::eExtraBandwidth;
|
||||
switch (type)
|
||||
{
|
||||
case low : /* not set */; break;
|
||||
case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; // 'P'
|
||||
case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth; // no break here, extra + high means 'X'
|
||||
case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break;
|
||||
}
|
||||
m_RouterInfo.SetCaps (caps);
|
||||
UpdateRouterInfo ();
|
||||
m_BandwidthLimit = limit;
|
||||
}
|
||||
|
||||
void RouterContext::SetBandwidth (int limit)
|
||||
{
|
||||
if (limit > 2000) { SetBandwidth('X'); }
|
||||
else if (limit > 256) { SetBandwidth('P'); }
|
||||
else if (limit > 128) { SetBandwidth('O'); }
|
||||
else if (limit > 64) { SetBandwidth('N'); }
|
||||
else if (limit > 48) { SetBandwidth('M'); }
|
||||
else if (limit > 12) { SetBandwidth('L'); }
|
||||
else { SetBandwidth('K'); }
|
||||
}
|
||||
|
||||
bool RouterContext::IsUnreachable () const
|
||||
{
|
||||
return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable;
|
||||
}
|
||||
|
||||
void RouterContext::SetUnreachable ()
|
||||
{
|
||||
// set caps
|
||||
uint8_t caps = m_RouterInfo.GetCaps ();
|
||||
caps &= ~i2p::data::RouterInfo::eReachable;
|
||||
caps |= i2p::data::RouterInfo::eUnreachable;
|
||||
caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill
|
||||
caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer
|
||||
m_RouterInfo.SetCaps (caps);
|
||||
// remove NTCP address
|
||||
auto& addresses = m_RouterInfo.GetAddresses ();
|
||||
for (auto it = addresses.begin (); it != addresses.end (); ++it)
|
||||
{
|
||||
if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP &&
|
||||
(*it)->host.is_v4 ())
|
||||
{
|
||||
addresses.erase (it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// delete previous introducers
|
||||
for (auto& addr : addresses)
|
||||
if (addr->ssu)
|
||||
addr->ssu->introducers.clear ();
|
||||
|
||||
// update
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::SetReachable ()
|
||||
{
|
||||
// update caps
|
||||
uint8_t caps = m_RouterInfo.GetCaps ();
|
||||
caps &= ~i2p::data::RouterInfo::eUnreachable;
|
||||
caps |= i2p::data::RouterInfo::eReachable;
|
||||
caps |= i2p::data::RouterInfo::eSSUIntroducer;
|
||||
if (m_IsFloodfill)
|
||||
caps |= i2p::data::RouterInfo::eFloodfill;
|
||||
m_RouterInfo.SetCaps (caps);
|
||||
|
||||
// insert NTCP back
|
||||
auto& addresses = m_RouterInfo.GetAddresses ();
|
||||
for (const auto& addr : addresses)
|
||||
{
|
||||
if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU &&
|
||||
addr->host.is_v4 ())
|
||||
{
|
||||
// insert NTCP address with host/port from SSU
|
||||
m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// delete previous introducers
|
||||
for (auto& addr : addresses)
|
||||
if (addr->ssu)
|
||||
addr->ssu->introducers.clear ();
|
||||
|
||||
// update
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::SetSupportsV6 (bool supportsV6)
|
||||
{
|
||||
if (supportsV6)
|
||||
m_RouterInfo.EnableV6 ();
|
||||
else
|
||||
m_RouterInfo.DisableV6 ();
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::SetSupportsV4 (bool supportsV4)
|
||||
{
|
||||
if (supportsV4)
|
||||
m_RouterInfo.EnableV4 ();
|
||||
else
|
||||
m_RouterInfo.DisableV4 ();
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
|
||||
void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host)
|
||||
{
|
||||
bool updated = false, found = false;
|
||||
int port = 0;
|
||||
auto& addresses = m_RouterInfo.GetAddresses ();
|
||||
for (auto& addr: addresses)
|
||||
{
|
||||
if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
|
||||
{
|
||||
if (addr->host != host)
|
||||
{
|
||||
addr->host = host;
|
||||
updated = true;
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
port = addr->port;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
// create new address
|
||||
m_RouterInfo.AddNTCPAddress (host.to_string ().c_str (), port);
|
||||
auto mtu = i2p::util::net::GetMTU (host);
|
||||
if (mtu)
|
||||
{
|
||||
LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu);
|
||||
if (mtu > 1472) { // TODO: magic constant
|
||||
mtu = 1472;
|
||||
LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes");
|
||||
}
|
||||
}
|
||||
m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO
|
||||
updated = true;
|
||||
}
|
||||
if (updated)
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
|
||||
void RouterContext::UpdateStats ()
|
||||
{
|
||||
if (m_IsFloodfill)
|
||||
{
|
||||
// update routers and leasesets
|
||||
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS, std::to_string(i2p::data::netdb.GetNumLeaseSets ()));
|
||||
m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS, std::to_string(i2p::data::netdb.GetNumRouters ()));
|
||||
UpdateRouterInfo ();
|
||||
}
|
||||
}
|
||||
|
||||
bool RouterContext::Load ()
|
||||
{
|
||||
std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary);
|
||||
if (!fk.is_open ()) return false;
|
||||
fk.seekg (0, std::ios::end);
|
||||
size_t len = fk.tellg();
|
||||
fk.seekg (0, std::ios::beg);
|
||||
|
||||
if (len == sizeof (i2p::data::Keys)) // old keys file format
|
||||
{
|
||||
i2p::data::Keys keys;
|
||||
fk.read ((char *)&keys, sizeof (keys));
|
||||
m_Keys = keys;
|
||||
}
|
||||
else // new keys file format
|
||||
{
|
||||
uint8_t * buf = new uint8_t[len];
|
||||
fk.read ((char *)buf, len);
|
||||
m_Keys.FromBuffer (buf, len);
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
m_RouterInfo.SetRouterIdentity (GetIdentity ());
|
||||
i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO));
|
||||
if (!routerInfo.IsUnreachable ()) // router.info looks good
|
||||
{
|
||||
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
|
||||
m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION);
|
||||
m_RouterInfo.SetProperty ("router.version", I2P_VERSION);
|
||||
|
||||
// Migration to 0.9.24. TODO: remove later
|
||||
m_RouterInfo.DeleteProperty ("coreVersion");
|
||||
m_RouterInfo.DeleteProperty ("stat_uptime");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, ROUTER_INFO, " is malformed. Creating new");
|
||||
NewRouterInfo ();
|
||||
}
|
||||
|
||||
if (IsUnreachable ())
|
||||
SetReachable (); // we assume reachable until we discover firewall through peer tests
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RouterContext::SaveKeys ()
|
||||
{
|
||||
// save in the same format as .dat files
|
||||
std::ofstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ofstream::binary | std::ofstream::out);
|
||||
size_t len = m_Keys.GetFullLen ();
|
||||
uint8_t * buf = new uint8_t[len];
|
||||
m_Keys.ToBuffer (buf, len);
|
||||
fk.write ((char *)buf, len);
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::tunnel::TunnelPool> RouterContext::GetTunnelPool () const
|
||||
{
|
||||
return i2p::tunnel::tunnels.GetExploratoryPool ();
|
||||
}
|
||||
|
||||
void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from)
|
||||
{
|
||||
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from));
|
||||
}
|
||||
|
||||
void RouterContext::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_GarlicMutex);
|
||||
i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg);
|
||||
}
|
||||
|
||||
void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_GarlicMutex);
|
||||
i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg);
|
||||
}
|
||||
|
||||
void RouterContext::CleanupDestination ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_GarlicMutex);
|
||||
i2p::garlic::GarlicDestination::CleanupExpiredTags ();
|
||||
}
|
||||
|
||||
uint32_t RouterContext::GetUptime () const
|
||||
{
|
||||
return i2p::util::GetSecondsSinceEpoch () - m_StartupTime;
|
||||
}
|
||||
}
|
129
libi2pd/RouterContext.h
Normal file
129
libi2pd/RouterContext.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
#ifndef ROUTER_CONTEXT_H__
|
||||
#define ROUTER_CONTEXT_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "Garlic.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
const char ROUTER_INFO[] = "router.info";
|
||||
const char ROUTER_KEYS[] = "router.keys";
|
||||
const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes
|
||||
|
||||
enum RouterStatus
|
||||
{
|
||||
eRouterStatusOK = 0,
|
||||
eRouterStatusTesting = 1,
|
||||
eRouterStatusFirewalled = 2,
|
||||
eRouterStatusError = 3
|
||||
};
|
||||
|
||||
enum RouterError
|
||||
{
|
||||
eRouterErrorNone = 0,
|
||||
eRouterErrorClockSkew = 1
|
||||
};
|
||||
|
||||
class RouterContext: public i2p::garlic::GarlicDestination
|
||||
{
|
||||
public:
|
||||
|
||||
RouterContext ();
|
||||
void Init ();
|
||||
|
||||
const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
|
||||
i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; };
|
||||
std::shared_ptr<const i2p::data::RouterInfo> GetSharedRouterInfo () const
|
||||
{
|
||||
return std::shared_ptr<const i2p::data::RouterInfo> (&m_RouterInfo,
|
||||
[](const i2p::data::RouterInfo *) {});
|
||||
}
|
||||
std::shared_ptr<i2p::garlic::GarlicDestination> GetSharedDestination ()
|
||||
{
|
||||
return std::shared_ptr<i2p::garlic::GarlicDestination> (this,
|
||||
[](i2p::garlic::GarlicDestination *) {});
|
||||
}
|
||||
|
||||
uint32_t GetUptime () const;
|
||||
uint32_t GetStartupTime () const { return m_StartupTime; };
|
||||
uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
|
||||
uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; };
|
||||
RouterStatus GetStatus () const { return m_Status; };
|
||||
void SetStatus (RouterStatus status);
|
||||
RouterError GetError () const { return m_Error; };
|
||||
void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; };
|
||||
int GetNetID () const { return m_NetID; };
|
||||
void SetNetID (int netID) { m_NetID = netID; };
|
||||
|
||||
void UpdatePort (int port); // called from Daemon
|
||||
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon
|
||||
bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer);
|
||||
void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
|
||||
bool IsUnreachable () const;
|
||||
void SetUnreachable ();
|
||||
void SetReachable ();
|
||||
bool IsFloodfill () const { return m_IsFloodfill; };
|
||||
void SetFloodfill (bool floodfill);
|
||||
void SetFamily (const std::string& family);
|
||||
std::string GetFamily () const;
|
||||
void SetBandwidth (int limit); /* in kilobytes */
|
||||
void SetBandwidth (char L); /* by letter */
|
||||
bool AcceptsTunnels () const { return m_AcceptsTunnels; };
|
||||
void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
|
||||
bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
|
||||
bool SupportsV4 () const { return m_RouterInfo.IsV4 (); };
|
||||
void SetSupportsV6 (bool supportsV6);
|
||||
void SetSupportsV4 (bool supportsV4);
|
||||
|
||||
void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session
|
||||
void UpdateStats ();
|
||||
void CleanupDestination (); // garlic destination
|
||||
|
||||
// implements LocalDestination
|
||||
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
|
||||
const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); };
|
||||
const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ()->GetStandardIdentity ().publicKey; };
|
||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
|
||||
void SetLeaseSetUpdated () {};
|
||||
|
||||
// implements GarlicDestination
|
||||
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () { return nullptr; };
|
||||
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const;
|
||||
void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from);
|
||||
|
||||
// override GarlicDestination
|
||||
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
private:
|
||||
|
||||
void CreateNewRouter ();
|
||||
void NewRouterInfo ();
|
||||
void UpdateRouterInfo ();
|
||||
bool Load ();
|
||||
void SaveKeys ();
|
||||
|
||||
private:
|
||||
|
||||
i2p::data::RouterInfo m_RouterInfo;
|
||||
i2p::data::PrivateKeys m_Keys;
|
||||
uint64_t m_LastUpdateTime;
|
||||
bool m_AcceptsTunnels, m_IsFloodfill;
|
||||
uint64_t m_StartupTime; // in seconds since epoch
|
||||
uint32_t m_BandwidthLimit; // allowed bandwidth
|
||||
RouterStatus m_Status;
|
||||
RouterError m_Error;
|
||||
int m_NetID;
|
||||
std::mutex m_GarlicMutex;
|
||||
};
|
||||
|
||||
extern RouterContext context;
|
||||
}
|
||||
|
||||
#endif
|
818
libi2pd/RouterInfo.cpp
Normal file
818
libi2pd/RouterInfo.cpp
Normal file
|
@ -0,0 +1,818 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "I2PEndian.h"
|
||||
#include <fstream>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#if (BOOST_VERSION >= 105300)
|
||||
#include <boost/atomic.hpp>
|
||||
#endif
|
||||
#include "version.h"
|
||||
#include "Crypto.h"
|
||||
#include "Base.h"
|
||||
#include "Timestamp.h"
|
||||
#include "Log.h"
|
||||
#include "NetDb.h"
|
||||
#include "RouterContext.h"
|
||||
#include "RouterInfo.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
RouterInfo::RouterInfo (): m_Buffer (nullptr)
|
||||
{
|
||||
m_Addresses = boost::make_shared<Addresses>(); // create empty list
|
||||
}
|
||||
|
||||
RouterInfo::RouterInfo (const std::string& fullPath):
|
||||
m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false),
|
||||
m_SupportedTransports (0), m_Caps (0)
|
||||
{
|
||||
m_Addresses = boost::make_shared<Addresses>(); // create empty list
|
||||
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||
ReadFromFile ();
|
||||
}
|
||||
|
||||
RouterInfo::RouterInfo (const uint8_t * buf, int len):
|
||||
m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0)
|
||||
{
|
||||
m_Addresses = boost::make_shared<Addresses>(); // create empty list
|
||||
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||
memcpy (m_Buffer, buf, len);
|
||||
m_BufferLen = len;
|
||||
ReadFromBuffer (true);
|
||||
}
|
||||
|
||||
RouterInfo::~RouterInfo ()
|
||||
{
|
||||
delete[] m_Buffer;
|
||||
}
|
||||
|
||||
void RouterInfo::Update (const uint8_t * buf, int len)
|
||||
{
|
||||
// verify signature since we have indentity already
|
||||
int l = len - m_RouterIdentity->GetSignatureLen ();
|
||||
if (m_RouterIdentity->Verify (buf, l, buf + l))
|
||||
{
|
||||
// clean up
|
||||
m_IsUpdated = true;
|
||||
m_IsUnreachable = false;
|
||||
m_SupportedTransports = 0;
|
||||
m_Caps = 0;
|
||||
// don't clean up m_Addresses, it will be replaced in ReadFromStream
|
||||
m_Properties.clear ();
|
||||
// copy buffer
|
||||
if (!m_Buffer)
|
||||
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||
memcpy (m_Buffer, buf, len);
|
||||
m_BufferLen = len;
|
||||
// skip identity
|
||||
size_t identityLen = m_RouterIdentity->GetFullLen ();
|
||||
// read new RI
|
||||
std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen));
|
||||
ReadFromStream (str);
|
||||
// don't delete buffer until saved to the file
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "RouterInfo: signature verification failed");
|
||||
m_IsUnreachable = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RouterInfo::SetRouterIdentity (std::shared_ptr<const IdentityEx> identity)
|
||||
{
|
||||
m_RouterIdentity = identity;
|
||||
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
}
|
||||
|
||||
bool RouterInfo::LoadFile ()
|
||||
{
|
||||
std::ifstream s(m_FullPath, std::ifstream::binary);
|
||||
if (s.is_open ())
|
||||
{
|
||||
s.seekg (0,std::ios::end);
|
||||
m_BufferLen = s.tellg ();
|
||||
if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE)
|
||||
{
|
||||
LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed");
|
||||
return false;
|
||||
}
|
||||
s.seekg(0, std::ios::beg);
|
||||
if (!m_Buffer)
|
||||
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||
s.read((char *)m_Buffer, m_BufferLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RouterInfo::ReadFromFile ()
|
||||
{
|
||||
if (LoadFile ())
|
||||
ReadFromBuffer (false);
|
||||
else
|
||||
m_IsUnreachable = true;
|
||||
}
|
||||
|
||||
void RouterInfo::ReadFromBuffer (bool verifySignature)
|
||||
{
|
||||
m_RouterIdentity = std::make_shared<IdentityEx>(m_Buffer, m_BufferLen);
|
||||
size_t identityLen = m_RouterIdentity->GetFullLen ();
|
||||
if (identityLen >= m_BufferLen)
|
||||
{
|
||||
LogPrint (eLogError, "RouterInfo: identity length ", identityLen, " exceeds buffer size ", m_BufferLen);
|
||||
m_IsUnreachable = true;
|
||||
return;
|
||||
}
|
||||
if (verifySignature)
|
||||
{
|
||||
// verify signature
|
||||
int l = m_BufferLen - m_RouterIdentity->GetSignatureLen ();
|
||||
if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l))
|
||||
{
|
||||
LogPrint (eLogError, "RouterInfo: signature verification failed");
|
||||
m_IsUnreachable = true;
|
||||
return;
|
||||
}
|
||||
m_RouterIdentity->DropVerifier ();
|
||||
}
|
||||
// parse RI
|
||||
std::stringstream str;
|
||||
str.write ((const char *)m_Buffer + identityLen, m_BufferLen - identityLen);
|
||||
ReadFromStream (str);
|
||||
if (!str)
|
||||
{
|
||||
LogPrint (eLogError, "RouterInfo: malformed message");
|
||||
m_IsUnreachable = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RouterInfo::ReadFromStream (std::istream& s)
|
||||
{
|
||||
s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
|
||||
m_Timestamp = be64toh (m_Timestamp);
|
||||
// read addresses
|
||||
auto addresses = boost::make_shared<Addresses>();
|
||||
uint8_t numAddresses;
|
||||
s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return;
|
||||
bool introducers = false;
|
||||
for (int i = 0; i < numAddresses; i++)
|
||||
{
|
||||
uint8_t supportedTransports = 0;
|
||||
bool isValidAddress = true;
|
||||
auto address = std::make_shared<Address>();
|
||||
s.read ((char *)&address->cost, sizeof (address->cost));
|
||||
s.read ((char *)&address->date, sizeof (address->date));
|
||||
char transportStyle[5];
|
||||
ReadString (transportStyle, 5, s);
|
||||
if (!strcmp (transportStyle, "NTCP"))
|
||||
address->transportStyle = eTransportNTCP;
|
||||
else if (!strcmp (transportStyle, "SSU"))
|
||||
{
|
||||
address->transportStyle = eTransportSSU;
|
||||
address->ssu.reset (new SSUExt ());
|
||||
address->ssu->mtu = 0;
|
||||
}
|
||||
else
|
||||
address->transportStyle = eTransportUnknown;
|
||||
address->port = 0;
|
||||
uint16_t size, r = 0;
|
||||
s.read ((char *)&size, sizeof (size)); if (!s) return;
|
||||
size = be16toh (size);
|
||||
while (r < size)
|
||||
{
|
||||
char key[255], value[255];
|
||||
r += ReadString (key, 255, s);
|
||||
s.seekg (1, std::ios_base::cur); r++; // =
|
||||
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;
|
||||
address->host = boost::asio::ip::address::from_string (value, ecode);
|
||||
if (ecode)
|
||||
{
|
||||
if (address->transportStyle == eTransportNTCP)
|
||||
{
|
||||
supportedTransports |= eNTCPV4; // TODO:
|
||||
address->addressString = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
supportedTransports |= eSSUV4; // TODO:
|
||||
address->addressString = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// add supported protocol
|
||||
if (address->host.is_v4 ())
|
||||
supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4;
|
||||
else
|
||||
supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6;
|
||||
}
|
||||
}
|
||||
else if (!strcmp (key, "port"))
|
||||
address->port = boost::lexical_cast<int>(value);
|
||||
else if (!strcmp (key, "mtu"))
|
||||
{
|
||||
if (address->ssu)
|
||||
address->ssu->mtu = boost::lexical_cast<int>(value);
|
||||
else
|
||||
LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP");
|
||||
}
|
||||
else if (!strcmp (key, "key"))
|
||||
{
|
||||
if (address->ssu)
|
||||
Base64ToByteStream (value, strlen (value), address->ssu->key, 32);
|
||||
else
|
||||
LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP");
|
||||
}
|
||||
else if (!strcmp (key, "caps"))
|
||||
ExtractCaps (value);
|
||||
else if (key[0] == 'i')
|
||||
{
|
||||
// introducers
|
||||
introducers = true;
|
||||
size_t l = strlen(key);
|
||||
unsigned char index = key[l-1] - '0'; // TODO:
|
||||
key[l-1] = 0;
|
||||
if (index > 9)
|
||||
{
|
||||
LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped");
|
||||
if (s) continue; else return;
|
||||
}
|
||||
if (index >= address->ssu->introducers.size ())
|
||||
address->ssu->introducers.resize (index + 1);
|
||||
Introducer& introducer = address->ssu->introducers.at (index);
|
||||
if (!strcmp (key, "ihost"))
|
||||
{
|
||||
boost::system::error_code ecode;
|
||||
introducer.iHost = boost::asio::ip::address::from_string (value, ecode);
|
||||
}
|
||||
else if (!strcmp (key, "iport"))
|
||||
introducer.iPort = boost::lexical_cast<int>(value);
|
||||
else if (!strcmp (key, "itag"))
|
||||
introducer.iTag = boost::lexical_cast<uint32_t>(value);
|
||||
else if (!strcmp (key, "ikey"))
|
||||
Base64ToByteStream (value, strlen (value), introducer.iKey, 32);
|
||||
}
|
||||
if (!s) return;
|
||||
}
|
||||
if (isValidAddress)
|
||||
{
|
||||
addresses->push_back(address);
|
||||
m_SupportedTransports |= supportedTransports;
|
||||
}
|
||||
}
|
||||
#if (BOOST_VERSION >= 105300)
|
||||
boost::atomic_store (&m_Addresses, addresses);
|
||||
#else
|
||||
m_Addresses = addresses; // race condition
|
||||
#endif
|
||||
// read peers
|
||||
uint8_t numPeers;
|
||||
s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
|
||||
s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
|
||||
// read properties
|
||||
uint16_t size, r = 0;
|
||||
s.read ((char *)&size, sizeof (size)); if (!s) return;
|
||||
size = be16toh (size);
|
||||
while (r < size)
|
||||
{
|
||||
char key[255], value[255];
|
||||
r += ReadString (key, 255, s);
|
||||
s.seekg (1, std::ios_base::cur); r++; // =
|
||||
r += ReadString (value, 255, s);
|
||||
s.seekg (1, std::ios_base::cur); r++; // ;
|
||||
if (!s) return;
|
||||
m_Properties[key] = value;
|
||||
|
||||
// extract caps
|
||||
if (!strcmp (key, "caps"))
|
||||
ExtractCaps (value);
|
||||
// check netId
|
||||
else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != i2p::context.GetNetID ())
|
||||
{
|
||||
LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value);
|
||||
m_IsUnreachable = true;
|
||||
}
|
||||
// family
|
||||
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY))
|
||||
{
|
||||
m_Family = value;
|
||||
boost::to_lower (m_Family);
|
||||
}
|
||||
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG))
|
||||
{
|
||||
if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value))
|
||||
{
|
||||
LogPrint (eLogWarning, "RouterInfo: family signature verification failed");
|
||||
m_Family.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
if (!s) return;
|
||||
}
|
||||
|
||||
if (!m_SupportedTransports || !m_Addresses->size() || (UsesIntroducer () && !introducers))
|
||||
SetUnreachable (true);
|
||||
}
|
||||
|
||||
bool RouterInfo::IsFamily(const std::string & fam) const {
|
||||
return m_Family == fam;
|
||||
}
|
||||
|
||||
void RouterInfo::ExtractCaps (const char * value)
|
||||
{
|
||||
const char * cap = value;
|
||||
while (*cap)
|
||||
{
|
||||
switch (*cap)
|
||||
{
|
||||
case CAPS_FLAG_FLOODFILL:
|
||||
m_Caps |= Caps::eFloodfill;
|
||||
break;
|
||||
case CAPS_FLAG_HIGH_BANDWIDTH1:
|
||||
case CAPS_FLAG_HIGH_BANDWIDTH2:
|
||||
case CAPS_FLAG_HIGH_BANDWIDTH3:
|
||||
m_Caps |= Caps::eHighBandwidth;
|
||||
break;
|
||||
case CAPS_FLAG_EXTRA_BANDWIDTH1:
|
||||
case CAPS_FLAG_EXTRA_BANDWIDTH2:
|
||||
m_Caps |= Caps::eExtraBandwidth;
|
||||
break;
|
||||
case CAPS_FLAG_HIDDEN:
|
||||
m_Caps |= Caps::eHidden;
|
||||
break;
|
||||
case CAPS_FLAG_REACHABLE:
|
||||
m_Caps |= Caps::eReachable;
|
||||
break;
|
||||
case CAPS_FLAG_UNREACHABLE:
|
||||
m_Caps |= Caps::eUnreachable;
|
||||
break;
|
||||
case CAPS_FLAG_SSU_TESTING:
|
||||
m_Caps |= Caps::eSSUTesting;
|
||||
break;
|
||||
case CAPS_FLAG_SSU_INTRODUCER:
|
||||
m_Caps |= Caps::eSSUIntroducer;
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
cap++;
|
||||
}
|
||||
}
|
||||
|
||||
void RouterInfo::UpdateCapsProperty ()
|
||||
{
|
||||
std::string caps;
|
||||
if (m_Caps & eFloodfill)
|
||||
{
|
||||
if (m_Caps & eExtraBandwidth) caps += (m_Caps & eHighBandwidth) ?
|
||||
CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X'
|
||||
CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P'
|
||||
caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
|
||||
caps += CAPS_FLAG_FLOODFILL; // floodfill
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Caps & eExtraBandwidth)
|
||||
{
|
||||
caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */
|
||||
caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
|
||||
}
|
||||
else
|
||||
caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth
|
||||
}
|
||||
if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden
|
||||
if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable
|
||||
if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable
|
||||
|
||||
SetProperty ("caps", caps);
|
||||
}
|
||||
|
||||
void RouterInfo::WriteToStream (std::ostream& s) const
|
||||
{
|
||||
uint64_t ts = htobe64 (m_Timestamp);
|
||||
s.write ((const char *)&ts, sizeof (ts));
|
||||
|
||||
// addresses
|
||||
uint8_t numAddresses = m_Addresses->size ();
|
||||
s.write ((char *)&numAddresses, sizeof (numAddresses));
|
||||
for (const auto& addr_ptr : *m_Addresses)
|
||||
{
|
||||
const Address& address = *addr_ptr;
|
||||
s.write ((const char *)&address.cost, sizeof (address.cost));
|
||||
s.write ((const char *)&address.date, sizeof (address.date));
|
||||
std::stringstream properties;
|
||||
if (address.transportStyle == eTransportNTCP)
|
||||
WriteString ("NTCP", s);
|
||||
else if (address.transportStyle == eTransportSSU)
|
||||
{
|
||||
WriteString ("SSU", s);
|
||||
// caps
|
||||
WriteString ("caps", properties);
|
||||
properties << '=';
|
||||
std::string caps;
|
||||
if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING;
|
||||
if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
|
||||
WriteString (caps, properties);
|
||||
properties << ';';
|
||||
}
|
||||
else
|
||||
WriteString ("", s);
|
||||
|
||||
WriteString ("host", properties);
|
||||
properties << '=';
|
||||
WriteString (address.host.to_string (), properties);
|
||||
properties << ';';
|
||||
if (address.transportStyle == eTransportSSU)
|
||||
{
|
||||
// write introducers if any
|
||||
if (address.ssu->introducers.size () > 0)
|
||||
{
|
||||
int i = 0;
|
||||
for (const auto& introducer: address.ssu->introducers)
|
||||
{
|
||||
WriteString ("ihost" + boost::lexical_cast<std::string>(i), properties);
|
||||
properties << '=';
|
||||
WriteString (introducer.iHost.to_string (), properties);
|
||||
properties << ';';
|
||||
i++;
|
||||
}
|
||||
i = 0;
|
||||
for (const auto& introducer: address.ssu->introducers)
|
||||
{
|
||||
WriteString ("ikey" + boost::lexical_cast<std::string>(i), properties);
|
||||
properties << '=';
|
||||
char value[64];
|
||||
size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64);
|
||||
value[l] = 0;
|
||||
WriteString (value, properties);
|
||||
properties << ';';
|
||||
i++;
|
||||
}
|
||||
i = 0;
|
||||
for (const auto& introducer: address.ssu->introducers)
|
||||
{
|
||||
WriteString ("iport" + boost::lexical_cast<std::string>(i), properties);
|
||||
properties << '=';
|
||||
WriteString (boost::lexical_cast<std::string>(introducer.iPort), properties);
|
||||
properties << ';';
|
||||
i++;
|
||||
}
|
||||
i = 0;
|
||||
for (const auto& introducer: address.ssu->introducers)
|
||||
{
|
||||
WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
|
||||
properties << '=';
|
||||
WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
|
||||
properties << ';';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// write intro key
|
||||
WriteString ("key", properties);
|
||||
properties << '=';
|
||||
char value[64];
|
||||
size_t l = ByteStreamToBase64 (address.ssu->key, 32, value, 64);
|
||||
value[l] = 0;
|
||||
WriteString (value, properties);
|
||||
properties << ';';
|
||||
// write mtu
|
||||
if (address.ssu->mtu)
|
||||
{
|
||||
WriteString ("mtu", properties);
|
||||
properties << '=';
|
||||
WriteString (boost::lexical_cast<std::string>(address.ssu->mtu), properties);
|
||||
properties << ';';
|
||||
}
|
||||
}
|
||||
WriteString ("port", properties);
|
||||
properties << '=';
|
||||
WriteString (boost::lexical_cast<std::string>(address.port), properties);
|
||||
properties << ';';
|
||||
|
||||
uint16_t size = htobe16 (properties.str ().size ());
|
||||
s.write ((char *)&size, sizeof (size));
|
||||
s.write (properties.str ().c_str (), properties.str ().size ());
|
||||
}
|
||||
|
||||
// peers
|
||||
uint8_t numPeers = 0;
|
||||
s.write ((char *)&numPeers, sizeof (numPeers));
|
||||
|
||||
// properties
|
||||
std::stringstream properties;
|
||||
for (const auto& p : m_Properties)
|
||||
{
|
||||
WriteString (p.first, properties);
|
||||
properties << '=';
|
||||
WriteString (p.second, properties);
|
||||
properties << ';';
|
||||
}
|
||||
uint16_t size = htobe16 (properties.str ().size ());
|
||||
s.write ((char *)&size, sizeof (size));
|
||||
s.write (properties.str ().c_str (), properties.str ().size ());
|
||||
}
|
||||
|
||||
bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const
|
||||
{
|
||||
if (!m_RouterIdentity) return false;
|
||||
size_t size = m_RouterIdentity->GetFullLen ();
|
||||
if (size + 8 > len) return false;
|
||||
return bufbe64toh (buf + size) > m_Timestamp;
|
||||
}
|
||||
|
||||
const uint8_t * RouterInfo::LoadBuffer ()
|
||||
{
|
||||
if (!m_Buffer)
|
||||
{
|
||||
if (LoadFile ())
|
||||
LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file");
|
||||
}
|
||||
return m_Buffer;
|
||||
}
|
||||
|
||||
void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys)
|
||||
{
|
||||
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp
|
||||
std::stringstream s;
|
||||
uint8_t ident[1024];
|
||||
auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024);
|
||||
s.write ((char *)ident, identLen);
|
||||
WriteToStream (s);
|
||||
m_BufferLen = s.str ().size ();
|
||||
if (!m_Buffer)
|
||||
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
|
||||
memcpy (m_Buffer, s.str ().c_str (), m_BufferLen);
|
||||
// signature
|
||||
privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen);
|
||||
m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen ();
|
||||
}
|
||||
|
||||
bool RouterInfo::SaveToFile (const std::string& fullPath)
|
||||
{
|
||||
m_FullPath = fullPath;
|
||||
if (!m_Buffer) {
|
||||
LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL");
|
||||
return false;
|
||||
}
|
||||
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
|
||||
if (!f.is_open ()) {
|
||||
LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath);
|
||||
return false;
|
||||
}
|
||||
f.write ((char *)m_Buffer, m_BufferLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const
|
||||
{
|
||||
uint8_t l;
|
||||
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);
|
||||
s.seekg (l, std::ios::cur); // skip
|
||||
str[0] = 0;
|
||||
}
|
||||
return l+1;
|
||||
}
|
||||
|
||||
void RouterInfo::WriteString (const std::string& str, std::ostream& s) const
|
||||
{
|
||||
uint8_t len = str.size ();
|
||||
s.write ((char *)&len, 1);
|
||||
s.write (str.c_str (), len);
|
||||
}
|
||||
|
||||
void RouterInfo::AddNTCPAddress (const char * host, int port)
|
||||
{
|
||||
auto addr = std::make_shared<Address>();
|
||||
addr->host = boost::asio::ip::address::from_string (host);
|
||||
addr->port = port;
|
||||
addr->transportStyle = eTransportNTCP;
|
||||
addr->cost = 2;
|
||||
addr->date = 0;
|
||||
for (const auto& it: *m_Addresses) // don't insert same address twice
|
||||
if (*it == *addr) return;
|
||||
m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4;
|
||||
m_Addresses->push_back(std::move(addr));
|
||||
}
|
||||
|
||||
void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu)
|
||||
{
|
||||
auto addr = std::make_shared<Address>();
|
||||
addr->host = boost::asio::ip::address::from_string (host);
|
||||
addr->port = port;
|
||||
addr->transportStyle = eTransportSSU;
|
||||
addr->cost = 10; // NTCP should have priority over SSU
|
||||
addr->date = 0;
|
||||
addr->ssu.reset (new SSUExt ());
|
||||
addr->ssu->mtu = mtu;
|
||||
memcpy (addr->ssu->key, key, 32);
|
||||
for (const auto& it: *m_Addresses) // don't insert same address twice
|
||||
if (*it == *addr) return;
|
||||
m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4;
|
||||
m_Addresses->push_back(std::move(addr));
|
||||
|
||||
m_Caps |= eSSUTesting;
|
||||
m_Caps |= eSSUIntroducer;
|
||||
}
|
||||
|
||||
bool RouterInfo::AddIntroducer (const Introducer& introducer)
|
||||
{
|
||||
for (auto& addr : *m_Addresses)
|
||||
{
|
||||
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
|
||||
{
|
||||
for (auto& intro: addr->ssu->introducers)
|
||||
if (intro.iTag == introducer.iTag) return false; // already presented
|
||||
addr->ssu->introducers.push_back (introducer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
|
||||
{
|
||||
for (auto& addr: *m_Addresses)
|
||||
{
|
||||
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
|
||||
{
|
||||
for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it)
|
||||
if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e)
|
||||
{
|
||||
addr->ssu->introducers.erase (it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RouterInfo::SetCaps (uint8_t caps)
|
||||
{
|
||||
m_Caps = caps;
|
||||
UpdateCapsProperty ();
|
||||
}
|
||||
|
||||
void RouterInfo::SetCaps (const char * caps)
|
||||
{
|
||||
SetProperty ("caps", caps);
|
||||
m_Caps = 0;
|
||||
ExtractCaps (caps);
|
||||
}
|
||||
|
||||
void RouterInfo::SetProperty (const std::string& key, const std::string& value)
|
||||
{
|
||||
m_Properties[key] = value;
|
||||
}
|
||||
|
||||
void RouterInfo::DeleteProperty (const std::string& key)
|
||||
{
|
||||
m_Properties.erase (key);
|
||||
}
|
||||
|
||||
std::string RouterInfo::GetProperty (const std::string& key) const
|
||||
{
|
||||
auto it = m_Properties.find (key);
|
||||
if (it != m_Properties.end ())
|
||||
return it->second;
|
||||
return "";
|
||||
}
|
||||
|
||||
bool RouterInfo::IsNTCP (bool v4only) const
|
||||
{
|
||||
if (v4only)
|
||||
return m_SupportedTransports & eNTCPV4;
|
||||
else
|
||||
return m_SupportedTransports & (eNTCPV4 | eNTCPV6);
|
||||
}
|
||||
|
||||
bool RouterInfo::IsSSU (bool v4only) const
|
||||
{
|
||||
if (v4only)
|
||||
return m_SupportedTransports & eSSUV4;
|
||||
else
|
||||
return m_SupportedTransports & (eSSUV4 | eSSUV6);
|
||||
}
|
||||
|
||||
bool RouterInfo::IsV6 () const
|
||||
{
|
||||
return m_SupportedTransports & (eNTCPV6 | eSSUV6);
|
||||
}
|
||||
|
||||
bool RouterInfo::IsV4 () const
|
||||
{
|
||||
return m_SupportedTransports & (eNTCPV4 | eSSUV4);
|
||||
}
|
||||
|
||||
void RouterInfo::EnableV6 ()
|
||||
{
|
||||
if (!IsV6 ())
|
||||
m_SupportedTransports |= eNTCPV6 | eSSUV6;
|
||||
}
|
||||
|
||||
void RouterInfo::EnableV4 ()
|
||||
{
|
||||
if (!IsV4 ())
|
||||
m_SupportedTransports |= eNTCPV4 | eSSUV4;
|
||||
}
|
||||
|
||||
|
||||
void RouterInfo::DisableV6 ()
|
||||
{
|
||||
if (IsV6 ())
|
||||
{
|
||||
m_SupportedTransports &= ~(eNTCPV6 | eSSUV6);
|
||||
for (auto it = m_Addresses->begin (); it != m_Addresses->end ();)
|
||||
{
|
||||
auto addr = *it;
|
||||
if (addr->host.is_v6 ())
|
||||
it = m_Addresses->erase (it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RouterInfo::DisableV4 ()
|
||||
{
|
||||
if (IsV4 ())
|
||||
{
|
||||
m_SupportedTransports &= ~(eNTCPV4 | eSSUV4);
|
||||
for (auto it = m_Addresses->begin (); it != m_Addresses->end ();)
|
||||
{
|
||||
auto addr = *it;
|
||||
if (addr->host.is_v4 ())
|
||||
it = m_Addresses->erase (it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool RouterInfo::UsesIntroducer () const
|
||||
{
|
||||
return m_Caps & Caps::eUnreachable; // non-reachable
|
||||
}
|
||||
|
||||
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetNTCPAddress (bool v4only) const
|
||||
{
|
||||
return GetAddress (eTransportNTCP, v4only);
|
||||
}
|
||||
|
||||
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const
|
||||
{
|
||||
return GetAddress (eTransportSSU, v4only);
|
||||
}
|
||||
|
||||
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUV6Address () const
|
||||
{
|
||||
return GetAddress (eTransportSSU, false, true);
|
||||
}
|
||||
|
||||
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const
|
||||
{
|
||||
#if (BOOST_VERSION >= 105300)
|
||||
auto addresses = boost::atomic_load (&m_Addresses);
|
||||
#else
|
||||
auto addresses = m_Addresses;
|
||||
#endif
|
||||
for (const auto& address : *addresses)
|
||||
{
|
||||
if (address->transportStyle == s)
|
||||
{
|
||||
if ((!v4only || address->host.is_v4 ()) && (!v6only || address->host.is_v6 ()))
|
||||
return address;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<RouterProfile> RouterInfo::GetProfile () const
|
||||
{
|
||||
if (!m_Profile)
|
||||
m_Profile = GetRouterProfile (GetIdentHash ());
|
||||
return m_Profile;
|
||||
}
|
||||
}
|
||||
}
|
219
libi2pd/RouterInfo.h
Normal file
219
libi2pd/RouterInfo.h
Normal file
|
@ -0,0 +1,219 @@
|
|||
#ifndef ROUTER_INFO_H__
|
||||
#define ROUTER_INFO_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include "Identity.h"
|
||||
#include "Profiling.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets";
|
||||
const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters";
|
||||
const char ROUTER_INFO_PROPERTY_NETID[] = "netId";
|
||||
const char ROUTER_INFO_PROPERTY_FAMILY[] = "family";
|
||||
const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig";
|
||||
|
||||
const char CAPS_FLAG_FLOODFILL = 'f';
|
||||
const char CAPS_FLAG_HIDDEN = 'H';
|
||||
const char CAPS_FLAG_REACHABLE = 'R';
|
||||
const char CAPS_FLAG_UNREACHABLE = 'U';
|
||||
/* bandwidth flags */
|
||||
const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */
|
||||
const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */
|
||||
const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */
|
||||
const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */
|
||||
const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */
|
||||
const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */
|
||||
const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */
|
||||
|
||||
const char CAPS_FLAG_SSU_TESTING = 'B';
|
||||
const char CAPS_FLAG_SSU_INTRODUCER = 'C';
|
||||
|
||||
const int MAX_RI_BUFFER_SIZE = 2048;
|
||||
class RouterInfo: public RoutingDestination
|
||||
{
|
||||
public:
|
||||
|
||||
enum SupportedTranports
|
||||
{
|
||||
eNTCPV4 = 0x01,
|
||||
eNTCPV6 = 0x02,
|
||||
eSSUV4 = 0x04,
|
||||
eSSUV6 = 0x08
|
||||
};
|
||||
|
||||
enum Caps
|
||||
{
|
||||
eFloodfill = 0x01,
|
||||
eHighBandwidth = 0x02,
|
||||
eExtraBandwidth = 0x04,
|
||||
eReachable = 0x08,
|
||||
eSSUTesting = 0x10,
|
||||
eSSUIntroducer = 0x20,
|
||||
eHidden = 0x40,
|
||||
eUnreachable = 0x80
|
||||
};
|
||||
|
||||
enum TransportStyle
|
||||
{
|
||||
eTransportUnknown = 0,
|
||||
eTransportNTCP,
|
||||
eTransportSSU
|
||||
};
|
||||
|
||||
typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey
|
||||
struct Introducer
|
||||
{
|
||||
boost::asio::ip::address iHost;
|
||||
int iPort;
|
||||
IntroKey iKey;
|
||||
uint32_t iTag;
|
||||
};
|
||||
|
||||
struct SSUExt
|
||||
{
|
||||
int mtu;
|
||||
IntroKey key; // intro key for SSU
|
||||
std::vector<Introducer> introducers;
|
||||
};
|
||||
|
||||
struct Address
|
||||
{
|
||||
TransportStyle transportStyle;
|
||||
boost::asio::ip::address host;
|
||||
std::string addressString;
|
||||
int port;
|
||||
uint64_t date;
|
||||
uint8_t cost;
|
||||
std::unique_ptr<SSUExt> ssu; // not null for SSU
|
||||
|
||||
bool IsCompatible (const boost::asio::ip::address& other) const
|
||||
{
|
||||
return (host.is_v4 () && other.is_v4 ()) ||
|
||||
(host.is_v6 () && other.is_v6 ());
|
||||
}
|
||||
|
||||
bool operator==(const Address& other) const
|
||||
{
|
||||
return transportStyle == other.transportStyle && host == other.host && port == other.port;
|
||||
}
|
||||
|
||||
bool operator!=(const Address& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
typedef std::list<std::shared_ptr<Address> > Addresses;
|
||||
|
||||
RouterInfo ();
|
||||
RouterInfo (const std::string& fullPath);
|
||||
RouterInfo (const RouterInfo& ) = default;
|
||||
RouterInfo& operator=(const RouterInfo& ) = default;
|
||||
RouterInfo (const uint8_t * buf, int len);
|
||||
~RouterInfo ();
|
||||
|
||||
std::shared_ptr<const IdentityEx> GetRouterIdentity () const { return m_RouterIdentity; };
|
||||
void SetRouterIdentity (std::shared_ptr<const IdentityEx> identity);
|
||||
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
|
||||
uint64_t GetTimestamp () const { return m_Timestamp; };
|
||||
Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr
|
||||
std::shared_ptr<const Address> GetNTCPAddress (bool v4only = true) const;
|
||||
std::shared_ptr<const Address> GetSSUAddress (bool v4only = true) const;
|
||||
std::shared_ptr<const Address> GetSSUV6Address () const;
|
||||
|
||||
void AddNTCPAddress (const char * host, int port);
|
||||
void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0);
|
||||
bool AddIntroducer (const Introducer& introducer);
|
||||
bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
|
||||
void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only
|
||||
void DeleteProperty (const std::string& key); // called from RouterContext only
|
||||
std::string GetProperty (const std::string& key) const; // called from RouterContext only
|
||||
void ClearProperties () { m_Properties.clear (); };
|
||||
bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
|
||||
bool IsReachable () const { return m_Caps & Caps::eReachable; };
|
||||
bool IsNTCP (bool v4only = true) const;
|
||||
bool IsSSU (bool v4only = true) const;
|
||||
bool IsV6 () const;
|
||||
bool IsV4 () const;
|
||||
void EnableV6 ();
|
||||
void DisableV6 ();
|
||||
void EnableV4 ();
|
||||
void DisableV4 ();
|
||||
bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; };
|
||||
bool UsesIntroducer () const;
|
||||
bool IsIntroducer () const { return m_Caps & eSSUIntroducer; };
|
||||
bool IsPeerTesting () const { return m_Caps & eSSUTesting; };
|
||||
bool IsHidden () const { return m_Caps & eHidden; };
|
||||
bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
|
||||
bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; };
|
||||
|
||||
uint8_t GetCaps () const { return m_Caps; };
|
||||
void SetCaps (uint8_t caps);
|
||||
void SetCaps (const char * caps);
|
||||
|
||||
void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; };
|
||||
bool IsUnreachable () const { return m_IsUnreachable; };
|
||||
|
||||
const uint8_t * GetBuffer () const { return m_Buffer; };
|
||||
const uint8_t * LoadBuffer (); // load if necessary
|
||||
int GetBufferLen () const { return m_BufferLen; };
|
||||
void CreateBuffer (const PrivateKeys& privateKeys);
|
||||
|
||||
bool IsUpdated () const { return m_IsUpdated; };
|
||||
void SetUpdated (bool updated) { m_IsUpdated = updated; };
|
||||
bool SaveToFile (const std::string& fullPath);
|
||||
|
||||
std::shared_ptr<RouterProfile> GetProfile () const;
|
||||
void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); };
|
||||
|
||||
void Update (const uint8_t * buf, int len);
|
||||
void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
|
||||
bool IsNewer (const uint8_t * buf, size_t len) const;
|
||||
|
||||
/** return true if we are in a router family and the signature is valid */
|
||||
bool IsFamily(const std::string & fam) const;
|
||||
|
||||
// implements RoutingDestination
|
||||
const IdentHash& GetIdentHash () const { return m_RouterIdentity->GetIdentHash (); };
|
||||
const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; };
|
||||
bool IsDestination () const { return false; };
|
||||
|
||||
private:
|
||||
|
||||
bool LoadFile ();
|
||||
void ReadFromFile ();
|
||||
void ReadFromStream (std::istream& s);
|
||||
void ReadFromBuffer (bool verifySignature);
|
||||
void WriteToStream (std::ostream& s) const;
|
||||
size_t ReadString (char* str, size_t len, std::istream& s) const;
|
||||
void WriteString (const std::string& str, std::ostream& s) const;
|
||||
void ExtractCaps (const char * value);
|
||||
std::shared_ptr<const Address> GetAddress (TransportStyle s, bool v4only, bool v6only = false) const;
|
||||
void UpdateCapsProperty ();
|
||||
|
||||
private:
|
||||
|
||||
std::string m_FullPath, m_Family;
|
||||
std::shared_ptr<const IdentityEx> m_RouterIdentity;
|
||||
uint8_t * m_Buffer;
|
||||
size_t m_BufferLen;
|
||||
uint64_t m_Timestamp;
|
||||
boost::shared_ptr<Addresses> m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9
|
||||
std::map<std::string, std::string> m_Properties;
|
||||
bool m_IsUpdated, m_IsUnreachable;
|
||||
uint8_t m_SupportedTransports, m_Caps;
|
||||
mutable std::shared_ptr<RouterProfile> m_Profile;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
782
libi2pd/SSU.cpp
Normal file
782
libi2pd/SSU.cpp
Normal file
|
@ -0,0 +1,782 @@
|
|||
#include <string.h>
|
||||
#include <boost/bind.hpp>
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "RouterContext.h"
|
||||
#include "NetDb.h"
|
||||
#include "SSU.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
|
||||
SSUServer::SSUServer (const boost::asio::ip::address & addr, int port):
|
||||
m_OnlyV6(true), m_IsRunning(false),
|
||||
m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
|
||||
m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6),
|
||||
m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6),
|
||||
m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint),
|
||||
m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service),
|
||||
m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service),
|
||||
m_TerminationTimerV6 (m_ServiceV6)
|
||||
{
|
||||
OpenSocketV6 ();
|
||||
}
|
||||
|
||||
SSUServer::SSUServer (int port):
|
||||
m_OnlyV6(false), m_IsRunning(false),
|
||||
m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
|
||||
m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6),
|
||||
m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6),
|
||||
m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port),
|
||||
m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6),
|
||||
m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service),
|
||||
m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6)
|
||||
{
|
||||
OpenSocket ();
|
||||
if (context.SupportsV6 ())
|
||||
OpenSocketV6 ();
|
||||
}
|
||||
|
||||
SSUServer::~SSUServer ()
|
||||
{
|
||||
}
|
||||
|
||||
void SSUServer::OpenSocket ()
|
||||
{
|
||||
m_Socket.open (boost::asio::ip::udp::v4());
|
||||
m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE));
|
||||
m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE));
|
||||
m_Socket.bind (m_Endpoint);
|
||||
}
|
||||
|
||||
void SSUServer::OpenSocketV6 ()
|
||||
{
|
||||
m_SocketV6.open (boost::asio::ip::udp::v6());
|
||||
m_SocketV6.set_option (boost::asio::ip::v6_only (true));
|
||||
m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE));
|
||||
m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE));
|
||||
m_SocketV6.bind (m_EndpointV6);
|
||||
}
|
||||
|
||||
void SSUServer::Start ()
|
||||
{
|
||||
m_IsRunning = true;
|
||||
if (!m_OnlyV6)
|
||||
{
|
||||
m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this));
|
||||
m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
|
||||
m_ReceiversService.post (std::bind (&SSUServer::Receive, this));
|
||||
ScheduleTermination ();
|
||||
}
|
||||
if (context.SupportsV6 ())
|
||||
{
|
||||
m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this));
|
||||
m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this));
|
||||
m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this));
|
||||
ScheduleTerminationV6 ();
|
||||
}
|
||||
SchedulePeerTestsCleanupTimer ();
|
||||
ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
|
||||
}
|
||||
|
||||
void SSUServer::Stop ()
|
||||
{
|
||||
DeleteAllSessions ();
|
||||
m_IsRunning = false;
|
||||
m_TerminationTimer.cancel ();
|
||||
m_TerminationTimerV6.cancel ();
|
||||
m_Service.stop ();
|
||||
m_Socket.close ();
|
||||
m_ServiceV6.stop ();
|
||||
m_SocketV6.close ();
|
||||
m_ReceiversService.stop ();
|
||||
m_ReceiversServiceV6.stop ();
|
||||
if (m_ReceiversThread)
|
||||
{
|
||||
m_ReceiversThread->join ();
|
||||
delete m_ReceiversThread;
|
||||
m_ReceiversThread = nullptr;
|
||||
}
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = nullptr;
|
||||
}
|
||||
if (m_ReceiversThreadV6)
|
||||
{
|
||||
m_ReceiversThreadV6->join ();
|
||||
delete m_ReceiversThreadV6;
|
||||
m_ReceiversThreadV6 = nullptr;
|
||||
}
|
||||
if (m_ThreadV6)
|
||||
{
|
||||
m_ThreadV6->join ();
|
||||
delete m_ThreadV6;
|
||||
m_ThreadV6 = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::Run ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Service.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::RunV6 ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_ServiceV6.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::RunReceivers ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_ReceiversService.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::RunReceiversV6 ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_ReceiversServiceV6.run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::AddRelay (uint32_t tag, std::shared_ptr<SSUSession> relay)
|
||||
{
|
||||
m_Relays[tag] = relay;
|
||||
}
|
||||
|
||||
void SSUServer::RemoveRelay (uint32_t tag)
|
||||
{
|
||||
m_Relays.erase (tag);
|
||||
}
|
||||
|
||||
std::shared_ptr<SSUSession> SSUServer::FindRelaySession (uint32_t tag)
|
||||
{
|
||||
auto it = m_Relays.find (tag);
|
||||
if (it != m_Relays.end ())
|
||||
{
|
||||
if (it->second->GetState () == eSessionStateEstablished)
|
||||
return it->second;
|
||||
else
|
||||
m_Relays.erase (it);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to)
|
||||
{
|
||||
if (to.protocol () == boost::asio::ip::udp::v4())
|
||||
m_Socket.send_to (boost::asio::buffer (buf, len), to);
|
||||
else
|
||||
m_SocketV6.send_to (boost::asio::buffer (buf, len), to);
|
||||
}
|
||||
|
||||
void SSUServer::Receive ()
|
||||
{
|
||||
SSUPacket * packet = new SSUPacket ();
|
||||
m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from,
|
||||
std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet));
|
||||
}
|
||||
|
||||
void SSUServer::ReceiveV6 ()
|
||||
{
|
||||
SSUPacket * packet = new SSUPacket ();
|
||||
m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from,
|
||||
std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet));
|
||||
}
|
||||
|
||||
void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
packet->len = bytes_transferred;
|
||||
std::vector<SSUPacket *> packets;
|
||||
packets.push_back (packet);
|
||||
|
||||
boost::system::error_code ec;
|
||||
size_t moreBytes = m_Socket.available(ec);
|
||||
if (!ec)
|
||||
{
|
||||
while (moreBytes && packets.size () < 25)
|
||||
{
|
||||
packet = new SSUPacket ();
|
||||
packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec);
|
||||
if (!ec)
|
||||
{
|
||||
packets.push_back (packet);
|
||||
moreBytes = m_Socket.available(ec);
|
||||
if (ec) break;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "SSU: receive_from error: ", ec.message ());
|
||||
delete packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions));
|
||||
Receive ();
|
||||
}
|
||||
else
|
||||
{
|
||||
delete packet;
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: receive error: ", ecode.message ());
|
||||
m_Socket.close ();
|
||||
OpenSocket ();
|
||||
Receive ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
|
||||
{
|
||||
if (!ecode)
|
||||
{
|
||||
packet->len = bytes_transferred;
|
||||
std::vector<SSUPacket *> packets;
|
||||
packets.push_back (packet);
|
||||
|
||||
boost::system::error_code ec;
|
||||
size_t moreBytes = m_SocketV6.available (ec);
|
||||
if (!ec)
|
||||
{
|
||||
while (moreBytes && packets.size () < 25)
|
||||
{
|
||||
packet = new SSUPacket ();
|
||||
packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec);
|
||||
if (!ec)
|
||||
{
|
||||
packets.push_back (packet);
|
||||
moreBytes = m_SocketV6.available(ec);
|
||||
if (ec) break;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "SSU: v6 receive_from error: ", ec.message ());
|
||||
delete packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6));
|
||||
ReceiveV6 ();
|
||||
}
|
||||
else
|
||||
{
|
||||
delete packet;
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ());
|
||||
m_SocketV6.close ();
|
||||
OpenSocketV6 ();
|
||||
ReceiveV6 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::HandleReceivedPackets (std::vector<SSUPacket *> packets,
|
||||
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > * sessions)
|
||||
{
|
||||
std::shared_ptr<SSUSession> session;
|
||||
for (auto& packet: packets)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous
|
||||
{
|
||||
if (session) session->FlushData ();
|
||||
auto it = sessions->find (packet->from);
|
||||
if (it != sessions->end ())
|
||||
session = it->second;
|
||||
if (!session)
|
||||
{
|
||||
session = std::make_shared<SSUSession> (*this, packet->from);
|
||||
session->WaitForConnect ();
|
||||
(*sessions)[packet->from] = session;
|
||||
LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created");
|
||||
}
|
||||
}
|
||||
session->ProcessNextMessage (packet->buf, packet->len, packet->from);
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ());
|
||||
if (session) session->FlushData ();
|
||||
session = nullptr;
|
||||
}
|
||||
delete packet;
|
||||
}
|
||||
if (session) session->FlushData ();
|
||||
}
|
||||
|
||||
std::shared_ptr<SSUSession> SSUServer::FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const
|
||||
{
|
||||
if (!router) return nullptr;
|
||||
auto address = router->GetSSUAddress (true); // v4 only
|
||||
if (!address) return nullptr;
|
||||
auto session = FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
|
||||
if (session || !context.SupportsV6 ())
|
||||
return session;
|
||||
// try v6
|
||||
address = router->GetSSUV6Address ();
|
||||
if (!address) return nullptr;
|
||||
return FindSession (boost::asio::ip::udp::endpoint (address->host, address->port));
|
||||
}
|
||||
|
||||
std::shared_ptr<SSUSession> SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const
|
||||
{
|
||||
auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
|
||||
auto it = sessions.find (e);
|
||||
if (it != sessions.end ())
|
||||
return it->second;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest, bool v4only)
|
||||
{
|
||||
auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ());
|
||||
if (address)
|
||||
CreateSession (router, address->host, address->port, peerTest);
|
||||
else
|
||||
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
|
||||
}
|
||||
|
||||
void SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
|
||||
const boost::asio::ip::address& addr, int port, bool peerTest)
|
||||
{
|
||||
if (router)
|
||||
{
|
||||
if (router->UsesIntroducer ())
|
||||
m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread
|
||||
else
|
||||
{
|
||||
boost::asio::ip::udp::endpoint remoteEndpoint (addr, port);
|
||||
auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service;
|
||||
s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest)
|
||||
{
|
||||
auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
|
||||
auto it = sessions.find (remoteEndpoint);
|
||||
if (it != sessions.end ())
|
||||
{
|
||||
auto session = it->second;
|
||||
if (peerTest && session->GetState () == eSessionStateEstablished)
|
||||
session->SendPeerTest ();
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise create new session
|
||||
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
|
||||
sessions[remoteEndpoint] = session;
|
||||
// connect
|
||||
LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ",
|
||||
remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ());
|
||||
session->Connect ();
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest)
|
||||
{
|
||||
if (router && router->UsesIntroducer ())
|
||||
{
|
||||
auto address = router->GetSSUAddress (true); // v4 only for now
|
||||
if (address)
|
||||
{
|
||||
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
|
||||
auto it = m_Sessions.find (remoteEndpoint);
|
||||
// check if session if presented alredy
|
||||
if (it != m_Sessions.end ())
|
||||
{
|
||||
auto session = it->second;
|
||||
if (peerTest && session->GetState () == eSessionStateEstablished)
|
||||
session->SendPeerTest ();
|
||||
return;
|
||||
}
|
||||
// create new session
|
||||
int numIntroducers = address->ssu->introducers.size ();
|
||||
if (numIntroducers > 0)
|
||||
{
|
||||
std::shared_ptr<SSUSession> introducerSession;
|
||||
const i2p::data::RouterInfo::Introducer * introducer = nullptr;
|
||||
// we might have a session to introducer already
|
||||
for (int i = 0; i < numIntroducers; i++)
|
||||
{
|
||||
auto intr = &(address->ssu->introducers[i]);
|
||||
boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort);
|
||||
if (ep.address ().is_v4 ()) // ipv4 only
|
||||
{
|
||||
if (!introducer) introducer = intr; // we pick first one for now
|
||||
it = m_Sessions.find (ep);
|
||||
if (it != m_Sessions.end ())
|
||||
{
|
||||
introducerSession = it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!introducer)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 introducers present");
|
||||
return;
|
||||
}
|
||||
|
||||
if (introducerSession) // session found
|
||||
LogPrint (eLogWarning, "SSU: Session to introducer already exists");
|
||||
else // create new
|
||||
{
|
||||
LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost);
|
||||
boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort);
|
||||
introducerSession = std::make_shared<SSUSession> (*this, introducerEndpoint, router);
|
||||
m_Sessions[introducerEndpoint] = introducerSession;
|
||||
}
|
||||
// create session
|
||||
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
|
||||
m_Sessions[remoteEndpoint] = session;
|
||||
// introduce
|
||||
LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()),
|
||||
"] through introducer ", introducer->iHost, ":", introducer->iPort);
|
||||
session->WaitForIntroduction ();
|
||||
if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable
|
||||
{
|
||||
uint8_t buf[1];
|
||||
Send (buf, 0, remoteEndpoint); // send HolePunch
|
||||
}
|
||||
introducerSession->Introduce (*introducer, router);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present");
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::DeleteSession (std::shared_ptr<SSUSession> session)
|
||||
{
|
||||
if (session)
|
||||
{
|
||||
session->Close ();
|
||||
auto& ep = session->GetRemoteEndpoint ();
|
||||
if (ep.address ().is_v6 ())
|
||||
m_SessionsV6.erase (ep);
|
||||
else
|
||||
m_Sessions.erase (ep);
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::DeleteAllSessions ()
|
||||
{
|
||||
for (auto& it: m_Sessions)
|
||||
it.second->Close ();
|
||||
m_Sessions.clear ();
|
||||
|
||||
for (auto& it: m_SessionsV6)
|
||||
it.second->Close ();
|
||||
m_SessionsV6.clear ();
|
||||
}
|
||||
|
||||
template<typename Filter>
|
||||
std::shared_ptr<SSUSession> SSUServer::GetRandomV4Session (Filter filter) // v4 only
|
||||
{
|
||||
std::vector<std::shared_ptr<SSUSession> > filteredSessions;
|
||||
for (const auto& s :m_Sessions)
|
||||
if (filter (s.second)) filteredSessions.push_back (s.second);
|
||||
if (filteredSessions.size () > 0)
|
||||
{
|
||||
auto ind = rand () % filteredSessions.size ();
|
||||
return filteredSessions[ind];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<SSUSession> SSUServer::GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded) // v4 only
|
||||
{
|
||||
return GetRandomV4Session (
|
||||
[excluded](std::shared_ptr<SSUSession> session)->bool
|
||||
{
|
||||
return session->GetState () == eSessionStateEstablished && session != excluded;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
template<typename Filter>
|
||||
std::shared_ptr<SSUSession> SSUServer::GetRandomV6Session (Filter filter) // v6 only
|
||||
{
|
||||
std::vector<std::shared_ptr<SSUSession> > filteredSessions;
|
||||
for (const auto& s :m_SessionsV6)
|
||||
if (filter (s.second)) filteredSessions.push_back (s.second);
|
||||
if (filteredSessions.size () > 0)
|
||||
{
|
||||
auto ind = rand () % filteredSessions.size ();
|
||||
return filteredSessions[ind];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<SSUSession> SSUServer::GetRandomEstablishedV6Session (std::shared_ptr<const SSUSession> excluded) // v6 only
|
||||
{
|
||||
return GetRandomV6Session (
|
||||
[excluded](std::shared_ptr<SSUSession> session)->bool
|
||||
{
|
||||
return session->GetState () == eSessionStateEstablished && session != excluded;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
std::set<SSUSession *> SSUServer::FindIntroducers (int maxNumIntroducers)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
std::set<SSUSession *> ret;
|
||||
for (int i = 0; i < maxNumIntroducers; i++)
|
||||
{
|
||||
auto session = GetRandomV4Session (
|
||||
[&ret, ts](std::shared_ptr<SSUSession> session)->bool
|
||||
{
|
||||
return session->GetRelayTag () && !ret.count (session.get ()) &&
|
||||
session->GetState () == eSessionStateEstablished &&
|
||||
ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION;
|
||||
}
|
||||
);
|
||||
if (session)
|
||||
{
|
||||
ret.insert (session.get ());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SSUServer::ScheduleIntroducersUpdateTimer ()
|
||||
{
|
||||
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
|
||||
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
|
||||
this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
// timeout expired
|
||||
if (i2p::context.GetStatus () == eRouterStatusTesting)
|
||||
{
|
||||
// we still don't know if we need introducers
|
||||
ScheduleIntroducersUpdateTimer ();
|
||||
return;
|
||||
}
|
||||
if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore
|
||||
// we are firewalled
|
||||
if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable ();
|
||||
std::list<boost::asio::ip::udp::endpoint> newList;
|
||||
size_t numIntroducers = 0;
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (const auto& it : m_Introducers)
|
||||
{
|
||||
auto session = FindSession (it);
|
||||
if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION)
|
||||
{
|
||||
session->SendKeepAlive ();
|
||||
newList.push_back (it);
|
||||
numIntroducers++;
|
||||
}
|
||||
else
|
||||
i2p::context.RemoveIntroducer (it);
|
||||
}
|
||||
|
||||
if (numIntroducers < SSU_MAX_NUM_INTRODUCERS)
|
||||
{
|
||||
// create new
|
||||
auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS);
|
||||
for (const auto& it1: introducers)
|
||||
{
|
||||
const auto& ep = it1->GetRemoteEndpoint ();
|
||||
i2p::data::RouterInfo::Introducer introducer;
|
||||
introducer.iHost = ep.address ();
|
||||
introducer.iPort = ep.port ();
|
||||
introducer.iTag = it1->GetRelayTag ();
|
||||
introducer.iKey = it1->GetIntroKey ();
|
||||
if (i2p::context.AddIntroducer (introducer))
|
||||
{
|
||||
newList.push_back (ep);
|
||||
if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_Introducers = newList;
|
||||
if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS)
|
||||
{
|
||||
auto introducer = i2p::data::netdb.GetRandomIntroducer ();
|
||||
if (introducer)
|
||||
CreateSession (introducer);
|
||||
}
|
||||
ScheduleIntroducersUpdateTimer ();
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session)
|
||||
{
|
||||
m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session };
|
||||
}
|
||||
|
||||
PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce)
|
||||
{
|
||||
auto it = m_PeerTests.find (nonce);
|
||||
if (it != m_PeerTests.end ())
|
||||
return it->second.role;
|
||||
else
|
||||
return ePeerTestParticipantUnknown;
|
||||
}
|
||||
|
||||
std::shared_ptr<SSUSession> SSUServer::GetPeerTestSession (uint32_t nonce)
|
||||
{
|
||||
auto it = m_PeerTests.find (nonce);
|
||||
if (it != m_PeerTests.end ())
|
||||
return it->second.session;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role)
|
||||
{
|
||||
auto it = m_PeerTests.find (nonce);
|
||||
if (it != m_PeerTests.end ())
|
||||
it->second.role = role;
|
||||
}
|
||||
|
||||
void SSUServer::RemovePeerTest (uint32_t nonce)
|
||||
{
|
||||
m_PeerTests.erase (nonce);
|
||||
}
|
||||
|
||||
void SSUServer::SchedulePeerTestsCleanupTimer ()
|
||||
{
|
||||
m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT));
|
||||
m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer,
|
||||
this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
int numDeleted = 0;
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
|
||||
{
|
||||
if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL)
|
||||
{
|
||||
numDeleted++;
|
||||
it = m_PeerTests.erase (it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
if (numDeleted > 0)
|
||||
LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired");
|
||||
SchedulePeerTestsCleanupTimer ();
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::ScheduleTermination ()
|
||||
{
|
||||
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT));
|
||||
m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer,
|
||||
this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto& it: m_Sessions)
|
||||
if (it.second->IsTerminationTimeoutExpired (ts))
|
||||
{
|
||||
auto session = it.second;
|
||||
m_Service.post ([session]
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds");
|
||||
session->Failed ();
|
||||
});
|
||||
}
|
||||
ScheduleTermination ();
|
||||
}
|
||||
}
|
||||
|
||||
void SSUServer::ScheduleTerminationV6 ()
|
||||
{
|
||||
m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT));
|
||||
m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6,
|
||||
this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto& it: m_SessionsV6)
|
||||
if (it.second->IsTerminationTimeoutExpired (ts))
|
||||
{
|
||||
auto session = it.second;
|
||||
m_ServiceV6.post ([session]
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds");
|
||||
session->Failed ();
|
||||
});
|
||||
}
|
||||
ScheduleTerminationV6 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
138
libi2pd/SSU.h
Normal file
138
libi2pd/SSU.h
Normal file
|
@ -0,0 +1,138 @@
|
|||
#ifndef SSU_H__
|
||||
#define SSU_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Crypto.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "SSUSession.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds
|
||||
const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds
|
||||
const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
|
||||
const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
|
||||
const size_t SSU_MAX_NUM_INTRODUCERS = 3;
|
||||
const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K
|
||||
const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K
|
||||
|
||||
struct SSUPacket
|
||||
{
|
||||
i2p::crypto::AESAlignedBuffer<SSU_MTU_V6 + 18> buf; // max MTU + iv + size
|
||||
boost::asio::ip::udp::endpoint from;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
class SSUServer
|
||||
{
|
||||
public:
|
||||
|
||||
SSUServer (int port);
|
||||
SSUServer (const boost::asio::ip::address & addr, int port); // ipv6 only constructor
|
||||
~SSUServer ();
|
||||
void Start ();
|
||||
void Stop ();
|
||||
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false, bool v4only = false);
|
||||
void CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
|
||||
const boost::asio::ip::address& addr, int port, bool peerTest = false);
|
||||
void CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest);
|
||||
std::shared_ptr<SSUSession> FindSession (std::shared_ptr<const i2p::data::RouterInfo> router) const;
|
||||
std::shared_ptr<SSUSession> FindSession (const boost::asio::ip::udp::endpoint& e) const;
|
||||
std::shared_ptr<SSUSession> GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded);
|
||||
std::shared_ptr<SSUSession> GetRandomEstablishedV6Session (std::shared_ptr<const SSUSession> excluded);
|
||||
void DeleteSession (std::shared_ptr<SSUSession> session);
|
||||
void DeleteAllSessions ();
|
||||
|
||||
boost::asio::io_service& GetService () { return m_Service; };
|
||||
boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; };
|
||||
const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; };
|
||||
void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to);
|
||||
void AddRelay (uint32_t tag, std::shared_ptr<SSUSession> relay);
|
||||
void RemoveRelay (uint32_t tag);
|
||||
std::shared_ptr<SSUSession> FindRelaySession (uint32_t tag);
|
||||
|
||||
void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session = nullptr);
|
||||
PeerTestParticipant GetPeerTestParticipant (uint32_t nonce);
|
||||
std::shared_ptr<SSUSession> GetPeerTestSession (uint32_t nonce);
|
||||
void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role);
|
||||
void RemovePeerTest (uint32_t nonce);
|
||||
|
||||
private:
|
||||
|
||||
void OpenSocket ();
|
||||
void OpenSocketV6 ();
|
||||
void Run ();
|
||||
void RunV6 ();
|
||||
void RunReceivers ();
|
||||
void RunReceiversV6 ();
|
||||
void Receive ();
|
||||
void ReceiveV6 ();
|
||||
void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
|
||||
void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
|
||||
void HandleReceivedPackets (std::vector<SSUPacket *> packets,
|
||||
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> >* sessions);
|
||||
|
||||
void CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false);
|
||||
template<typename Filter>
|
||||
std::shared_ptr<SSUSession> GetRandomV4Session (Filter filter);
|
||||
template<typename Filter>
|
||||
std::shared_ptr<SSUSession> GetRandomV6Session (Filter filter);
|
||||
|
||||
std::set<SSUSession *> FindIntroducers (int maxNumIntroducers);
|
||||
void ScheduleIntroducersUpdateTimer ();
|
||||
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode);
|
||||
|
||||
void SchedulePeerTestsCleanupTimer ();
|
||||
void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode);
|
||||
|
||||
// timer
|
||||
void ScheduleTermination ();
|
||||
void HandleTerminationTimer (const boost::system::error_code& ecode);
|
||||
void ScheduleTerminationV6 ();
|
||||
void HandleTerminationTimerV6 (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
struct PeerTest
|
||||
{
|
||||
uint64_t creationTime;
|
||||
PeerTestParticipant role;
|
||||
std::shared_ptr<SSUSession> session; // for Bob to Alice
|
||||
};
|
||||
|
||||
bool m_OnlyV6;
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread, * m_ReceiversThreadV6;
|
||||
boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService, m_ReceiversServiceV6;
|
||||
boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork, m_ReceiversWorkV6;
|
||||
boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6;
|
||||
boost::asio::ip::udp::socket m_Socket, m_SocketV6;
|
||||
boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer,
|
||||
m_TerminationTimer, m_TerminationTimerV6;
|
||||
std::list<boost::asio::ip::udp::endpoint> m_Introducers; // introducers we are connected to
|
||||
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > m_Sessions, m_SessionsV6;
|
||||
std::map<uint32_t, std::shared_ptr<SSUSession> > m_Relays; // we are introducer
|
||||
std::map<uint32_t, PeerTest> m_PeerTests; // nonce -> creation time in milliseconds
|
||||
|
||||
public:
|
||||
// for HTTP only
|
||||
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
|
||||
const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
513
libi2pd/SSUData.cpp
Normal file
513
libi2pd/SSUData.cpp
Normal file
|
@ -0,0 +1,513 @@
|
|||
#include <stdlib.h>
|
||||
#include <boost/bind.hpp>
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "NetDb.h"
|
||||
#include "SSU.h"
|
||||
#include "SSUData.h"
|
||||
#ifdef WITH_EVENTS
|
||||
#include "Event.h"
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize)
|
||||
{
|
||||
if (msg->len + fragmentSize > msg->maxLen)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough");
|
||||
auto newMsg = NewI2NPMessage ();
|
||||
*newMsg = *msg;
|
||||
msg = newMsg;
|
||||
}
|
||||
if (msg->Concat (fragment, fragmentSize) < fragmentSize)
|
||||
LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen);
|
||||
nextFragmentNum++;
|
||||
}
|
||||
|
||||
SSUData::SSUData (SSUSession& session):
|
||||
m_Session (session), m_ResendTimer (session.GetService ()),
|
||||
m_IncompleteMessagesCleanupTimer (session.GetService ()),
|
||||
m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE),
|
||||
m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0)
|
||||
{
|
||||
}
|
||||
|
||||
SSUData::~SSUData ()
|
||||
{
|
||||
}
|
||||
|
||||
void SSUData::Start ()
|
||||
{
|
||||
ScheduleIncompleteMessagesCleanup ();
|
||||
}
|
||||
|
||||
void SSUData::Stop ()
|
||||
{
|
||||
m_ResendTimer.cancel ();
|
||||
m_IncompleteMessagesCleanupTimer.cancel ();
|
||||
m_IncompleteMessages.clear ();
|
||||
m_SentMessages.clear ();
|
||||
m_ReceivedMessages.clear ();
|
||||
}
|
||||
|
||||
void SSUData::AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter)
|
||||
{
|
||||
if (!remoteRouter) return;
|
||||
auto ssuAddress = remoteRouter->GetSSUAddress ();
|
||||
if (ssuAddress && ssuAddress->ssu->mtu)
|
||||
{
|
||||
if (m_Session.IsV6 ())
|
||||
m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE;
|
||||
else
|
||||
m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE;
|
||||
if (m_PacketSize > 0)
|
||||
{
|
||||
// make sure packet size multiple of 16
|
||||
m_PacketSize >>= 4;
|
||||
m_PacketSize <<= 4;
|
||||
if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize;
|
||||
LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu);
|
||||
m_PacketSize = m_MaxPacketSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent)
|
||||
{
|
||||
auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent);
|
||||
if (routerInfo)
|
||||
AdjustPacketSize (routerInfo);
|
||||
}
|
||||
|
||||
void SSUData::ProcessSentMessageAck (uint32_t msgID)
|
||||
{
|
||||
auto it = m_SentMessages.find (msgID);
|
||||
if (it != m_SentMessages.end ())
|
||||
{
|
||||
m_SentMessages.erase (it);
|
||||
if (m_SentMessages.empty ())
|
||||
m_ResendTimer.cancel ();
|
||||
}
|
||||
}
|
||||
|
||||
void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag)
|
||||
{
|
||||
if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED)
|
||||
{
|
||||
// explicit ACKs
|
||||
uint8_t numAcks =*buf;
|
||||
buf++;
|
||||
for (int i = 0; i < numAcks; i++)
|
||||
ProcessSentMessageAck (bufbe32toh (buf+i*4));
|
||||
buf += numAcks*4;
|
||||
}
|
||||
if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED)
|
||||
{
|
||||
// explicit ACK bitfields
|
||||
uint8_t numBitfields =*buf;
|
||||
buf++;
|
||||
for (int i = 0; i < numBitfields; i++)
|
||||
{
|
||||
uint32_t msgID = bufbe32toh (buf);
|
||||
buf += 4; // msgID
|
||||
auto it = m_SentMessages.find (msgID);
|
||||
// process individual Ack bitfields
|
||||
bool isNonLast = false;
|
||||
int fragment = 0;
|
||||
do
|
||||
{
|
||||
uint8_t bitfield = *buf;
|
||||
isNonLast = bitfield & 0x80;
|
||||
bitfield &= 0x7F; // clear MSB
|
||||
if (bitfield && it != m_SentMessages.end ())
|
||||
{
|
||||
int numSentFragments = it->second->fragments.size ();
|
||||
// process bits
|
||||
uint8_t mask = 0x01;
|
||||
for (int j = 0; j < 7; j++)
|
||||
{
|
||||
if (bitfield & mask)
|
||||
{
|
||||
if (fragment < numSentFragments)
|
||||
it->second->fragments[fragment].reset (nullptr);
|
||||
}
|
||||
fragment++;
|
||||
mask <<= 1;
|
||||
}
|
||||
}
|
||||
buf++;
|
||||
}
|
||||
while (isNonLast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUData::ProcessFragments (uint8_t * buf)
|
||||
{
|
||||
uint8_t numFragments = *buf; // number of fragments
|
||||
buf++;
|
||||
for (int i = 0; i < numFragments; i++)
|
||||
{
|
||||
uint32_t msgID = bufbe32toh (buf); // message ID
|
||||
buf += 4;
|
||||
uint8_t frag[4] = {0};
|
||||
memcpy (frag + 1, buf, 3);
|
||||
buf += 3;
|
||||
uint32_t fragmentInfo = bufbe32toh (frag); // fragment info
|
||||
uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13
|
||||
bool isLast = fragmentInfo & 0x010000; // bit 16
|
||||
uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17
|
||||
if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE)
|
||||
{
|
||||
LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size");
|
||||
return;
|
||||
}
|
||||
|
||||
// find message with msgID
|
||||
auto it = m_IncompleteMessages.find (msgID);
|
||||
if (it == m_IncompleteMessages.end ())
|
||||
{
|
||||
// create new message
|
||||
auto msg = NewI2NPShortMessage ();
|
||||
msg->len -= I2NP_SHORT_HEADER_SIZE;
|
||||
it = m_IncompleteMessages.insert (std::make_pair (msgID,
|
||||
std::unique_ptr<IncompleteMessage>(new IncompleteMessage (msg)))).first;
|
||||
}
|
||||
std::unique_ptr<IncompleteMessage>& incompleteMessage = it->second;
|
||||
|
||||
// handle current fragment
|
||||
if (fragmentNum == incompleteMessage->nextFragmentNum)
|
||||
{
|
||||
// expected fragment
|
||||
incompleteMessage->AttachNextFragment (buf, fragmentSize);
|
||||
if (!isLast && !incompleteMessage->savedFragments.empty ())
|
||||
{
|
||||
// try saved fragments
|
||||
for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();)
|
||||
{
|
||||
auto& savedFragment = *it1;
|
||||
if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum)
|
||||
{
|
||||
incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len);
|
||||
isLast = savedFragment->isLast;
|
||||
incompleteMessage->savedFragments.erase (it1++);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (isLast)
|
||||
LogPrint (eLogDebug, "SSU: Message ", msgID, " complete");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fragmentNum < incompleteMessage->nextFragmentNum)
|
||||
// duplicate fragment
|
||||
LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored");
|
||||
else
|
||||
{
|
||||
// missing fragment
|
||||
LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID);
|
||||
auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast);
|
||||
if (incompleteMessage->savedFragments.insert (std::unique_ptr<Fragment>(savedFragment)).second)
|
||||
incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
else
|
||||
LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved");
|
||||
}
|
||||
isLast = false;
|
||||
}
|
||||
|
||||
if (isLast)
|
||||
{
|
||||
// delete incomplete message
|
||||
auto msg = incompleteMessage->msg;
|
||||
incompleteMessage->msg = nullptr;
|
||||
m_IncompleteMessages.erase (msgID);
|
||||
// process message
|
||||
SendMsgAck (msgID);
|
||||
msg->FromSSU (msgID);
|
||||
if (m_Session.GetState () == eSessionStateEstablished)
|
||||
{
|
||||
if (!m_ReceivedMessages.count (msgID))
|
||||
{
|
||||
m_ReceivedMessages.insert (msgID);
|
||||
m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (!msg->IsExpired ())
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
QueueIntEvent("transport.recvmsg", m_Session.GetIdentHashBase64(), 1);
|
||||
#endif
|
||||
m_Handler.PutNextMessage (msg);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogDebug, "SSU: message expired");
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "SSU: Message ", msgID, " already received");
|
||||
}
|
||||
else
|
||||
{
|
||||
// we expect DeliveryStatus
|
||||
if (msg->GetTypeID () == eI2NPDeliveryStatus)
|
||||
{
|
||||
LogPrint (eLogDebug, "SSU: session established");
|
||||
m_Session.Established ();
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ());
|
||||
}
|
||||
}
|
||||
else
|
||||
SendFragmentAck (msgID, fragmentNum);
|
||||
buf += fragmentSize;
|
||||
}
|
||||
}
|
||||
|
||||
void SSUData::FlushReceivedMessage ()
|
||||
{
|
||||
m_Handler.Flush ();
|
||||
}
|
||||
|
||||
void SSUData::ProcessMessage (uint8_t * buf, size_t len)
|
||||
{
|
||||
//uint8_t * start = buf;
|
||||
uint8_t flag = *buf;
|
||||
buf++;
|
||||
LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len);
|
||||
// process acks if presented
|
||||
if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED))
|
||||
ProcessAcks (buf, flag);
|
||||
// extended data if presented
|
||||
if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED)
|
||||
{
|
||||
uint8_t extendedDataSize = *buf;
|
||||
buf++; // size
|
||||
LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present");
|
||||
buf += extendedDataSize;
|
||||
}
|
||||
// process data
|
||||
ProcessFragments (buf);
|
||||
}
|
||||
|
||||
void SSUData::Send (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||
{
|
||||
uint32_t msgID = msg->ToSSU ();
|
||||
if (m_SentMessages.count (msgID) > 0)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: message ", msgID, " already sent");
|
||||
return;
|
||||
}
|
||||
if (m_SentMessages.empty ()) // schedule resend at first message only
|
||||
ScheduleResend ();
|
||||
|
||||
auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr<SentMessage>(new SentMessage)));
|
||||
std::unique_ptr<SentMessage>& sentMessage = ret.first->second;
|
||||
if (ret.second)
|
||||
{
|
||||
sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL;
|
||||
sentMessage->numResends = 0;
|
||||
}
|
||||
auto& fragments = sentMessage->fragments;
|
||||
size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3)
|
||||
size_t len = msg->GetLength ();
|
||||
uint8_t * msgBuf = msg->GetSSUHeader ();
|
||||
|
||||
uint32_t fragmentNum = 0;
|
||||
while (len > 0)
|
||||
{
|
||||
Fragment * fragment = new Fragment;
|
||||
fragment->fragmentNum = fragmentNum;
|
||||
uint8_t * buf = fragment->buf;
|
||||
uint8_t * payload = buf + sizeof (SSUHeader);
|
||||
*payload = DATA_FLAG_WANT_REPLY; // for compatibility
|
||||
payload++;
|
||||
*payload = 1; // always 1 message fragment per message
|
||||
payload++;
|
||||
htobe32buf (payload, msgID);
|
||||
payload += 4;
|
||||
bool isLast = (len <= payloadSize);
|
||||
size_t size = isLast ? len : payloadSize;
|
||||
uint32_t fragmentInfo = (fragmentNum << 17);
|
||||
if (isLast)
|
||||
fragmentInfo |= 0x010000;
|
||||
|
||||
fragmentInfo |= size;
|
||||
fragmentInfo = htobe32 (fragmentInfo);
|
||||
memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3);
|
||||
payload += 3;
|
||||
memcpy (payload, msgBuf, size);
|
||||
|
||||
size += payload - buf;
|
||||
if (size & 0x0F) // make sure 16 bytes boundary
|
||||
size = ((size >> 4) + 1) << 4; // (/16 + 1)*16
|
||||
fragment->len = size;
|
||||
fragments.push_back (std::unique_ptr<Fragment> (fragment));
|
||||
|
||||
// encrypt message with session key
|
||||
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size);
|
||||
try
|
||||
{
|
||||
m_Session.Send (buf, size);
|
||||
}
|
||||
catch (boost::system::system_error& ec)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ());
|
||||
}
|
||||
if (!isLast)
|
||||
{
|
||||
len -= payloadSize;
|
||||
msgBuf += payloadSize;
|
||||
}
|
||||
else
|
||||
len = 0;
|
||||
fragmentNum++;
|
||||
}
|
||||
}
|
||||
|
||||
void SSUData::SendMsgAck (uint32_t msgID)
|
||||
{
|
||||
uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16
|
||||
uint8_t * payload = buf + sizeof (SSUHeader);
|
||||
*payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag
|
||||
payload++;
|
||||
*payload = 1; // number of ACKs
|
||||
payload++;
|
||||
htobe32buf (payload, msgID); // msgID
|
||||
payload += 4;
|
||||
*payload = 0; // number of fragments
|
||||
|
||||
// encrypt message with session key
|
||||
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48);
|
||||
m_Session.Send (buf, 48);
|
||||
}
|
||||
|
||||
void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum)
|
||||
{
|
||||
if (fragmentNum > 64)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64");
|
||||
return;
|
||||
}
|
||||
uint8_t buf[64 + 18] = {0};
|
||||
uint8_t * payload = buf + sizeof (SSUHeader);
|
||||
*payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag
|
||||
payload++;
|
||||
*payload = 1; // number of ACK bitfields
|
||||
payload++;
|
||||
// one ack
|
||||
*(uint32_t *)(payload) = htobe32 (msgID); // msgID
|
||||
payload += 4;
|
||||
div_t d = div (fragmentNum, 7);
|
||||
memset (payload, 0x80, d.quot); // 0x80 means non-last
|
||||
payload += d.quot;
|
||||
*payload = 0x01 << d.rem; // set corresponding bit
|
||||
payload++;
|
||||
*payload = 0; // number of fragments
|
||||
|
||||
size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1)
|
||||
// encrypt message with session key
|
||||
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len);
|
||||
m_Session.Send (buf, len);
|
||||
}
|
||||
|
||||
void SSUData::ScheduleResend()
|
||||
{
|
||||
m_ResendTimer.cancel ();
|
||||
m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL));
|
||||
auto s = m_Session.shared_from_this();
|
||||
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
|
||||
{ s->m_Data.HandleResendTimer (ecode); });
|
||||
}
|
||||
|
||||
void SSUData::HandleResendTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
int numResent = 0;
|
||||
for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();)
|
||||
{
|
||||
if (ts >= it->second->nextResendTime)
|
||||
{
|
||||
if (it->second->numResends < MAX_NUM_RESENDS)
|
||||
{
|
||||
for (auto& f: it->second->fragments)
|
||||
if (f)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Session.Send (f->buf, f->len); // resend
|
||||
numResent++;
|
||||
}
|
||||
catch (boost::system::system_error& ec)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ());
|
||||
}
|
||||
}
|
||||
|
||||
it->second->numResends++;
|
||||
it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL;
|
||||
++it;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogInfo, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted");
|
||||
it = m_SentMessages.erase (it);
|
||||
}
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
if (m_SentMessages.empty ()) return; // nothing to resend
|
||||
if (numResent < MAX_OUTGOING_WINDOW_SIZE)
|
||||
ScheduleResend ();
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated");
|
||||
m_Session.Close ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSUData::ScheduleIncompleteMessagesCleanup ()
|
||||
{
|
||||
m_IncompleteMessagesCleanupTimer.cancel ();
|
||||
m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT));
|
||||
auto s = m_Session.shared_from_this();
|
||||
m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode)
|
||||
{ s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); });
|
||||
}
|
||||
|
||||
void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();)
|
||||
{
|
||||
if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)
|
||||
{
|
||||
LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted");
|
||||
it = m_IncompleteMessages.erase (it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
// decay
|
||||
if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES ||
|
||||
i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL)
|
||||
m_ReceivedMessages.clear ();
|
||||
|
||||
ScheduleIncompleteMessagesCleanup ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
131
libi2pd/SSUData.h
Normal file
131
libi2pd/SSUData.h
Normal file
|
@ -0,0 +1,131 @@
|
|||
#ifndef SSU_DATA_H__
|
||||
#define SSU_DATA_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Identity.h"
|
||||
#include "RouterInfo.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
|
||||
const size_t SSU_MTU_V4 = 1484;
|
||||
#ifdef MESHNET
|
||||
const size_t SSU_MTU_V6 = 1286;
|
||||
#else
|
||||
const size_t SSU_MTU_V6 = 1488;
|
||||
#endif
|
||||
const size_t IPV4_HEADER_SIZE = 20;
|
||||
const size_t IPV6_HEADER_SIZE = 40;
|
||||
const size_t UDP_HEADER_SIZE = 8;
|
||||
const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456
|
||||
const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440
|
||||
const int RESEND_INTERVAL = 3; // in seconds
|
||||
const int MAX_NUM_RESENDS = 5;
|
||||
const int DECAY_INTERVAL = 20; // in seconds
|
||||
const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
|
||||
const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check
|
||||
const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store
|
||||
// data flags
|
||||
const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02;
|
||||
const uint8_t DATA_FLAG_WANT_REPLY = 0x04;
|
||||
const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08;
|
||||
const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10;
|
||||
const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40;
|
||||
const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80;
|
||||
|
||||
struct Fragment
|
||||
{
|
||||
int fragmentNum;
|
||||
size_t len;
|
||||
bool isLast;
|
||||
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest
|
||||
|
||||
Fragment () = default;
|
||||
Fragment (int n, const uint8_t * b, int l, bool last):
|
||||
fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); };
|
||||
};
|
||||
|
||||
struct FragmentCmp
|
||||
{
|
||||
bool operator() (const std::unique_ptr<Fragment>& f1, const std::unique_ptr<Fragment>& f2) const
|
||||
{
|
||||
return f1->fragmentNum < f2->fragmentNum;
|
||||
};
|
||||
};
|
||||
|
||||
struct IncompleteMessage
|
||||
{
|
||||
std::shared_ptr<I2NPMessage> msg;
|
||||
int nextFragmentNum;
|
||||
uint32_t lastFragmentInsertTime; // in seconds
|
||||
std::set<std::unique_ptr<Fragment>, FragmentCmp> savedFragments;
|
||||
|
||||
IncompleteMessage (std::shared_ptr<I2NPMessage> m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {};
|
||||
void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize);
|
||||
};
|
||||
|
||||
struct SentMessage
|
||||
{
|
||||
std::vector<std::unique_ptr<Fragment> > fragments;
|
||||
uint32_t nextResendTime; // in seconds
|
||||
int numResends;
|
||||
};
|
||||
|
||||
class SSUSession;
|
||||
class SSUData
|
||||
{
|
||||
public:
|
||||
|
||||
SSUData (SSUSession& session);
|
||||
~SSUData ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
void ProcessMessage (uint8_t * buf, size_t len);
|
||||
void FlushReceivedMessage ();
|
||||
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
|
||||
void AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter);
|
||||
void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent);
|
||||
|
||||
private:
|
||||
|
||||
void SendMsgAck (uint32_t msgID);
|
||||
void SendFragmentAck (uint32_t msgID, int fragmentNum);
|
||||
void ProcessAcks (uint8_t *& buf, uint8_t flag);
|
||||
void ProcessFragments (uint8_t * buf);
|
||||
void ProcessSentMessageAck (uint32_t msgID);
|
||||
|
||||
void ScheduleResend ();
|
||||
void HandleResendTimer (const boost::system::error_code& ecode);
|
||||
|
||||
void ScheduleIncompleteMessagesCleanup ();
|
||||
void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
SSUSession& m_Session;
|
||||
std::map<uint32_t, std::unique_ptr<IncompleteMessage> > m_IncompleteMessages;
|
||||
std::map<uint32_t, std::unique_ptr<SentMessage> > m_SentMessages;
|
||||
std::unordered_set<uint32_t> m_ReceivedMessages;
|
||||
boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer;
|
||||
int m_MaxPacketSize, m_PacketSize;
|
||||
i2p::I2NPMessagesHandler m_Handler;
|
||||
uint32_t m_LastMessageReceivedTime; // in second
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
1200
libi2pd/SSUSession.cpp
Normal file
1200
libi2pd/SSUSession.cpp
Normal file
File diff suppressed because it is too large
Load diff
165
libi2pd/SSUSession.h
Normal file
165
libi2pd/SSUSession.h
Normal file
|
@ -0,0 +1,165 @@
|
|||
#ifndef SSU_SESSION_H__
|
||||
#define SSU_SESSION_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include "Crypto.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TransportSession.h"
|
||||
#include "SSUData.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04;
|
||||
struct SSUHeader
|
||||
{
|
||||
uint8_t mac[16];
|
||||
uint8_t iv[16];
|
||||
uint8_t flag;
|
||||
uint8_t time[4];
|
||||
|
||||
uint8_t GetPayloadType () const { return flag >> 4; };
|
||||
bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; };
|
||||
};
|
||||
|
||||
const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds
|
||||
const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes
|
||||
const int SSU_CLOCK_SKEW = 60; // in seconds
|
||||
|
||||
// payload types (4 bits)
|
||||
const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0;
|
||||
const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1;
|
||||
const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2;
|
||||
const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3;
|
||||
const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4;
|
||||
const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5;
|
||||
const uint8_t PAYLOAD_TYPE_DATA = 6;
|
||||
const uint8_t PAYLOAD_TYPE_PEER_TEST = 7;
|
||||
const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8;
|
||||
|
||||
// extended options
|
||||
const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001;
|
||||
|
||||
enum SessionState
|
||||
{
|
||||
eSessionStateUnknown,
|
||||
eSessionStateIntroduced,
|
||||
eSessionStateEstablished,
|
||||
eSessionStateClosed,
|
||||
eSessionStateFailed
|
||||
};
|
||||
|
||||
enum PeerTestParticipant
|
||||
{
|
||||
ePeerTestParticipantUnknown = 0,
|
||||
ePeerTestParticipantAlice1,
|
||||
ePeerTestParticipantAlice2,
|
||||
ePeerTestParticipantBob,
|
||||
ePeerTestParticipantCharlie
|
||||
};
|
||||
|
||||
class SSUServer;
|
||||
class SSUSession: public TransportSession, public std::enable_shared_from_this<SSUSession>
|
||||
{
|
||||
public:
|
||||
|
||||
SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint,
|
||||
std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, bool peerTest = false);
|
||||
void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
|
||||
~SSUSession ();
|
||||
|
||||
void Connect ();
|
||||
void WaitForConnect ();
|
||||
void Introduce (const i2p::data::RouterInfo::Introducer& introducer,
|
||||
std::shared_ptr<const i2p::data::RouterInfo> to); // Alice to Charlie
|
||||
void WaitForIntroduction ();
|
||||
void Close ();
|
||||
void Done ();
|
||||
void Failed ();
|
||||
boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
|
||||
bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); };
|
||||
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||
void SendPeerTest (); // Alice
|
||||
|
||||
SessionState GetState () const { return m_State; };
|
||||
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||
|
||||
void SendKeepAlive ();
|
||||
uint32_t GetRelayTag () const { return m_RelayTag; };
|
||||
const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; };
|
||||
uint32_t GetCreationTime () const { return m_CreationTime; };
|
||||
|
||||
void FlushData ();
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::io_service& GetService ();
|
||||
void CreateAESandMacKey (const uint8_t * pubKey);
|
||||
size_t GetSSUHeaderSize (const uint8_t * buf) const;
|
||||
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
|
||||
void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session
|
||||
void ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
|
||||
void SendSessionRequest ();
|
||||
void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce);
|
||||
void ProcessSessionCreated (uint8_t * buf, size_t len);
|
||||
void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true);
|
||||
void ProcessSessionConfirmed (const uint8_t * buf, size_t len);
|
||||
void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen);
|
||||
void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);
|
||||
void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from,
|
||||
const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to);
|
||||
void SendRelayIntro (std::shared_ptr<SSUSession> session, const boost::asio::ip::udp::endpoint& from);
|
||||
void ProcessRelayResponse (const uint8_t * buf, size_t len);
|
||||
void ProcessRelayIntro (const uint8_t * buf, size_t len);
|
||||
void Established ();
|
||||
void ScheduleConnectTimer ();
|
||||
void HandleConnectTimer (const boost::system::error_code& ecode);
|
||||
void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
|
||||
void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true);
|
||||
void ProcessData (uint8_t * buf, size_t len);
|
||||
void SendSessionDestroyed ();
|
||||
void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key
|
||||
void Send (const uint8_t * buf, size_t size);
|
||||
|
||||
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey,
|
||||
const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0);
|
||||
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key
|
||||
void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey);
|
||||
void DecryptSessionKey (uint8_t * buf, size_t len);
|
||||
bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey);
|
||||
|
||||
void Reset ();
|
||||
|
||||
private:
|
||||
|
||||
friend class SSUData; // TODO: change in later
|
||||
SSUServer& m_Server;
|
||||
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
|
||||
boost::asio::deadline_timer m_ConnectTimer;
|
||||
bool m_IsPeerTest;
|
||||
SessionState m_State;
|
||||
bool m_IsSessionKey;
|
||||
uint32_t m_RelayTag; // received from peer
|
||||
uint32_t m_SentRelayTag; // sent by us
|
||||
i2p::crypto::CBCEncryption m_SessionKeyEncryption;
|
||||
i2p::crypto::CBCDecryption m_SessionKeyDecryption;
|
||||
i2p::crypto::AESKey m_SessionKey;
|
||||
i2p::crypto::MACKey m_MacKey;
|
||||
i2p::data::RouterInfo::IntroKey m_IntroKey;
|
||||
uint32_t m_CreationTime; // seconds since epoch
|
||||
SSUData m_Data;
|
||||
bool m_IsDataReceived;
|
||||
std::unique_ptr<SignedData> m_SignedData; // we need it for SessionConfirmed only
|
||||
std::map<uint32_t, std::shared_ptr<const i2p::data::RouterInfo> > m_RelayRequests; // nonce->Charlie
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
496
libi2pd/Signature.cpp
Normal file
496
libi2pd/Signature.cpp
Normal file
|
@ -0,0 +1,496 @@
|
|||
#include <memory>
|
||||
#include "Log.h"
|
||||
#include "Signature.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
class Ed25519
|
||||
{
|
||||
public:
|
||||
|
||||
Ed25519 ()
|
||||
{
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
BIGNUM * tmp = BN_new ();
|
||||
|
||||
q = BN_new ();
|
||||
// 2^255-19
|
||||
BN_set_bit (q, 255); // 2^255
|
||||
BN_sub_word (q, 19);
|
||||
|
||||
l = BN_new ();
|
||||
// 2^252 + 27742317777372353535851937790883648493
|
||||
BN_set_bit (l, 252);
|
||||
two_252_2 = BN_dup (l);
|
||||
BN_dec2bn (&tmp, "27742317777372353535851937790883648493");
|
||||
BN_add (l, l, tmp);
|
||||
BN_sub_word (two_252_2, 2); // 2^252 - 2
|
||||
|
||||
// -121665*inv(121666)
|
||||
d = BN_new ();
|
||||
BN_set_word (tmp, 121666);
|
||||
BN_mod_inverse (tmp, tmp, q, ctx);
|
||||
BN_set_word (d, 121665);
|
||||
BN_set_negative (d, 1);
|
||||
BN_mul (d, d, tmp, ctx);
|
||||
|
||||
// 2^((q-1)/4)
|
||||
I = BN_new ();
|
||||
BN_free (tmp);
|
||||
tmp = BN_dup (q);
|
||||
BN_sub_word (tmp, 1);
|
||||
BN_div_word (tmp, 4);
|
||||
BN_set_word (I, 2);
|
||||
BN_mod_exp (I, I, tmp, q, ctx);
|
||||
BN_free (tmp);
|
||||
|
||||
// 4*inv(5)
|
||||
BIGNUM * By = BN_new ();
|
||||
BN_set_word (By, 5);
|
||||
BN_mod_inverse (By, By, q, ctx);
|
||||
BN_mul_word (By, 4);
|
||||
BIGNUM * Bx = RecoverX (By, ctx);
|
||||
BN_mod (Bx, Bx, q, ctx); // % q
|
||||
BN_mod (By, By, q, ctx); // % q
|
||||
|
||||
// precalculate Bi256 table
|
||||
Bi256Carry = { Bx, By }; // B
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
Bi256[i][0] = Bi256Carry; // first point
|
||||
for (int j = 1; j < 128; j++)
|
||||
Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B
|
||||
Bi256Carry = Bi256[i][127];
|
||||
for (int j = 0; j < 128; j++) // add first point 128 more times
|
||||
Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx);
|
||||
}
|
||||
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
|
||||
Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)),
|
||||
d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)),
|
||||
Bi256Carry (other.Bi256Carry)
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
for (int j = 0; j < 128; j++)
|
||||
Bi256[i][j] = other.Bi256[i][j];
|
||||
}
|
||||
|
||||
~Ed25519 ()
|
||||
{
|
||||
BN_free (q);
|
||||
BN_free (l);
|
||||
BN_free (d);
|
||||
BN_free (I);
|
||||
BN_free (two_252_2);
|
||||
}
|
||||
|
||||
|
||||
EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const
|
||||
{
|
||||
return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian
|
||||
}
|
||||
|
||||
EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const
|
||||
{
|
||||
return DecodePoint (buf, ctx);
|
||||
}
|
||||
|
||||
void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const
|
||||
{
|
||||
EncodePoint (Normalize (publicKey, ctx), buf);
|
||||
}
|
||||
|
||||
bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const
|
||||
{
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
BIGNUM * h = DecodeBN<64> (digest);
|
||||
// signature 0..31 - R, 32..63 - S
|
||||
// B*S = R + PK*h => R = B*S - PK*h
|
||||
// we don't decode R, but encode (B*S - PK*h)
|
||||
auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S;
|
||||
BN_mod (h, h, l, ctx); // public key is multiple of B, but B%l = 0
|
||||
auto PKh = Mul (publicKey, h, ctx); // PK*h
|
||||
uint8_t diff[32];
|
||||
EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded
|
||||
bool passed = !memcmp (signature, diff, 32); // R
|
||||
BN_free (h);
|
||||
BN_CTX_free (ctx);
|
||||
if (!passed)
|
||||
LogPrint (eLogError, "25519 signature verification failed");
|
||||
return passed;
|
||||
}
|
||||
|
||||
void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len,
|
||||
uint8_t * signature) const
|
||||
{
|
||||
BN_CTX * bnCtx = BN_CTX_new ();
|
||||
// calculate r
|
||||
SHA512_CTX ctx;
|
||||
SHA512_Init (&ctx);
|
||||
SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key
|
||||
SHA512_Update (&ctx, buf, len); // data
|
||||
uint8_t digest[64];
|
||||
SHA512_Final (digest, &ctx);
|
||||
BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors
|
||||
// calculate R
|
||||
uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf
|
||||
EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors
|
||||
// calculate S
|
||||
SHA512_Init (&ctx);
|
||||
SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R
|
||||
SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
|
||||
SHA512_Update (&ctx, buf, len); // data
|
||||
SHA512_Final (digest, &ctx);
|
||||
BIGNUM * h = DecodeBN<64> (digest);
|
||||
// S = (r + h*a) % l
|
||||
BIGNUM * a = DecodeBN<EDDSA25519_PRIVATE_KEY_LENGTH> (expandedPrivateKey); // left half of expanded key
|
||||
BN_mod_mul (h, h, a, l, bnCtx); // %l
|
||||
BN_mod_add (h, h, r, l, bnCtx); // %l
|
||||
memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2);
|
||||
EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S
|
||||
BN_free (r); BN_free (h); BN_free (a);
|
||||
BN_CTX_free (bnCtx);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const
|
||||
{
|
||||
// x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2)
|
||||
// y3 = (y1*y2+x1*x2)*(z1*z2+d*t1*t2)
|
||||
// z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2)
|
||||
// t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2)
|
||||
BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new ();
|
||||
|
||||
BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2
|
||||
BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2
|
||||
|
||||
BN_CTX_start (ctx);
|
||||
BIGNUM * t1 = p1.t, * t2 = p2.t;
|
||||
if (!t1) { t1 = BN_CTX_get (ctx); BN_mul (t1, p1.x, p1.y, ctx); }
|
||||
if (!t2) { t2 = BN_CTX_get (ctx); BN_mul (t2, p2.x, p2.y, ctx); }
|
||||
BN_mul (t3, t1, t2, ctx);
|
||||
BN_mul (t3, t3, d, ctx); // C = d*t1*t2
|
||||
|
||||
if (p1.z)
|
||||
{
|
||||
if (p2.z)
|
||||
BN_mul (z3, p1.z, p2.z, ctx); // D = z1*z2
|
||||
else
|
||||
BN_copy (z3, p1.z); // D = z1
|
||||
}
|
||||
else
|
||||
{
|
||||
if (p2.z)
|
||||
BN_copy (z3, p2.z); // D = z2
|
||||
else
|
||||
BN_one (z3); // D = 1
|
||||
}
|
||||
|
||||
BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx);
|
||||
BN_add (E, p1.x, p1.y);
|
||||
BN_add (F, p2.x, p2.y);
|
||||
BN_mul (E, E, F, ctx); // (x1 + y1)*(x2 + y2)
|
||||
BN_sub (E, E, x3);
|
||||
BN_sub (E, E, y3); // E = (x1 + y1)*(x2 + y2) - A - B
|
||||
BN_sub (F, z3, t3); // F = D - C
|
||||
BN_add (G, z3, t3); // G = D + C
|
||||
BN_add (H, y3, x3); // H = B + A
|
||||
|
||||
BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F
|
||||
BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H
|
||||
BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G
|
||||
BN_mod_mul (t3, E, H, q, ctx); // t3 = E*H
|
||||
|
||||
BN_CTX_end (ctx);
|
||||
|
||||
return EDDSAPoint {x3, y3, z3, t3};
|
||||
}
|
||||
|
||||
void Double (EDDSAPoint& p, BN_CTX * ctx) const
|
||||
{
|
||||
BN_CTX_start (ctx);
|
||||
BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * z2 = BN_CTX_get (ctx), * t2 = BN_CTX_get (ctx);
|
||||
|
||||
BN_sqr (x2, p.x, ctx); // x2 = A = x^2
|
||||
BN_sqr (y2, p.y, ctx); // y2 = B = y^2
|
||||
if (p.t)
|
||||
BN_sqr (t2, p.t, ctx); // t2 = t^2
|
||||
else
|
||||
{
|
||||
BN_mul (t2, p.x, p.y, ctx); // t = x*y
|
||||
BN_sqr (t2, t2, ctx); // t2 = t^2
|
||||
}
|
||||
BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2
|
||||
if (p.z)
|
||||
BN_sqr (z2, p.z, ctx); // z2 = D = z^2
|
||||
else
|
||||
BN_one (z2); // z2 = 1
|
||||
|
||||
BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx);
|
||||
// E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy
|
||||
BN_mul (E, p.x, p.y, ctx);
|
||||
BN_lshift1 (E, E); // E =2*x*y
|
||||
BN_sub (F, z2, t2); // F = D - C
|
||||
BN_add (G, z2, t2); // G = D + C
|
||||
BN_add (H, y2, x2); // H = B + A
|
||||
|
||||
BN_mod_mul (p.x, E, F, q, ctx); // x2 = E*F
|
||||
BN_mod_mul (p.y, G, H, q, ctx); // y2 = G*H
|
||||
if (!p.z) p.z = BN_new ();
|
||||
BN_mod_mul (p.z, F, G, q, ctx); // z2 = F*G
|
||||
if (!p.t) p.t = BN_new ();
|
||||
BN_mod_mul (p.t, E, H, q, ctx); // t2 = E*H
|
||||
|
||||
BN_CTX_end (ctx);
|
||||
}
|
||||
|
||||
EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const
|
||||
{
|
||||
BIGNUM * zero = BN_new (), * one = BN_new ();
|
||||
BN_zero (zero); BN_one (one);
|
||||
EDDSAPoint res {zero, one};
|
||||
if (!BN_is_zero (e))
|
||||
{
|
||||
int bitCount = BN_num_bits (e);
|
||||
for (int i = bitCount - 1; i >= 0; i--)
|
||||
{
|
||||
Double (res, ctx);
|
||||
if (BN_is_bit_set (e, i)) res = Sum (res, p, ctx);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e, e is 32 bytes Little Endian
|
||||
{
|
||||
BIGNUM * zero = BN_new (), * one = BN_new ();
|
||||
BN_zero (zero); BN_one (one);
|
||||
EDDSAPoint res {zero, one};
|
||||
bool carry = false;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
uint8_t x = e[i];
|
||||
if (carry)
|
||||
{
|
||||
if (x < 255)
|
||||
{
|
||||
x++;
|
||||
carry = false;
|
||||
}
|
||||
else
|
||||
x = 0;
|
||||
}
|
||||
if (x > 0)
|
||||
{
|
||||
if (x <= 128)
|
||||
res = Sum (res, Bi256[i][x-1], ctx);
|
||||
else
|
||||
{
|
||||
res = Sum (res, -Bi256[i][255-x], ctx); // -Bi[256-x]
|
||||
carry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (carry) res = Sum (res, Bi256Carry, ctx);
|
||||
return res;
|
||||
}
|
||||
|
||||
EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const
|
||||
{
|
||||
if (p.z)
|
||||
{
|
||||
BIGNUM * x = BN_new (), * y = BN_new ();
|
||||
BN_mod_inverse (y, p.z, q, ctx);
|
||||
BN_mod_mul (x, p.x, y, q, ctx); // x = x/z
|
||||
BN_mod_mul (y, p.y, y, q, ctx); // y = y/z
|
||||
return EDDSAPoint{x, y};
|
||||
}
|
||||
else
|
||||
return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)};
|
||||
}
|
||||
|
||||
bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const
|
||||
{
|
||||
BN_CTX_start (ctx);
|
||||
BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * tmp = BN_CTX_get (ctx);
|
||||
BN_sqr (x2, p.x, ctx); // x^2
|
||||
BN_sqr (y2, p.y, ctx); // y^2
|
||||
// y^2 - x^2 - 1 - d*x^2*y^2
|
||||
BN_mul (tmp, d, x2, ctx);
|
||||
BN_mul (tmp, tmp, y2, ctx);
|
||||
BN_sub (tmp, y2, tmp);
|
||||
BN_sub (tmp, tmp, x2);
|
||||
BN_sub_word (tmp, 1);
|
||||
BN_mod (tmp, tmp, q, ctx); // % q
|
||||
bool ret = BN_is_zero (tmp);
|
||||
BN_CTX_end (ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const
|
||||
{
|
||||
BN_CTX_start (ctx);
|
||||
BIGNUM * y2 = BN_CTX_get (ctx), * xx = BN_CTX_get (ctx);
|
||||
BN_sqr (y2, y, ctx); // y^2
|
||||
// xx = (y^2 -1)*inv(d*y^2 +1)
|
||||
BN_mul (xx, d, y2, ctx);
|
||||
BN_add_word (xx, 1);
|
||||
BN_mod_inverse (xx, xx, q, ctx);
|
||||
BN_sub_word (y2, 1);
|
||||
BN_mul (xx, y2, xx, ctx);
|
||||
// x = srqt(xx) = xx^(2^252-2)
|
||||
BIGNUM * x = BN_new ();
|
||||
BN_mod_exp (x, xx, two_252_2, q, ctx);
|
||||
// check (x^2 -xx) % q
|
||||
BN_sqr (y2, x, ctx);
|
||||
BN_mod_sub (y2, y2, xx, q, ctx);
|
||||
if (!BN_is_zero (y2))
|
||||
BN_mod_mul (x, x, I, q, ctx);
|
||||
if (BN_is_odd (x))
|
||||
BN_sub (x, q, x);
|
||||
return x;
|
||||
}
|
||||
|
||||
EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const
|
||||
{
|
||||
// buf is 32 bytes Little Endian, convert it to Big Endian
|
||||
uint8_t buf1[EDDSA25519_PUBLIC_KEY_LENGTH];
|
||||
for (size_t i = 0; i < EDDSA25519_PUBLIC_KEY_LENGTH/2; i++) // invert bytes
|
||||
{
|
||||
buf1[i] = buf[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i];
|
||||
buf1[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i] = buf[i];
|
||||
}
|
||||
bool isHighestBitSet = buf1[0] & 0x80;
|
||||
if (isHighestBitSet)
|
||||
buf1[0] &= 0x7f; // clear highest bit
|
||||
BIGNUM * y = BN_new ();
|
||||
BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y);
|
||||
auto x = RecoverX (y, ctx);
|
||||
if (BN_is_bit_set (x, 0) != isHighestBitSet)
|
||||
BN_sub (x, q, x); // x = q - x
|
||||
BIGNUM * z = BN_new (), * t = BN_new ();
|
||||
BN_one (z); BN_mod_mul (t, x, y, q, ctx); // pre-calculate t
|
||||
EDDSAPoint p {x, y, z, t};
|
||||
if (!IsOnCurve (p, ctx))
|
||||
LogPrint (eLogError, "Decoded point is not on 25519");
|
||||
return p;
|
||||
}
|
||||
|
||||
void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const
|
||||
{
|
||||
EncodeBN (p.y, buf,EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||
if (BN_is_bit_set (p.x, 0)) // highest bit
|
||||
buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit
|
||||
}
|
||||
|
||||
template<int len>
|
||||
BIGNUM * DecodeBN (const uint8_t * buf) const
|
||||
{
|
||||
// buf is Little Endian convert it to Big Endian
|
||||
uint8_t buf1[len];
|
||||
for (size_t i = 0; i < len/2; i++) // invert bytes
|
||||
{
|
||||
buf1[i] = buf[len -1 - i];
|
||||
buf1[len -1 - i] = buf[i];
|
||||
}
|
||||
BIGNUM * res = BN_new ();
|
||||
BN_bin2bn (buf1, len, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const
|
||||
{
|
||||
bn2buf (bn, buf, len);
|
||||
// To Little Endian
|
||||
for (size_t i = 0; i < len/2; i++) // invert bytes
|
||||
{
|
||||
uint8_t tmp = buf[i];
|
||||
buf[i] = buf[len -1 - i];
|
||||
buf[len -1 - i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
BIGNUM * q, * l, * d, * I;
|
||||
// transient values
|
||||
BIGNUM * two_252_2; // 2^252-2
|
||||
EDDSAPoint Bi256[32][128]; // per byte, Bi256[i][j] = (256+j+1)^i*B, we don't store zeroes
|
||||
// if j > 128 we use 256 - j and carry 1 to next byte
|
||||
// Bi256[0][0] = B, base point
|
||||
EDDSAPoint Bi256Carry; // Bi256[32][0]
|
||||
};
|
||||
|
||||
static std::unique_ptr<Ed25519> g_Ed25519;
|
||||
std::unique_ptr<Ed25519>& GetEd25519 ()
|
||||
{
|
||||
if (!g_Ed25519)
|
||||
{
|
||||
auto c = new Ed25519();
|
||||
if (!g_Ed25519) // make sure it was not created already
|
||||
g_Ed25519.reset (c);
|
||||
else
|
||||
delete c;
|
||||
}
|
||||
return g_Ed25519;
|
||||
}
|
||||
|
||||
|
||||
EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey)
|
||||
{
|
||||
memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx);
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
|
||||
bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||
{
|
||||
uint8_t digest[64];
|
||||
SHA512_CTX ctx;
|
||||
SHA512_Init (&ctx);
|
||||
SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R
|
||||
SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key
|
||||
SHA512_Update (&ctx, buf, len); // data
|
||||
SHA512_Final (digest, &ctx);
|
||||
|
||||
return GetEd25519 ()->Verify (m_PublicKey, digest, signature);
|
||||
}
|
||||
|
||||
EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
|
||||
{
|
||||
// expand key
|
||||
SHA512 (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH, m_ExpandedPrivateKey);
|
||||
m_ExpandedPrivateKey[0] &= 0xF8; // drop last 3 bits
|
||||
m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x3F; // drop first 2 bits
|
||||
m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit
|
||||
|
||||
// generate and encode public key
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx);
|
||||
GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx);
|
||||
|
||||
if (signingPublicKey && memcmp (m_PublicKeyEncoded, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH))
|
||||
{
|
||||
// keys don't match, it means older key with 0x1F
|
||||
LogPrint (eLogWarning, "Older EdDSA key detected");
|
||||
m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0xDF; // drop third bit
|
||||
publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx);
|
||||
GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx);
|
||||
}
|
||||
BN_CTX_free (ctx);
|
||||
}
|
||||
|
||||
void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
567
libi2pd/Signature.h
Normal file
567
libi2pd/Signature.h
Normal file
|
@ -0,0 +1,567 @@
|
|||
#ifndef SIGNATURE_H__
|
||||
#define SIGNATURE_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <openssl/dsa.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include "Crypto.h"
|
||||
#include "Gost.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace crypto
|
||||
{
|
||||
class Verifier
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~Verifier () {};
|
||||
virtual bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const = 0;
|
||||
virtual size_t GetPublicKeyLen () const = 0;
|
||||
virtual size_t GetSignatureLen () const = 0;
|
||||
virtual size_t GetPrivateKeyLen () const { return GetSignatureLen ()/2; };
|
||||
};
|
||||
|
||||
class Signer
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~Signer () {};
|
||||
virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0;
|
||||
};
|
||||
|
||||
const size_t DSA_PUBLIC_KEY_LENGTH = 128;
|
||||
const size_t DSA_SIGNATURE_LENGTH = 40;
|
||||
const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2;
|
||||
class DSAVerifier: public Verifier
|
||||
{
|
||||
public:
|
||||
|
||||
DSAVerifier (const uint8_t * signingKey)
|
||||
{
|
||||
m_PublicKey = CreateDSA ();
|
||||
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:
|
||||
|
||||
DSA * m_PublicKey;
|
||||
};
|
||||
|
||||
class DSASigner: public Signer
|
||||
{
|
||||
public:
|
||||
|
||||
DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
|
||||
// openssl 1.1 always requires DSA public key even for signing
|
||||
{
|
||||
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 ()
|
||||
{
|
||||
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:
|
||||
|
||||
DSA * m_PrivateKey;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
struct SHA256Hash
|
||||
{
|
||||
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
SHA256 (buf, len, digest);
|
||||
}
|
||||
|
||||
enum { hashLen = 32 };
|
||||
};
|
||||
|
||||
struct SHA384Hash
|
||||
{
|
||||
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
SHA384 (buf, len, digest);
|
||||
}
|
||||
|
||||
enum { hashLen = 48 };
|
||||
};
|
||||
|
||||
struct SHA512Hash
|
||||
{
|
||||
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
SHA512 (buf, len, digest);
|
||||
}
|
||||
|
||||
enum { hashLen = 64 };
|
||||
};
|
||||
|
||||
template<typename Hash, int curve, size_t keyLen>
|
||||
class ECDSAVerifier: public Verifier
|
||||
{
|
||||
public:
|
||||
|
||||
ECDSAVerifier (const uint8_t * signingKey)
|
||||
{
|
||||
m_PublicKey = EC_KEY_new_by_curve_name (curve);
|
||||
BIGNUM * x = BN_bin2bn (signingKey, keyLen/2, NULL);
|
||||
BIGNUM * y = BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL);
|
||||
EC_KEY_set_public_key_affine_coordinates (m_PublicKey, x, y);
|
||||
BN_free (x); BN_free (y);
|
||||
}
|
||||
|
||||
~ECDSAVerifier ()
|
||||
{
|
||||
EC_KEY_free (m_PublicKey);
|
||||
}
|
||||
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||
{
|
||||
uint8_t digest[Hash::hashLen];
|
||||
Hash::CalculateHash (buf, len, digest);
|
||||
ECDSA_SIG * sig = ECDSA_SIG_new();
|
||||
auto r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL);
|
||||
auto s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL);
|
||||
ECDSA_SIG_set0(sig, r, s);
|
||||
// ECDSA verification
|
||||
int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey);
|
||||
ECDSA_SIG_free(sig);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t GetPublicKeyLen () const { return keyLen; };
|
||||
size_t GetSignatureLen () const { return keyLen; }; // signature length = key length
|
||||
|
||||
|
||||
private:
|
||||
|
||||
EC_KEY * m_PublicKey;
|
||||
};
|
||||
|
||||
template<typename Hash, int curve, size_t keyLen>
|
||||
class ECDSASigner: public Signer
|
||||
{
|
||||
public:
|
||||
|
||||
ECDSASigner (const uint8_t * signingPrivateKey)
|
||||
{
|
||||
m_PrivateKey = EC_KEY_new_by_curve_name (curve);
|
||||
EC_KEY_set_private_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen/2, NULL));
|
||||
}
|
||||
|
||||
~ECDSASigner ()
|
||||
{
|
||||
EC_KEY_free (m_PrivateKey);
|
||||
}
|
||||
|
||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
uint8_t digest[Hash::hashLen];
|
||||
Hash::CalculateHash (buf, len, digest);
|
||||
ECDSA_SIG * sig = ECDSA_do_sign (digest, Hash::hashLen, m_PrivateKey);
|
||||
const BIGNUM * r, * s;
|
||||
ECDSA_SIG_get0 (sig, &r, &s);
|
||||
// signatureLen = keyLen
|
||||
bn2buf (r, signature, keyLen/2);
|
||||
bn2buf (s, signature + keyLen/2, keyLen/2);
|
||||
ECDSA_SIG_free(sig);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
EC_KEY * m_PrivateKey;
|
||||
};
|
||||
|
||||
inline void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
EC_KEY * signingKey = EC_KEY_new_by_curve_name (curve);
|
||||
EC_KEY_generate_key (signingKey);
|
||||
bn2buf (EC_KEY_get0_private_key (signingKey), signingPrivateKey, keyLen/2);
|
||||
BIGNUM * x = BN_new(), * y = BN_new();
|
||||
EC_POINT_get_affine_coordinates_GFp (EC_KEY_get0_group(signingKey),
|
||||
EC_KEY_get0_public_key (signingKey), x, y, NULL);
|
||||
bn2buf (x, signingPublicKey, keyLen/2);
|
||||
bn2buf (y, signingPublicKey + keyLen/2, keyLen/2);
|
||||
BN_free (x); BN_free (y);
|
||||
EC_KEY_free (signingKey);
|
||||
}
|
||||
|
||||
// ECDSA_SHA256_P256
|
||||
const size_t ECDSAP256_KEY_LENGTH = 64;
|
||||
typedef ECDSAVerifier<SHA256Hash, NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH> ECDSAP256Verifier;
|
||||
typedef ECDSASigner<SHA256Hash, NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH> ECDSAP256Signer;
|
||||
|
||||
inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey);
|
||||
}
|
||||
|
||||
// ECDSA_SHA384_P384
|
||||
const size_t ECDSAP384_KEY_LENGTH = 96;
|
||||
typedef ECDSAVerifier<SHA384Hash, NID_secp384r1, ECDSAP384_KEY_LENGTH> ECDSAP384Verifier;
|
||||
typedef ECDSASigner<SHA384Hash, NID_secp384r1, ECDSAP384_KEY_LENGTH> ECDSAP384Signer;
|
||||
|
||||
inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey);
|
||||
}
|
||||
|
||||
// ECDSA_SHA512_P521
|
||||
const size_t ECDSAP521_KEY_LENGTH = 132;
|
||||
typedef ECDSAVerifier<SHA512Hash, NID_secp521r1, ECDSAP521_KEY_LENGTH> ECDSAP521Verifier;
|
||||
typedef ECDSASigner<SHA512Hash, NID_secp521r1, ECDSAP521_KEY_LENGTH> ECDSAP521Signer;
|
||||
|
||||
inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey);
|
||||
}
|
||||
|
||||
// RSA
|
||||
template<typename Hash, int type, size_t keyLen>
|
||||
class RSAVerifier: public Verifier
|
||||
{
|
||||
public:
|
||||
|
||||
RSAVerifier (const uint8_t * signingKey)
|
||||
{
|
||||
m_PublicKey = RSA_new ();
|
||||
RSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, keyLen, NULL) /* n */ , BN_dup (GetRSAE ()) /* d */, NULL);
|
||||
}
|
||||
|
||||
~RSAVerifier ()
|
||||
{
|
||||
RSA_free (m_PublicKey);
|
||||
}
|
||||
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||
{
|
||||
uint8_t digest[Hash::hashLen];
|
||||
Hash::CalculateHash (buf, len, digest);
|
||||
return RSA_verify (type, digest, Hash::hashLen, signature, GetSignatureLen (), m_PublicKey);
|
||||
}
|
||||
size_t GetPublicKeyLen () const { return keyLen; }
|
||||
size_t GetSignatureLen () const { return keyLen; }
|
||||
size_t GetPrivateKeyLen () const { return GetSignatureLen ()*2; };
|
||||
|
||||
private:
|
||||
|
||||
RSA * m_PublicKey;
|
||||
};
|
||||
|
||||
|
||||
template<typename Hash, int type, size_t keyLen>
|
||||
class RSASigner: public Signer
|
||||
{
|
||||
public:
|
||||
|
||||
RSASigner (const uint8_t * signingPrivateKey)
|
||||
{
|
||||
m_PrivateKey = RSA_new ();
|
||||
RSA_set0_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen, NULL), /* n */
|
||||
BN_dup (GetRSAE ()) /* e */, BN_bin2bn (signingPrivateKey + keyLen, keyLen, NULL) /* d */);
|
||||
}
|
||||
|
||||
~RSASigner ()
|
||||
{
|
||||
RSA_free (m_PrivateKey);
|
||||
}
|
||||
|
||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
uint8_t digest[Hash::hashLen];
|
||||
Hash::CalculateHash (buf, len, digest);
|
||||
unsigned int signatureLen = keyLen;
|
||||
RSA_sign (type, digest, Hash::hashLen, signature, &signatureLen, m_PrivateKey);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
RSA * m_PrivateKey;
|
||||
};
|
||||
|
||||
inline void CreateRSARandomKeys (size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
RSA * rsa = RSA_new ();
|
||||
BIGNUM * e = BN_dup (GetRSAE ()); // make it non-const
|
||||
RSA_generate_key_ex (rsa, publicKeyLen*8, e, NULL);
|
||||
const BIGNUM * n, * d, * e1;
|
||||
RSA_get0_key (rsa, &n, &e1, &d);
|
||||
bn2buf (n, signingPrivateKey, publicKeyLen);
|
||||
bn2buf (d, signingPrivateKey + publicKeyLen, publicKeyLen);
|
||||
bn2buf (n, signingPublicKey, publicKeyLen);
|
||||
BN_free (e); // this e is not assigned to rsa->e
|
||||
RSA_free (rsa);
|
||||
}
|
||||
|
||||
// RSA_SHA256_2048
|
||||
const size_t RSASHA2562048_KEY_LENGTH = 256;
|
||||
typedef RSAVerifier<SHA256Hash, NID_sha256, RSASHA2562048_KEY_LENGTH> RSASHA2562048Verifier;
|
||||
typedef RSASigner<SHA256Hash, NID_sha256, RSASHA2562048_KEY_LENGTH> RSASHA2562048Signer;
|
||||
|
||||
// RSA_SHA384_3072
|
||||
const size_t RSASHA3843072_KEY_LENGTH = 384;
|
||||
typedef RSAVerifier<SHA384Hash, NID_sha384, RSASHA3843072_KEY_LENGTH> RSASHA3843072Verifier;
|
||||
typedef RSASigner<SHA384Hash, NID_sha384, RSASHA3843072_KEY_LENGTH> RSASHA3843072Signer;
|
||||
|
||||
// RSA_SHA512_4096
|
||||
const size_t RSASHA5124096_KEY_LENGTH = 512;
|
||||
typedef RSAVerifier<SHA512Hash, NID_sha512, RSASHA5124096_KEY_LENGTH> RSASHA5124096Verifier;
|
||||
typedef RSASigner<SHA512Hash, NID_sha512, RSASHA5124096_KEY_LENGTH> RSASHA5124096Signer;
|
||||
|
||||
// EdDSA
|
||||
struct EDDSAPoint
|
||||
{
|
||||
BIGNUM * x, * y;
|
||||
BIGNUM * z, * t; // projective coordinates
|
||||
EDDSAPoint (): x(nullptr), y(nullptr), z(nullptr), t(nullptr) {};
|
||||
EDDSAPoint (const EDDSAPoint& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr)
|
||||
{ *this = other; };
|
||||
EDDSAPoint (EDDSAPoint&& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr)
|
||||
{ *this = std::move (other); };
|
||||
EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr): x(x1), y(y1), z(z1), t(t1) {};
|
||||
~EDDSAPoint () { BN_free (x); BN_free (y); BN_free(z); BN_free(t); };
|
||||
|
||||
EDDSAPoint& operator=(EDDSAPoint&& other)
|
||||
{
|
||||
if (x) BN_free (x); x = other.x; other.x = nullptr;
|
||||
if (y) BN_free (y); y = other.y; other.y = nullptr;
|
||||
if (z) BN_free (z); z = other.z; other.z = nullptr;
|
||||
if (t) BN_free (t); t = other.t; other.t = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
EDDSAPoint& operator=(const EDDSAPoint& other)
|
||||
{
|
||||
if (x) BN_free (x); x = other.x ? BN_dup (other.x) : nullptr;
|
||||
if (y) BN_free (y); y = other.y ? BN_dup (other.y) : nullptr;
|
||||
if (z) BN_free (z); z = other.z ? BN_dup (other.z) : nullptr;
|
||||
if (t) BN_free (t); t = other.t ? BN_dup (other.t) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
EDDSAPoint operator-() const
|
||||
{
|
||||
BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL;
|
||||
if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); };
|
||||
if (y) y1 = BN_dup (y);
|
||||
if (z) z1 = BN_dup (z);
|
||||
if (t) { t1 = BN_dup (t); BN_set_negative (t1, !BN_is_negative (t)); };
|
||||
return EDDSAPoint {x1, y1, z1, t1};
|
||||
}
|
||||
};
|
||||
|
||||
const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32;
|
||||
const size_t EDDSA25519_SIGNATURE_LENGTH = 64;
|
||||
const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32;
|
||||
class EDDSA25519Verifier: public Verifier
|
||||
{
|
||||
public:
|
||||
|
||||
EDDSA25519Verifier (const uint8_t * signingKey);
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const;
|
||||
|
||||
size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; };
|
||||
size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; };
|
||||
|
||||
private:
|
||||
|
||||
EDDSAPoint m_PublicKey;
|
||||
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
|
||||
};
|
||||
|
||||
class EDDSA25519Signer: public Signer
|
||||
{
|
||||
public:
|
||||
|
||||
EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr);
|
||||
// we pass signingPublicKey to check if it matches private key
|
||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
|
||||
const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; };
|
||||
|
||||
private:
|
||||
|
||||
uint8_t m_ExpandedPrivateKey[64];
|
||||
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
|
||||
};
|
||||
|
||||
inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH);
|
||||
EDDSA25519Signer signer (signingPrivateKey);
|
||||
memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
|
||||
}
|
||||
|
||||
|
||||
// ГОСТ Р 34.11
|
||||
struct GOSTR3411_256_Hash
|
||||
{
|
||||
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
GOSTR3411_2012_256 (buf, len, digest);
|
||||
}
|
||||
|
||||
enum { hashLen = 32 };
|
||||
};
|
||||
|
||||
struct GOSTR3411_512_Hash
|
||||
{
|
||||
static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest)
|
||||
{
|
||||
GOSTR3411_2012_512 (buf, len, digest);
|
||||
}
|
||||
|
||||
enum { hashLen = 64 };
|
||||
};
|
||||
|
||||
// ГОСТ Р 34.10
|
||||
const size_t GOSTR3410_256_PUBLIC_KEY_LENGTH = 64;
|
||||
const size_t GOSTR3410_512_PUBLIC_KEY_LENGTH = 128;
|
||||
|
||||
template<typename Hash>
|
||||
class GOSTR3410Verifier: public Verifier
|
||||
{
|
||||
public:
|
||||
|
||||
enum { keyLen = Hash::hashLen };
|
||||
|
||||
GOSTR3410Verifier (GOSTR3410ParamSet paramSet, const uint8_t * signingKey):
|
||||
m_ParamSet (paramSet)
|
||||
{
|
||||
BIGNUM * x = BN_bin2bn (signingKey, GetPublicKeyLen ()/2, NULL);
|
||||
BIGNUM * y = BN_bin2bn (signingKey + GetPublicKeyLen ()/2, GetPublicKeyLen ()/2, NULL);
|
||||
m_PublicKey = GetGOSTR3410Curve (m_ParamSet)->CreatePoint (x, y);
|
||||
BN_free (x); BN_free (y);
|
||||
}
|
||||
~GOSTR3410Verifier () { EC_POINT_free (m_PublicKey); }
|
||||
|
||||
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
|
||||
{
|
||||
uint8_t digest[Hash::hashLen];
|
||||
Hash::CalculateHash (buf, len, digest);
|
||||
BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr);
|
||||
BIGNUM * r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL);
|
||||
BIGNUM * s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL);
|
||||
bool ret = GetGOSTR3410Curve (m_ParamSet)->Verify (m_PublicKey, d, r, s);
|
||||
BN_free (d); BN_free (r); BN_free (s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t GetPublicKeyLen () const { return keyLen*2; }
|
||||
size_t GetSignatureLen () const { return keyLen*2; }
|
||||
|
||||
private:
|
||||
|
||||
GOSTR3410ParamSet m_ParamSet;
|
||||
EC_POINT * m_PublicKey;
|
||||
};
|
||||
|
||||
template<typename Hash>
|
||||
class GOSTR3410Signer: public Signer
|
||||
{
|
||||
public:
|
||||
|
||||
enum { keyLen = Hash::hashLen };
|
||||
|
||||
GOSTR3410Signer (GOSTR3410ParamSet paramSet, const uint8_t * signingPrivateKey):
|
||||
m_ParamSet (paramSet)
|
||||
{
|
||||
m_PrivateKey = BN_bin2bn (signingPrivateKey, keyLen, nullptr);
|
||||
}
|
||||
~GOSTR3410Signer () { BN_free (m_PrivateKey); }
|
||||
|
||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
|
||||
{
|
||||
uint8_t digest[Hash::hashLen];
|
||||
Hash::CalculateHash (buf, len, digest);
|
||||
BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr);
|
||||
BIGNUM * r = BN_new (), * s = BN_new ();
|
||||
GetGOSTR3410Curve (m_ParamSet)->Sign (m_PrivateKey, d, r, s);
|
||||
bn2buf (r, signature, keyLen);
|
||||
bn2buf (s, signature + keyLen, keyLen);
|
||||
BN_free (d); BN_free (r); BN_free (s);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
GOSTR3410ParamSet m_ParamSet;
|
||||
BIGNUM * m_PrivateKey;
|
||||
};
|
||||
|
||||
inline void CreateGOSTR3410RandomKeys (GOSTR3410ParamSet paramSet, uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
|
||||
{
|
||||
const auto& curve = GetGOSTR3410Curve (paramSet);
|
||||
auto keyLen = curve->GetKeyLen ();
|
||||
RAND_bytes (signingPrivateKey, keyLen);
|
||||
BIGNUM * priv = BN_bin2bn (signingPrivateKey, keyLen, nullptr);
|
||||
|
||||
auto pub = curve->MulP (priv);
|
||||
BN_free (priv);
|
||||
BIGNUM * x = BN_new (), * y = BN_new ();
|
||||
curve->GetXY (pub, x, y);
|
||||
EC_POINT_free (pub);
|
||||
bn2buf (x, signingPublicKey, keyLen);
|
||||
bn2buf (y, signingPublicKey + keyLen, keyLen);
|
||||
BN_free (x); BN_free (y);
|
||||
}
|
||||
|
||||
typedef GOSTR3410Verifier<GOSTR3411_256_Hash> GOSTR3410_256_Verifier;
|
||||
typedef GOSTR3410Signer<GOSTR3411_256_Hash> GOSTR3410_256_Signer;
|
||||
typedef GOSTR3410Verifier<GOSTR3411_512_Hash> GOSTR3410_512_Verifier;
|
||||
typedef GOSTR3410Signer<GOSTR3411_512_Hash> GOSTR3410_512_Signer;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
1212
libi2pd/Streaming.cpp
Normal file
1212
libi2pd/Streaming.cpp
Normal file
File diff suppressed because it is too large
Load diff
380
libi2pd/Streaming.h
Normal file
380
libi2pd/Streaming.h
Normal file
|
@ -0,0 +1,380 @@
|
|||
#ifndef STREAMING_H__
|
||||
#define STREAMING_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Base.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Identity.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Garlic.h"
|
||||
#include "Tunnel.h"
|
||||
#include "util.h" // MemoryPool
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace client
|
||||
{
|
||||
class ClientDestination;
|
||||
}
|
||||
namespace stream
|
||||
{
|
||||
const uint16_t PACKET_FLAG_SYNCHRONIZE = 0x0001;
|
||||
const uint16_t PACKET_FLAG_CLOSE = 0x0002;
|
||||
const uint16_t PACKET_FLAG_RESET = 0x0004;
|
||||
const uint16_t PACKET_FLAG_SIGNATURE_INCLUDED = 0x0008;
|
||||
const uint16_t PACKET_FLAG_SIGNATURE_REQUESTED = 0x0010;
|
||||
const uint16_t PACKET_FLAG_FROM_INCLUDED = 0x0020;
|
||||
const uint16_t PACKET_FLAG_DELAY_REQUESTED = 0x0040;
|
||||
const uint16_t PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED = 0x0080;
|
||||
const uint16_t PACKET_FLAG_PROFILE_INTERACTIVE = 0x0100;
|
||||
const uint16_t PACKET_FLAG_ECHO = 0x0200;
|
||||
const uint16_t PACKET_FLAG_NO_ACK = 0x0400;
|
||||
|
||||
const size_t STREAMING_MTU = 1730;
|
||||
const size_t MAX_PACKET_SIZE = 4096;
|
||||
const size_t COMPRESSION_THRESHOLD_SIZE = 66;
|
||||
const int ACK_SEND_TIMEOUT = 200; // in milliseconds
|
||||
const int MAX_NUM_RESEND_ATTEMPTS = 6;
|
||||
const int WINDOW_SIZE = 6; // in messages
|
||||
const int MIN_WINDOW_SIZE = 1;
|
||||
const int MAX_WINDOW_SIZE = 128;
|
||||
const int INITIAL_RTT = 8000; // in milliseconds
|
||||
const int INITIAL_RTO = 9000; // in milliseconds
|
||||
const size_t MAX_PENDING_INCOMING_BACKLOG = 128;
|
||||
const int PENDING_INCOMING_TIMEOUT = 10; // in seconds
|
||||
const int MAX_RECEIVE_TIMEOUT = 30; // in seconds
|
||||
|
||||
/** i2cp option for limiting inbound stremaing connections */
|
||||
const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxconns";
|
||||
/** default maximum connections attempts per minute per destination */
|
||||
const uint32_t DEFAULT_MAX_CONNS_PER_MIN = 600;
|
||||
|
||||
/**
|
||||
* max banned destinations per local destination
|
||||
* TODO: make configurable
|
||||
*/
|
||||
const uint16_t MAX_BANNED_CONNS = 9999;
|
||||
/**
|
||||
* length of a ban in ms
|
||||
* TODO: make configurable
|
||||
*/
|
||||
const uint64_t DEFAULT_BAN_INTERVAL = 60 * 60 * 1000;
|
||||
|
||||
struct Packet
|
||||
{
|
||||
size_t len, offset;
|
||||
uint8_t buf[MAX_PACKET_SIZE];
|
||||
uint64_t sendTime;
|
||||
|
||||
Packet (): len (0), offset (0), sendTime (0) {};
|
||||
uint8_t * GetBuffer () { return buf + offset; };
|
||||
size_t GetLength () const { return len - offset; };
|
||||
|
||||
uint32_t GetSendStreamID () const { return bufbe32toh (buf); };
|
||||
uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); };
|
||||
uint32_t GetSeqn () const { return bufbe32toh (buf + 8); };
|
||||
uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); };
|
||||
uint8_t GetNACKCount () const { return buf[16]; };
|
||||
uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); };
|
||||
const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags
|
||||
uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); };
|
||||
uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); };
|
||||
const uint8_t * GetOptionData () const { return GetOption () + 2; };
|
||||
const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); };
|
||||
|
||||
bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; };
|
||||
bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; };
|
||||
};
|
||||
|
||||
struct PacketCmp
|
||||
{
|
||||
bool operator() (const Packet * p1, const Packet * p2) const
|
||||
{
|
||||
return p1->GetSeqn () < p2->GetSeqn ();
|
||||
};
|
||||
};
|
||||
|
||||
typedef std::function<void (const boost::system::error_code& ecode)> SendHandler;
|
||||
struct SendBuffer
|
||||
{
|
||||
uint8_t * buf;
|
||||
size_t len, offset;
|
||||
SendHandler handler;
|
||||
|
||||
SendBuffer (const uint8_t * b, size_t l, SendHandler h):
|
||||
len(l), offset (0), handler(h)
|
||||
{
|
||||
buf = new uint8_t[len];
|
||||
memcpy (buf, b, len);
|
||||
}
|
||||
~SendBuffer ()
|
||||
{
|
||||
delete[] buf;
|
||||
if (handler) handler(boost::system::error_code ());
|
||||
}
|
||||
size_t GetRemainingSize () const { return len - offset; };
|
||||
const uint8_t * GetRemaningBuffer () const { return buf + offset; };
|
||||
void Cancel () { if (handler) handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); handler = nullptr; };
|
||||
};
|
||||
|
||||
class SendBufferQueue
|
||||
{
|
||||
public:
|
||||
|
||||
SendBufferQueue (): m_Size (0) {};
|
||||
~SendBufferQueue () { CleanUp (); };
|
||||
|
||||
void Add (const uint8_t * buf, size_t len, SendHandler handler);
|
||||
size_t Get (uint8_t * buf, size_t len);
|
||||
size_t GetSize () const { return m_Size; };
|
||||
bool IsEmpty () const { return m_Buffers.empty (); };
|
||||
void CleanUp ();
|
||||
|
||||
private:
|
||||
|
||||
std::list<std::shared_ptr<SendBuffer> > m_Buffers;
|
||||
size_t m_Size;
|
||||
};
|
||||
|
||||
enum StreamStatus
|
||||
{
|
||||
eStreamStatusNew = 0,
|
||||
eStreamStatusOpen,
|
||||
eStreamStatusReset,
|
||||
eStreamStatusClosing,
|
||||
eStreamStatusClosed
|
||||
};
|
||||
|
||||
class StreamingDestination;
|
||||
class Stream: public std::enable_shared_from_this<Stream>
|
||||
{
|
||||
public:
|
||||
|
||||
Stream (boost::asio::io_service& service, StreamingDestination& local,
|
||||
std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0); // outgoing
|
||||
Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming
|
||||
|
||||
~Stream ();
|
||||
uint32_t GetSendStreamID () const { return m_SendStreamID; };
|
||||
uint32_t GetRecvStreamID () const { return m_RecvStreamID; };
|
||||
std::shared_ptr<const i2p::data::LeaseSet> GetRemoteLeaseSet () const { return m_RemoteLeaseSet; };
|
||||
std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () const { return m_RemoteIdentity; };
|
||||
bool IsOpen () const { return m_Status == eStreamStatusOpen; };
|
||||
bool IsEstablished () const { return m_SendStreamID; };
|
||||
StreamStatus GetStatus () const { return m_Status; };
|
||||
StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
|
||||
|
||||
void HandleNextPacket (Packet * packet);
|
||||
size_t Send (const uint8_t * buf, size_t len);
|
||||
void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler);
|
||||
|
||||
template<typename Buffer, typename ReceiveHandler>
|
||||
void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0);
|
||||
size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); };
|
||||
|
||||
void Close ();
|
||||
void Cancel () { m_ReceiveTimer.cancel (); };
|
||||
|
||||
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||
size_t GetSendQueueSize () const { return m_SentPackets.size (); };
|
||||
size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); };
|
||||
size_t GetSendBufferSize () const { return m_SendBuffer.GetSize (); };
|
||||
int GetWindowSize () const { return m_WindowSize; };
|
||||
int GetRTT () const { return m_RTT; };
|
||||
|
||||
/** don't call me */
|
||||
void Terminate ();
|
||||
|
||||
private:
|
||||
|
||||
void CleanUp ();
|
||||
|
||||
void SendBuffer ();
|
||||
void SendQuickAck ();
|
||||
void SendClose ();
|
||||
bool SendPacket (Packet * packet);
|
||||
void SendPackets (const std::vector<Packet *>& packets);
|
||||
void SendUpdatedLeaseSet ();
|
||||
|
||||
void SavePacket (Packet * packet);
|
||||
void ProcessPacket (Packet * packet);
|
||||
void ProcessAck (Packet * packet);
|
||||
size_t ConcatenatePackets (uint8_t * buf, size_t len);
|
||||
|
||||
void UpdateCurrentRemoteLease (bool expired = false);
|
||||
|
||||
template<typename Buffer, typename ReceiveHandler>
|
||||
void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout);
|
||||
|
||||
void ScheduleResend ();
|
||||
void HandleResendTimer (const boost::system::error_code& ecode);
|
||||
void HandleAckSendTimer (const boost::system::error_code& ecode);
|
||||
|
||||
private:
|
||||
|
||||
boost::asio::io_service& m_Service;
|
||||
uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
|
||||
int32_t m_LastReceivedSequenceNumber;
|
||||
StreamStatus m_Status;
|
||||
bool m_IsAckSendScheduled;
|
||||
StreamingDestination& m_LocalDestination;
|
||||
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
|
||||
std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
|
||||
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
|
||||
std::shared_ptr<const i2p::data::Lease> m_CurrentRemoteLease;
|
||||
std::shared_ptr<i2p::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
|
||||
std::queue<Packet *> m_ReceiveQueue;
|
||||
std::set<Packet *, PacketCmp> m_SavedPackets;
|
||||
std::set<Packet *, PacketCmp> m_SentPackets;
|
||||
boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer;
|
||||
size_t m_NumSentBytes, m_NumReceivedBytes;
|
||||
uint16_t m_Port;
|
||||
|
||||
std::mutex m_SendBufferMutex;
|
||||
SendBufferQueue m_SendBuffer;
|
||||
int m_WindowSize, m_RTT, m_RTO;
|
||||
uint64_t m_LastWindowSizeIncreaseTime;
|
||||
int m_NumResendAttempts;
|
||||
};
|
||||
|
||||
class StreamingDestination: public std::enable_shared_from_this<StreamingDestination>
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::function<void (std::shared_ptr<Stream>)> Acceptor;
|
||||
|
||||
StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort = 0, bool gzip = true);
|
||||
~StreamingDestination ();
|
||||
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
std::shared_ptr<Stream> CreateNewOutgoingStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0);
|
||||
void DeleteStream (std::shared_ptr<Stream> stream);
|
||||
void SetAcceptor (const Acceptor& acceptor);
|
||||
void ResetAcceptor ();
|
||||
bool IsAcceptorSet () const { return m_Acceptor != nullptr; };
|
||||
void AcceptOnce (const Acceptor& acceptor);
|
||||
|
||||
std::shared_ptr<i2p::client::ClientDestination> GetOwner () const { return m_Owner; };
|
||||
void SetOwner (std::shared_ptr<i2p::client::ClientDestination> owner) { m_Owner = owner; };
|
||||
uint16_t GetLocalPort () const { return m_LocalPort; };
|
||||
|
||||
void HandleDataMessagePayload (const uint8_t * buf, size_t len);
|
||||
std::shared_ptr<I2NPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort);
|
||||
|
||||
/** set max connections per minute per destination */
|
||||
void SetMaxConnsPerMinute(const uint32_t conns);
|
||||
|
||||
Packet * NewPacket () { return m_PacketsPool.Acquire (); };
|
||||
void DeletePacket (Packet * p) { if (p) m_PacketsPool.Release (p); };
|
||||
|
||||
private:
|
||||
|
||||
void AcceptOnceAcceptor (std::shared_ptr<Stream> stream, Acceptor acceptor, Acceptor prev);
|
||||
|
||||
void HandleNextPacket (Packet * packet);
|
||||
std::shared_ptr<Stream> CreateNewIncomingStream ();
|
||||
void HandlePendingIncomingTimer (const boost::system::error_code& ecode);
|
||||
|
||||
/** handle cleaning up connection tracking for ratelimits */
|
||||
void HandleConnTrack(const boost::system::error_code& ecode);
|
||||
|
||||
bool DropNewStream(const i2p::data::IdentHash & ident);
|
||||
|
||||
void ScheduleConnTrack();
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<i2p::client::ClientDestination> m_Owner;
|
||||
uint16_t m_LocalPort;
|
||||
bool m_Gzip; // gzip compression of data messages
|
||||
std::mutex m_StreamsMutex;
|
||||
std::map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream
|
||||
Acceptor m_Acceptor;
|
||||
uint32_t m_LastIncomingReceiveStreamID;
|
||||
std::list<std::shared_ptr<Stream> > m_PendingIncomingStreams;
|
||||
boost::asio::deadline_timer m_PendingIncomingTimer;
|
||||
std::map<uint32_t, std::list<Packet *> > m_SavedPackets; // receiveStreamID->packets, arrived before SYN
|
||||
|
||||
std::mutex m_ConnsMutex;
|
||||
/** how many connections per minute did each identity have */
|
||||
std::map<i2p::data::IdentHash, uint32_t> m_Conns;
|
||||
boost::asio::deadline_timer m_ConnTrackTimer;
|
||||
uint32_t m_ConnsPerMinute;
|
||||
/** banned identities */
|
||||
std::vector<i2p::data::IdentHash> m_Banned;
|
||||
uint64_t m_LastBanClear;
|
||||
|
||||
i2p::util::MemoryPool<Packet> m_PacketsPool;
|
||||
|
||||
public:
|
||||
|
||||
i2p::data::GzipInflator m_Inflator;
|
||||
i2p::data::GzipDeflator m_Deflator;
|
||||
|
||||
// for HTTP only
|
||||
const decltype(m_Streams)& GetStreams () const { return m_Streams; };
|
||||
};
|
||||
|
||||
//-------------------------------------------------
|
||||
|
||||
template<typename Buffer, typename ReceiveHandler>
|
||||
void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout)
|
||||
{
|
||||
auto s = shared_from_this();
|
||||
m_Service.post ([=](void)
|
||||
{
|
||||
if (!m_ReceiveQueue.empty () || m_Status == eStreamStatusReset)
|
||||
s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler, 0);
|
||||
else
|
||||
{
|
||||
int t = (timeout > MAX_RECEIVE_TIMEOUT) ? MAX_RECEIVE_TIMEOUT : timeout;
|
||||
s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(t));
|
||||
s->m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode)
|
||||
{ s->HandleReceiveTimer (ecode, buffer, handler, timeout - t); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Buffer, typename ReceiveHandler>
|
||||
void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout)
|
||||
{
|
||||
size_t received = ConcatenatePackets (boost::asio::buffer_cast<uint8_t *>(buffer), boost::asio::buffer_size(buffer));
|
||||
if (received > 0)
|
||||
handler (boost::system::error_code (), received);
|
||||
else if (ecode == boost::asio::error::operation_aborted)
|
||||
{
|
||||
// timeout not expired
|
||||
if (m_Status == eStreamStatusReset)
|
||||
handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0);
|
||||
else
|
||||
handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// timeout expired
|
||||
if (remainingTimeout <= 0)
|
||||
handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received);
|
||||
else
|
||||
{
|
||||
// itermediate iterrupt
|
||||
SendUpdatedLeaseSet (); // send our leaseset if applicable
|
||||
AsyncReceive (buffer, handler, remainingTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
95
libi2pd/Tag.h
Normal file
95
libi2pd/Tag.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
#ifndef TAG_H__
|
||||
#define TAG_H__
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013-2016, 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 <boost/static_assert.hpp>
|
||||
#include <string.h>
|
||||
#include <openssl/rand.h>
|
||||
#include "Base.h"
|
||||
|
||||
namespace i2p {
|
||||
namespace data {
|
||||
|
||||
template<size_t sz>
|
||||
class Tag
|
||||
{
|
||||
BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes");
|
||||
|
||||
public:
|
||||
|
||||
Tag () = default;
|
||||
Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }
|
||||
|
||||
bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); }
|
||||
bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; }
|
||||
|
||||
uint8_t * operator()() { return m_Buf; }
|
||||
const uint8_t * operator()() const { return m_Buf; }
|
||||
|
||||
operator uint8_t * () { return m_Buf; }
|
||||
operator const uint8_t * () const { return m_Buf; }
|
||||
|
||||
const uint8_t * data() const { return m_Buf; }
|
||||
const uint64_t * GetLL () const { return ll; }
|
||||
|
||||
bool IsZero () const
|
||||
{
|
||||
for (size_t i = 0; i < sz/8; ++i)
|
||||
if (ll[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Fill(uint8_t c)
|
||||
{
|
||||
memset(m_Buf, c, sz);
|
||||
}
|
||||
|
||||
void Randomize()
|
||||
{
|
||||
RAND_bytes(m_Buf, sz);
|
||||
}
|
||||
|
||||
std::string ToBase64 () const
|
||||
{
|
||||
char str[sz*2];
|
||||
size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2);
|
||||
return std::string (str, str + l);
|
||||
}
|
||||
|
||||
std::string ToBase32 () const
|
||||
{
|
||||
char str[sz*2];
|
||||
size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2);
|
||||
return std::string (str, str + l);
|
||||
}
|
||||
|
||||
void FromBase32 (const std::string& s)
|
||||
{
|
||||
i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
|
||||
}
|
||||
|
||||
void FromBase64 (const std::string& s)
|
||||
{
|
||||
i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
union // 8 bytes aligned
|
||||
{
|
||||
uint8_t m_Buf[sz];
|
||||
uint64_t ll[sz/8];
|
||||
};
|
||||
};
|
||||
|
||||
} // data
|
||||
} // i2p
|
||||
|
||||
#endif /* TAG_H__ */
|
66
libi2pd/Timestamp.cpp
Normal file
66
libi2pd/Timestamp.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include "Log.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Timestamp.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#ifndef _WIN64
|
||||
#define _USE_32BIT_TIME_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
static int64_t g_TimeOffset = 0; // in seconds
|
||||
|
||||
void SyncTimeWithNTP (const std::string& address)
|
||||
{
|
||||
boost::asio::io_service service;
|
||||
boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp");
|
||||
boost::system::error_code ec;
|
||||
auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec);
|
||||
if (!ec && it != boost::asio::ip::udp::resolver::iterator())
|
||||
{
|
||||
auto ep = (*it).endpoint (); // take first one
|
||||
boost::asio::ip::udp::socket socket (service);
|
||||
socket.open (boost::asio::ip::udp::v4 (), ec);
|
||||
if (!ec)
|
||||
{
|
||||
uint8_t buf[48];// 48 bytes NTP request/response
|
||||
memset (buf, 0, 48);
|
||||
htobe32buf (buf, (3 << 27) | (3 << 24)); // RFC 4330
|
||||
size_t len = 0;
|
||||
try
|
||||
{
|
||||
socket.send_to (boost::asio::buffer (buf, 48), ep);
|
||||
int i = 0;
|
||||
while (!socket.available() && i < 10) // 10 seconds max
|
||||
{
|
||||
std::this_thread::sleep_for (std::chrono::seconds(1));
|
||||
i++;
|
||||
}
|
||||
if (socket.available ())
|
||||
len = socket.receive_from (boost::asio::buffer (buf, 48), ep);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
LogPrint (eLogError, "NTP error: ", e.what ());
|
||||
}
|
||||
if (len >= 8)
|
||||
{
|
||||
auto ourTs = GetSecondsSinceEpoch ();
|
||||
uint32_t ts = bufbe32toh (buf + 32);
|
||||
if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900
|
||||
g_TimeOffset = ts - ourTs;
|
||||
LogPrint (eLogInfo, address, " time offset from system time is ", g_TimeOffset, " seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
libi2pd/Timestamp.h
Normal file
32
libi2pd/Timestamp.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef TIMESTAMP_H__
|
||||
#define TIMESTAMP_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <chrono>
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
inline uint64_t GetMillisecondsSinceEpoch ()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count ();
|
||||
}
|
||||
|
||||
inline uint32_t GetHoursSinceEpoch ()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::hours>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count ();
|
||||
}
|
||||
|
||||
inline uint64_t GetSecondsSinceEpoch ()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
110
libi2pd/TransitTunnel.cpp
Normal file
110
libi2pd/TransitTunnel.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include <string.h>
|
||||
#include "I2PEndian.h"
|
||||
#include "Log.h"
|
||||
#include "RouterContext.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Tunnel.h"
|
||||
#include "Transports.h"
|
||||
#include "TransitTunnel.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
TransitTunnel::TransitTunnel (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||
TunnelBase (receiveTunnelID, nextTunnelID, nextIdent)
|
||||
{
|
||||
m_Encryption.SetKeys (layerKey, ivKey);
|
||||
}
|
||||
|
||||
void TransitTunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
|
||||
{
|
||||
m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4);
|
||||
}
|
||||
|
||||
TransitTunnelParticipant::~TransitTunnelParticipant ()
|
||||
{
|
||||
}
|
||||
|
||||
void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||
{
|
||||
auto newMsg = CreateEmptyTunnelDataMsg ();
|
||||
EncryptTunnelMsg (tunnelMsg, newMsg);
|
||||
|
||||
m_NumTransmittedBytes += tunnelMsg->GetLength ();
|
||||
htobe32buf (newMsg->GetPayload (), GetNextTunnelID ());
|
||||
newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||
m_TunnelDataMsgs.push_back (newMsg);
|
||||
}
|
||||
|
||||
void TransitTunnelParticipant::FlushTunnelDataMsgs ()
|
||||
{
|
||||
if (!m_TunnelDataMsgs.empty ())
|
||||
{
|
||||
auto num = m_TunnelDataMsgs.size ();
|
||||
if (num > 1)
|
||||
LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num);
|
||||
i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs);
|
||||
m_TunnelDataMsgs.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||
{
|
||||
LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
|
||||
}
|
||||
|
||||
void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||
{
|
||||
LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
|
||||
}
|
||||
|
||||
void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||
{
|
||||
TunnelMessageBlock block;
|
||||
block.deliveryType = eDeliveryTypeLocal;
|
||||
block.data = msg;
|
||||
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||
m_Gateway.PutTunnelDataMsg (block);
|
||||
}
|
||||
|
||||
void TransitTunnelGateway::FlushTunnelDataMsgs ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||
m_Gateway.SendBuffer ();
|
||||
}
|
||||
|
||||
void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||
{
|
||||
auto newMsg = CreateEmptyTunnelDataMsg ();
|
||||
EncryptTunnelMsg (tunnelMsg, newMsg);
|
||||
|
||||
LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
|
||||
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
|
||||
}
|
||||
|
||||
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey,
|
||||
bool isGateway, bool isEndpoint)
|
||||
{
|
||||
if (isEndpoint)
|
||||
{
|
||||
LogPrint (eLogDebug, "TransitTunnel: endpoint ", receiveTunnelID, " created");
|
||||
return std::make_shared<TransitTunnelEndpoint> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||
}
|
||||
else if (isGateway)
|
||||
{
|
||||
LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created");
|
||||
return std::make_shared<TransitTunnelGateway> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogDebug, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created");
|
||||
return std::make_shared<TransitTunnelParticipant> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
105
libi2pd/TransitTunnel.h
Normal file
105
libi2pd/TransitTunnel.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
#ifndef TRANSIT_TUNNEL_H__
|
||||
#define TRANSIT_TUNNEL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "Crypto.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelEndpoint.h"
|
||||
#include "TunnelGateway.h"
|
||||
#include "TunnelBase.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class TransitTunnel: public TunnelBase
|
||||
{
|
||||
public:
|
||||
|
||||
TransitTunnel (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey);
|
||||
|
||||
virtual size_t GetNumTransmittedBytes () const { return 0; };
|
||||
|
||||
// implements TunnelBase
|
||||
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
|
||||
private:
|
||||
|
||||
i2p::crypto::TunnelEncryption m_Encryption;
|
||||
};
|
||||
|
||||
class TransitTunnelParticipant: public TransitTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
TransitTunnelParticipant (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
|
||||
layerKey, ivKey), m_NumTransmittedBytes (0) {};
|
||||
~TransitTunnelParticipant ();
|
||||
|
||||
size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; };
|
||||
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||
void FlushTunnelDataMsgs ();
|
||||
|
||||
private:
|
||||
|
||||
size_t m_NumTransmittedBytes;
|
||||
std::vector<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
|
||||
};
|
||||
|
||||
class TransitTunnelGateway: public TransitTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
TransitTunnelGateway (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
|
||||
layerKey, ivKey), m_Gateway(this) {};
|
||||
|
||||
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
void FlushTunnelDataMsgs ();
|
||||
size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); };
|
||||
|
||||
private:
|
||||
|
||||
std::mutex m_SendMutex;
|
||||
TunnelGateway m_Gateway;
|
||||
};
|
||||
|
||||
class TransitTunnelEndpoint: public TransitTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
TransitTunnelEndpoint (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey):
|
||||
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
|
||||
m_Endpoint (false) {}; // transit endpoint is always outbound
|
||||
|
||||
void Cleanup () { m_Endpoint.Cleanup (); }
|
||||
|
||||
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||
size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }
|
||||
|
||||
private:
|
||||
|
||||
TunnelEndpoint m_Endpoint;
|
||||
};
|
||||
|
||||
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
|
||||
const uint8_t * nextIdent, uint32_t nextTunnelID,
|
||||
const uint8_t * layerKey,const uint8_t * ivKey,
|
||||
bool isGateway, bool isEndpoint);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
96
libi2pd/TransportSession.h
Normal file
96
libi2pd/TransportSession.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
#ifndef TRANSPORT_SESSION_H__
|
||||
#define TRANSPORT_SESSION_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "Identity.h"
|
||||
#include "Crypto.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Timestamp.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
class SignedData
|
||||
{
|
||||
public:
|
||||
|
||||
SignedData () {}
|
||||
SignedData (const SignedData& other)
|
||||
{
|
||||
m_Stream << other.m_Stream.rdbuf ();
|
||||
}
|
||||
void Insert (const uint8_t * buf, size_t len)
|
||||
{
|
||||
m_Stream.write ((char *)buf, len);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Insert (T t)
|
||||
{
|
||||
m_Stream.write ((char *)&t, sizeof (T));
|
||||
}
|
||||
|
||||
bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const
|
||||
{
|
||||
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
|
||||
{
|
||||
keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::stringstream m_Stream;
|
||||
};
|
||||
|
||||
class TransportSession
|
||||
{
|
||||
public:
|
||||
|
||||
TransportSession (std::shared_ptr<const i2p::data::RouterInfo> router, int terminationTimeout):
|
||||
m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout),
|
||||
m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ())
|
||||
{
|
||||
if (router)
|
||||
m_RemoteIdentity = router->GetRouterIdentity ();
|
||||
}
|
||||
|
||||
virtual ~TransportSession () {};
|
||||
virtual void Done () = 0;
|
||||
|
||||
std::string GetIdentHashBase64() const { return m_RemoteIdentity ? m_RemoteIdentity->GetIdentHash().ToBase64() : ""; }
|
||||
|
||||
std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () { return m_RemoteIdentity; };
|
||||
void SetRemoteIdentity (std::shared_ptr<const i2p::data::IdentityEx> ident) { m_RemoteIdentity = ident; };
|
||||
|
||||
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||
bool IsOutgoing () const { return m_IsOutgoing; };
|
||||
|
||||
int GetTerminationTimeout () const { return m_TerminationTimeout; };
|
||||
void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; };
|
||||
bool IsTerminationTimeoutExpired (uint64_t ts) const
|
||||
{ return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); };
|
||||
|
||||
virtual void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
|
||||
std::shared_ptr<i2p::crypto::DHKeys> m_DHKeysPair; // X - for client and Y - for server
|
||||
size_t m_NumSentBytes, m_NumReceivedBytes;
|
||||
bool m_IsOutgoing;
|
||||
int m_TerminationTimeout;
|
||||
uint64_t m_LastActivityTimestamp;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
807
libi2pd/Transports.cpp
Normal file
807
libi2pd/Transports.cpp
Normal file
|
@ -0,0 +1,807 @@
|
|||
#include "Log.h"
|
||||
#include "Crypto.h"
|
||||
#include "RouterContext.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "NetDb.h"
|
||||
#include "Transports.h"
|
||||
#include "Config.h"
|
||||
#ifdef WITH_EVENTS
|
||||
#include "Event.h"
|
||||
#include "util.h"
|
||||
#endif
|
||||
|
||||
using namespace i2p::data;
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
DHKeysPairSupplier::DHKeysPairSupplier (int size):
|
||||
m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
DHKeysPairSupplier::~DHKeysPairSupplier ()
|
||||
{
|
||||
Stop ();
|
||||
}
|
||||
|
||||
void DHKeysPairSupplier::Start ()
|
||||
{
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&DHKeysPairSupplier::Run, this));
|
||||
}
|
||||
|
||||
void DHKeysPairSupplier::Stop ()
|
||||
{
|
||||
m_IsRunning = false;
|
||||
m_Acquired.notify_one ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DHKeysPairSupplier::Run ()
|
||||
{
|
||||
while (m_IsRunning)
|
||||
{
|
||||
int num, total = 0;
|
||||
while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 20)
|
||||
{
|
||||
CreateDHKeysPairs (num);
|
||||
total += num;
|
||||
}
|
||||
if (total >= 20)
|
||||
{
|
||||
LogPrint (eLogWarning, "Transports: ", total, " DH keys generated at the time");
|
||||
std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||
m_Acquired.wait (l); // wait for element gets aquired
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DHKeysPairSupplier::CreateDHKeysPairs (int num)
|
||||
{
|
||||
if (num > 0)
|
||||
{
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
auto pair = std::make_shared<i2p::crypto::DHKeys> ();
|
||||
pair->GenerateKeys ();
|
||||
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||
m_Queue.push (pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::crypto::DHKeys> DHKeysPairSupplier::Acquire ()
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_AcquiredMutex);
|
||||
if (!m_Queue.empty ())
|
||||
{
|
||||
auto pair = m_Queue.front ();
|
||||
m_Queue.pop ();
|
||||
m_Acquired.notify_one ();
|
||||
return pair;
|
||||
}
|
||||
}
|
||||
// queue is empty, create new
|
||||
auto pair = std::make_shared<i2p::crypto::DHKeys> ();
|
||||
pair->GenerateKeys ();
|
||||
return pair;
|
||||
}
|
||||
|
||||
void DHKeysPairSupplier::Return (std::shared_ptr<i2p::crypto::DHKeys> pair)
|
||||
{
|
||||
std::unique_lock<std::mutex>l(m_AcquiredMutex);
|
||||
if ((int)m_Queue.size () < 2*m_QueueSize)
|
||||
m_Queue.push (pair);
|
||||
}
|
||||
|
||||
Transports transports;
|
||||
|
||||
Transports::Transports ():
|
||||
m_IsOnline (true), m_IsRunning (false), m_Thread (nullptr), m_Service (nullptr),
|
||||
m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr),
|
||||
m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys
|
||||
m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0),
|
||||
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0)
|
||||
{
|
||||
}
|
||||
|
||||
Transports::~Transports ()
|
||||
{
|
||||
Stop ();
|
||||
if (m_Service)
|
||||
{
|
||||
delete m_PeerCleanupTimer; m_PeerCleanupTimer = nullptr;
|
||||
delete m_PeerTestTimer; m_PeerTestTimer = nullptr;
|
||||
delete m_Work; m_Work = nullptr;
|
||||
delete m_Service; m_Service = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::Start (bool enableNTCP, bool enableSSU)
|
||||
{
|
||||
if (!m_Service)
|
||||
{
|
||||
m_Service = new boost::asio::io_service ();
|
||||
m_Work = new boost::asio::io_service::work (*m_Service);
|
||||
m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service);
|
||||
m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service);
|
||||
}
|
||||
|
||||
m_DHKeysPairSupplier.Start ();
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&Transports::Run, this));
|
||||
// create acceptors
|
||||
auto& addresses = context.GetRouterInfo ().GetAddresses ();
|
||||
for (const auto& address : addresses)
|
||||
{
|
||||
if (!address) continue;
|
||||
if (m_NTCPServer == nullptr && enableNTCP)
|
||||
{
|
||||
m_NTCPServer = new NTCPServer ();
|
||||
m_NTCPServer->Start ();
|
||||
if (!(m_NTCPServer->IsBoundV6() || m_NTCPServer->IsBoundV4())) {
|
||||
/** failed to bind to NTCP */
|
||||
LogPrint(eLogError, "Transports: failed to bind to TCP");
|
||||
m_NTCPServer->Stop();
|
||||
delete m_NTCPServer;
|
||||
m_NTCPServer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (address->transportStyle == RouterInfo::eTransportSSU)
|
||||
{
|
||||
if (m_SSUServer == nullptr && enableSSU)
|
||||
{
|
||||
if (address->host.is_v4())
|
||||
m_SSUServer = new SSUServer (address->port);
|
||||
else
|
||||
m_SSUServer = new SSUServer (address->host, address->port);
|
||||
LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port);
|
||||
try {
|
||||
m_SSUServer->Start ();
|
||||
} catch ( std::exception & ex ) {
|
||||
LogPrint(eLogError, "Transports: Failed to bind to UDP port", address->port);
|
||||
delete m_SSUServer;
|
||||
m_SSUServer = nullptr;
|
||||
continue;
|
||||
}
|
||||
DetectExternalIP ();
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Transports: SSU server already exists");
|
||||
}
|
||||
}
|
||||
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
|
||||
m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
|
||||
m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
|
||||
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void Transports::Stop ()
|
||||
{
|
||||
if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel ();
|
||||
if (m_PeerTestTimer) m_PeerTestTimer->cancel ();
|
||||
m_Peers.clear ();
|
||||
if (m_SSUServer)
|
||||
{
|
||||
m_SSUServer->Stop ();
|
||||
delete m_SSUServer;
|
||||
m_SSUServer = nullptr;
|
||||
}
|
||||
if (m_NTCPServer)
|
||||
{
|
||||
m_NTCPServer->Stop ();
|
||||
delete m_NTCPServer;
|
||||
m_NTCPServer = nullptr;
|
||||
}
|
||||
|
||||
m_DHKeysPairSupplier.Stop ();
|
||||
m_IsRunning = false;
|
||||
if (m_Service) m_Service->stop ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::Run ()
|
||||
{
|
||||
while (m_IsRunning && m_Service)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Service->run ();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "Transports: runtime exception: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::UpdateBandwidth ()
|
||||
{
|
||||
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
if (m_LastBandwidthUpdateTime > 0)
|
||||
{
|
||||
auto delta = ts - m_LastBandwidthUpdateTime;
|
||||
if (delta > 0)
|
||||
{
|
||||
m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second
|
||||
m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second
|
||||
}
|
||||
}
|
||||
m_LastBandwidthUpdateTime = ts;
|
||||
m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
|
||||
m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
|
||||
}
|
||||
|
||||
bool Transports::IsBandwidthExceeded () const
|
||||
{
|
||||
auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes
|
||||
auto bw = std::max (m_InBandwidth, m_OutBandwidth);
|
||||
return bw > limit;
|
||||
}
|
||||
|
||||
void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
|
||||
{
|
||||
SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });
|
||||
}
|
||||
|
||||
void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
QueueIntEvent("transport.send", ident.ToBase64(), msgs.size());
|
||||
#endif
|
||||
m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs));
|
||||
}
|
||||
|
||||
void Transports::PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs)
|
||||
{
|
||||
if (ident == i2p::context.GetRouterInfo ().GetIdentHash ())
|
||||
{
|
||||
// we send it to ourself
|
||||
for (auto& it: msgs)
|
||||
m_LoopbackHandler.PutNextMessage (it);
|
||||
m_LoopbackHandler.Flush ();
|
||||
return;
|
||||
}
|
||||
if(RoutesRestricted() && ! IsRestrictedPeer(ident)) return;
|
||||
auto it = m_Peers.find (ident);
|
||||
if (it == m_Peers.end ())
|
||||
{
|
||||
bool connected = false;
|
||||
try
|
||||
{
|
||||
auto r = netdb.FindRouter (ident);
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, { 0, r, {},
|
||||
i2p::util::GetSecondsSinceEpoch (), {} })).first;
|
||||
}
|
||||
connected = ConnectToPeer (ident, it->second);
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ());
|
||||
}
|
||||
if (!connected) return;
|
||||
}
|
||||
if (!it->second.sessions.empty ())
|
||||
it->second.sessions.front ()->SendI2NPMessages (msgs);
|
||||
else
|
||||
{
|
||||
if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES)
|
||||
{
|
||||
for (auto& it1: msgs)
|
||||
it->second.delayedMessages.push_back (it1);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Transports: delayed messages queue size exceeds ", MAX_NUM_DELAYED_MESSAGES);
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
m_Peers.erase (it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer)
|
||||
{
|
||||
if (peer.router) // we have RI already
|
||||
{
|
||||
if (!peer.numAttempts) // NTCP
|
||||
{
|
||||
peer.numAttempts++;
|
||||
auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ());
|
||||
if (address && m_NTCPServer)
|
||||
{
|
||||
#if BOOST_VERSION >= 104900
|
||||
if (!address->host.is_unspecified ()) // we have address now
|
||||
#else
|
||||
boost::system::error_code ecode;
|
||||
address->host.to_string (ecode);
|
||||
if (!ecode)
|
||||
#endif
|
||||
{
|
||||
if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ())
|
||||
{
|
||||
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
|
||||
m_NTCPServer->Connect (address->host, address->port, s);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // we don't have address
|
||||
{
|
||||
if (address->addressString.length () > 0) // trying to resolve
|
||||
{
|
||||
LogPrint (eLogDebug, "Transports: Resolving NTCP ", address->addressString);
|
||||
NTCPResolve (address->addressString, ident);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogDebug, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU");
|
||||
}
|
||||
if (peer.numAttempts == 1)// SSU
|
||||
{
|
||||
peer.numAttempts++;
|
||||
if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ()))
|
||||
{
|
||||
auto address = peer.router->GetSSUAddress (!context.SupportsV6 ());
|
||||
#if BOOST_VERSION >= 104900
|
||||
if (!address->host.is_unspecified ()) // we have address now
|
||||
#else
|
||||
boost::system::error_code ecode;
|
||||
address->host.to_string (ecode);
|
||||
if (!ecode)
|
||||
#endif
|
||||
{
|
||||
m_SSUServer->CreateSession (peer.router, address->host, address->port);
|
||||
return true;
|
||||
}
|
||||
else // we don't have address
|
||||
{
|
||||
if (address->addressString.length () > 0) // trying to resolve
|
||||
{
|
||||
LogPrint (eLogDebug, "Transports: Resolving SSU ", address->addressString);
|
||||
SSUResolve (address->addressString, ident);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LogPrint (eLogInfo, "Transports: No NTCP or SSU addresses available");
|
||||
peer.Done ();
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
m_Peers.erase (ident);
|
||||
return false;
|
||||
}
|
||||
else // otherwise request RI
|
||||
{
|
||||
LogPrint (eLogInfo, "Transports: RouterInfo for ", ident.ToBase64 (), " not found, requested");
|
||||
i2p::data::netdb.RequestDestination (ident, std::bind (
|
||||
&Transports::RequestComplete, this, std::placeholders::_1, ident));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Transports::RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
|
||||
{
|
||||
m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident));
|
||||
}
|
||||
|
||||
void Transports::HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident)
|
||||
{
|
||||
auto it = m_Peers.find (ident);
|
||||
if (it != m_Peers.end ())
|
||||
{
|
||||
if (r)
|
||||
{
|
||||
LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, Trying to connect");
|
||||
it->second.router = r;
|
||||
ConnectToPeer (ident, it->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "Transports: RouterInfo not found, Failed to send messages");
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
m_Peers.erase (it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident)
|
||||
{
|
||||
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(*m_Service);
|
||||
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""),
|
||||
std::bind (&Transports::HandleNTCPResolve, this,
|
||||
std::placeholders::_1, std::placeholders::_2, ident, resolver));
|
||||
}
|
||||
|
||||
void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
|
||||
{
|
||||
auto it1 = m_Peers.find (ident);
|
||||
if (it1 != m_Peers.end ())
|
||||
{
|
||||
auto& peer = it1->second;
|
||||
if (!ecode && peer.router)
|
||||
{
|
||||
while (it != boost::asio::ip::tcp::resolver::iterator())
|
||||
{
|
||||
auto address = (*it).endpoint ().address ();
|
||||
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
|
||||
if (address.is_v4 () || context.SupportsV6 ())
|
||||
{
|
||||
auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested
|
||||
if (addr)
|
||||
{
|
||||
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
|
||||
m_NTCPServer->Connect (address, addr->port, s);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported");
|
||||
it++;
|
||||
}
|
||||
}
|
||||
LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ());
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
m_Peers.erase (it1);
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident)
|
||||
{
|
||||
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(*m_Service);
|
||||
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""),
|
||||
std::bind (&Transports::HandleSSUResolve, this,
|
||||
std::placeholders::_1, std::placeholders::_2, ident, resolver));
|
||||
}
|
||||
|
||||
void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
|
||||
{
|
||||
auto it1 = m_Peers.find (ident);
|
||||
if (it1 != m_Peers.end ())
|
||||
{
|
||||
auto& peer = it1->second;
|
||||
if (!ecode && peer.router)
|
||||
{
|
||||
while (it != boost::asio::ip::tcp::resolver::iterator())
|
||||
{
|
||||
auto address = (*it).endpoint ().address ();
|
||||
LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address);
|
||||
if (address.is_v4 () || context.SupportsV6 ())
|
||||
{
|
||||
auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested
|
||||
if (addr)
|
||||
{
|
||||
m_SSUServer->CreateSession (peer.router, address, addr->port);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported");
|
||||
it++;
|
||||
}
|
||||
}
|
||||
LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ());
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
m_Peers.erase (it1);
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
|
||||
{
|
||||
if (!router) return;
|
||||
m_Service->post (std::bind (&Transports::PostCloseSession, this, router));
|
||||
}
|
||||
|
||||
void Transports::PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
|
||||
{
|
||||
auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (router) : nullptr;
|
||||
if (ssuSession) // try SSU first
|
||||
{
|
||||
m_SSUServer->DeleteSession (ssuSession);
|
||||
LogPrint (eLogDebug, "Transports: SSU session closed");
|
||||
}
|
||||
auto ntcpSession = m_NTCPServer ? m_NTCPServer->FindNTCPSession(router->GetIdentHash()) : nullptr;
|
||||
if (ntcpSession) // try deleting ntcp session too
|
||||
{
|
||||
ntcpSession->Terminate ();
|
||||
LogPrint(eLogDebug, "Transports: NTCP session closed");
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::DetectExternalIP ()
|
||||
{
|
||||
if (RoutesRestricted())
|
||||
{
|
||||
LogPrint(eLogInfo, "Transports: restricted routes enabled, not detecting ip");
|
||||
i2p::context.SetStatus (eRouterStatusOK);
|
||||
return;
|
||||
}
|
||||
if (m_SSUServer)
|
||||
{
|
||||
bool nat; i2p::config::GetOption("nat", nat);
|
||||
bool isv4 = i2p::context.SupportsV4 ();
|
||||
if (nat && isv4)
|
||||
i2p::context.SetStatus (eRouterStatusTesting);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
auto router = i2p::data::netdb.GetRandomPeerTestRouter (isv4); // v4 only if v4
|
||||
if (router)
|
||||
m_SSUServer->CreateSession (router, true, isv4); // peer test
|
||||
else
|
||||
{
|
||||
// if not peer test capable routers found pick any
|
||||
router = i2p::data::netdb.GetRandomRouter ();
|
||||
if (router && router->IsSSU ())
|
||||
m_SSUServer->CreateSession (router); // no peer test
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available");
|
||||
}
|
||||
|
||||
void Transports::PeerTest ()
|
||||
{
|
||||
if (RoutesRestricted() || !i2p::context.SupportsV4 ()) return;
|
||||
if (m_SSUServer)
|
||||
{
|
||||
bool statusChanged = false;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
auto router = i2p::data::netdb.GetRandomPeerTestRouter (true); // v4 only
|
||||
if (router)
|
||||
{
|
||||
if (!statusChanged)
|
||||
{
|
||||
statusChanged = true;
|
||||
i2p::context.SetStatus (eRouterStatusTesting); // first time only
|
||||
}
|
||||
m_SSUServer->CreateSession (router, true, true); // peer test v4
|
||||
}
|
||||
}
|
||||
if (!statusChanged)
|
||||
LogPrint (eLogWarning, "Can't find routers for peer test");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::crypto::DHKeys> Transports::GetNextDHKeysPair ()
|
||||
{
|
||||
return m_DHKeysPairSupplier.Acquire ();
|
||||
}
|
||||
|
||||
void Transports::ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair)
|
||||
{
|
||||
m_DHKeysPairSupplier.Return (pair);
|
||||
}
|
||||
|
||||
void Transports::PeerConnected (std::shared_ptr<TransportSession> session)
|
||||
{
|
||||
m_Service->post([session, this]()
|
||||
{
|
||||
auto remoteIdentity = session->GetRemoteIdentity ();
|
||||
if (!remoteIdentity) return;
|
||||
auto ident = remoteIdentity->GetIdentHash ();
|
||||
auto it = m_Peers.find (ident);
|
||||
if (it != m_Peers.end ())
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "false"}});
|
||||
#endif
|
||||
bool sendDatabaseStore = true;
|
||||
if (it->second.delayedMessages.size () > 0)
|
||||
{
|
||||
// check if first message is our DatabaseStore (publishing)
|
||||
auto firstMsg = it->second.delayedMessages[0];
|
||||
if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore &&
|
||||
i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ())
|
||||
sendDatabaseStore = false; // we have it in the list already
|
||||
}
|
||||
if (sendDatabaseStore)
|
||||
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () });
|
||||
else
|
||||
session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds
|
||||
it->second.sessions.push_back (session);
|
||||
session->SendI2NPMessages (it->second.delayedMessages);
|
||||
it->second.delayedMessages.clear ();
|
||||
}
|
||||
else // incoming connection
|
||||
{
|
||||
if(RoutesRestricted() && ! IsRestrictedPeer(ident)) {
|
||||
// not trusted
|
||||
LogPrint(eLogWarning, "Transports: closing untrusted inbound connection from ", ident.ToBase64());
|
||||
session->Done();
|
||||
return;
|
||||
}
|
||||
#ifdef WITH_EVENTS
|
||||
EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "true"}});
|
||||
#endif
|
||||
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Transports::PeerDisconnected (std::shared_ptr<TransportSession> session)
|
||||
{
|
||||
m_Service->post([session, this]()
|
||||
{
|
||||
auto remoteIdentity = session->GetRemoteIdentity ();
|
||||
if (!remoteIdentity) return;
|
||||
auto ident = remoteIdentity->GetIdentHash ();
|
||||
#ifdef WITH_EVENTS
|
||||
EmitEvent({{"type" , "transport.disconnected"}, {"ident", ident.ToBase64()}});
|
||||
#endif
|
||||
auto it = m_Peers.find (ident);
|
||||
if (it != m_Peers.end ())
|
||||
{
|
||||
it->second.sessions.remove (session);
|
||||
if (it->second.sessions.empty ()) // TODO: why?
|
||||
{
|
||||
if (it->second.delayedMessages.size () > 0)
|
||||
ConnectToPeer (ident, it->second);
|
||||
else
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
m_Peers.erase (it);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Transports::IsConnected (const i2p::data::IdentHash& ident) const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
auto it = m_Peers.find (ident);
|
||||
return it != m_Peers.end ();
|
||||
}
|
||||
|
||||
void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = m_Peers.begin (); it != m_Peers.end (); )
|
||||
{
|
||||
if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
|
||||
{
|
||||
LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds");
|
||||
auto profile = i2p::data::GetRouterProfile(it->first);
|
||||
if (profile)
|
||||
{
|
||||
profile->TunnelNonReplied();
|
||||
profile->Save(it->first);
|
||||
}
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
it = m_Peers.erase (it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
UpdateBandwidth (); // TODO: use separate timer(s) for it
|
||||
if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test
|
||||
DetectExternalIP ();
|
||||
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
|
||||
m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
|
||||
void Transports::HandlePeerTestTimer (const boost::system::error_code& ecode)
|
||||
{
|
||||
if (ecode != boost::asio::error::operation_aborted)
|
||||
{
|
||||
PeerTest ();
|
||||
m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
|
||||
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer () const
|
||||
{
|
||||
if (m_Peers.empty ()) return nullptr;
|
||||
std::unique_lock<std::mutex> l(m_PeersMutex);
|
||||
auto it = m_Peers.begin ();
|
||||
std::advance (it, rand () % m_Peers.size ());
|
||||
return it != m_Peers.end () ? it->second.router : nullptr;
|
||||
}
|
||||
void Transports::RestrictRoutesToFamilies(std::set<std::string> families)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_FamilyMutex);
|
||||
m_TrustedFamilies.clear();
|
||||
for ( const auto& fam : families )
|
||||
m_TrustedFamilies.push_back(fam);
|
||||
}
|
||||
|
||||
void Transports::RestrictRoutesToRouters(std::set<i2p::data::IdentHash> routers)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_TrustedRoutersMutex);
|
||||
m_TrustedRouters.clear();
|
||||
for (const auto & ri : routers )
|
||||
m_TrustedRouters.push_back(ri);
|
||||
}
|
||||
|
||||
bool Transports::RoutesRestricted() const {
|
||||
std::unique_lock<std::mutex> famlock(m_FamilyMutex);
|
||||
std::unique_lock<std::mutex> routerslock(m_TrustedRoutersMutex);
|
||||
return m_TrustedFamilies.size() > 0 || m_TrustedRouters.size() > 0;
|
||||
}
|
||||
|
||||
/** XXX: if routes are not restricted this dies */
|
||||
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() const
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> l(m_FamilyMutex);
|
||||
std::string fam;
|
||||
auto sz = m_TrustedFamilies.size();
|
||||
if(sz > 1)
|
||||
{
|
||||
auto it = m_TrustedFamilies.begin ();
|
||||
std::advance(it, rand() % sz);
|
||||
fam = *it;
|
||||
boost::to_lower(fam);
|
||||
}
|
||||
else if (sz == 1)
|
||||
{
|
||||
fam = m_TrustedFamilies[0];
|
||||
}
|
||||
if (fam.size())
|
||||
return i2p::data::netdb.GetRandomRouterInFamily(fam);
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_TrustedRoutersMutex);
|
||||
auto sz = m_TrustedRouters.size();
|
||||
if (sz)
|
||||
{
|
||||
if(sz == 1)
|
||||
return i2p::data::netdb.FindRouter(m_TrustedRouters[0]);
|
||||
auto it = m_TrustedRouters.begin();
|
||||
std::advance(it, rand() % sz);
|
||||
return i2p::data::netdb.FindRouter(*it);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_TrustedRoutersMutex);
|
||||
for (const auto & r : m_TrustedRouters )
|
||||
if ( r == ih ) return true;
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_FamilyMutex);
|
||||
auto ri = i2p::data::netdb.FindRouter(ih);
|
||||
for (const auto & fam : m_TrustedFamilies)
|
||||
if(ri->IsFamily(fam)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
185
libi2pd/Transports.h
Normal file
185
libi2pd/Transports.h
Normal file
|
@ -0,0 +1,185 @@
|
|||
#ifndef TRANSPORTS_H__
|
||||
#define TRANSPORTS_H__
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <boost/asio.hpp>
|
||||
#include "TransportSession.h"
|
||||
#include "NTCPSession.h"
|
||||
#include "SSU.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace transport
|
||||
{
|
||||
class DHKeysPairSupplier
|
||||
{
|
||||
public:
|
||||
|
||||
DHKeysPairSupplier (int size);
|
||||
~DHKeysPairSupplier ();
|
||||
void Start ();
|
||||
void Stop ();
|
||||
std::shared_ptr<i2p::crypto::DHKeys> Acquire ();
|
||||
void Return (std::shared_ptr<i2p::crypto::DHKeys> pair);
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void CreateDHKeysPairs (int num);
|
||||
|
||||
private:
|
||||
|
||||
const int m_QueueSize;
|
||||
std::queue<std::shared_ptr<i2p::crypto::DHKeys> > m_Queue;
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
std::condition_variable m_Acquired;
|
||||
std::mutex m_AcquiredMutex;
|
||||
};
|
||||
|
||||
struct Peer
|
||||
{
|
||||
int numAttempts;
|
||||
std::shared_ptr<const i2p::data::RouterInfo> router;
|
||||
std::list<std::shared_ptr<TransportSession> > sessions;
|
||||
uint64_t creationTime;
|
||||
std::vector<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
|
||||
|
||||
void Done ()
|
||||
{
|
||||
for (auto& it: sessions)
|
||||
it->Done ();
|
||||
}
|
||||
};
|
||||
|
||||
const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds
|
||||
const int PEER_TEST_INTERVAL = 71; // in minutes
|
||||
const int MAX_NUM_DELAYED_MESSAGES = 50;
|
||||
class Transports
|
||||
{
|
||||
public:
|
||||
|
||||
Transports ();
|
||||
~Transports ();
|
||||
|
||||
void Start (bool enableNTCP=true, bool enableSSU=true);
|
||||
void Stop ();
|
||||
|
||||
bool IsBoundNTCP() const { return m_NTCPServer != nullptr; }
|
||||
bool IsBoundSSU() const { return m_SSUServer != nullptr; }
|
||||
|
||||
bool IsOnline() const { return m_IsOnline; };
|
||||
void SetOnline (bool online) { m_IsOnline = online; };
|
||||
|
||||
boost::asio::io_service& GetService () { return *m_Service; };
|
||||
std::shared_ptr<i2p::crypto::DHKeys> GetNextDHKeysPair ();
|
||||
void ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair);
|
||||
|
||||
void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
void SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs);
|
||||
void CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
|
||||
|
||||
void PeerConnected (std::shared_ptr<TransportSession> session);
|
||||
void PeerDisconnected (std::shared_ptr<TransportSession> session);
|
||||
bool IsConnected (const i2p::data::IdentHash& ident) const;
|
||||
|
||||
void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; };
|
||||
void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; };
|
||||
uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; };
|
||||
uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; };
|
||||
uint32_t GetInBandwidth () const { return m_InBandwidth; };
|
||||
uint32_t GetOutBandwidth () const { return m_OutBandwidth; };
|
||||
bool IsBandwidthExceeded () const;
|
||||
size_t GetNumPeers () const { return m_Peers.size (); };
|
||||
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer () const;
|
||||
|
||||
/** get a trusted first hop for restricted routes */
|
||||
std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer() const;
|
||||
/** do we want to use restricted routes? */
|
||||
bool RoutesRestricted() const;
|
||||
/** restrict routes to use only these router families for first hops */
|
||||
void RestrictRoutesToFamilies(std::set<std::string> families);
|
||||
/** restrict routes to use only these routers for first hops */
|
||||
void RestrictRoutesToRouters(std::set<i2p::data::IdentHash> routers);
|
||||
|
||||
bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const;
|
||||
|
||||
void PeerTest ();
|
||||
|
||||
private:
|
||||
|
||||
void Run ();
|
||||
void RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident);
|
||||
void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident);
|
||||
void PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs);
|
||||
void PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router);
|
||||
bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer);
|
||||
void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
|
||||
void HandlePeerTestTimer (const boost::system::error_code& ecode);
|
||||
|
||||
void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident);
|
||||
void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
|
||||
void SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident);
|
||||
void HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
|
||||
i2p::data::IdentHash ident, std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
|
||||
|
||||
void UpdateBandwidth ();
|
||||
void DetectExternalIP ();
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsOnline, m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
boost::asio::io_service * m_Service;
|
||||
boost::asio::io_service::work * m_Work;
|
||||
boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer;
|
||||
|
||||
NTCPServer * m_NTCPServer;
|
||||
SSUServer * m_SSUServer;
|
||||
mutable std::mutex m_PeersMutex;
|
||||
std::map<i2p::data::IdentHash, Peer> m_Peers;
|
||||
|
||||
DHKeysPairSupplier m_DHKeysPairSupplier;
|
||||
|
||||
std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes;
|
||||
uint32_t m_InBandwidth, m_OutBandwidth; // bytes per second
|
||||
uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes;
|
||||
uint64_t m_LastBandwidthUpdateTime;
|
||||
|
||||
/** which router families to trust for first hops */
|
||||
std::vector<std::string> m_TrustedFamilies;
|
||||
mutable std::mutex m_FamilyMutex;
|
||||
|
||||
/** which routers for first hop to trust */
|
||||
std::vector<i2p::data::IdentHash> m_TrustedRouters;
|
||||
mutable std::mutex m_TrustedRoutersMutex;
|
||||
|
||||
i2p::I2NPMessagesHandler m_LoopbackHandler;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
const NTCPServer * GetNTCPServer () const { return m_NTCPServer; };
|
||||
const SSUServer * GetSSUServer () const { return m_SSUServer; };
|
||||
const decltype(m_Peers)& GetPeers () const { return m_Peers; };
|
||||
};
|
||||
|
||||
extern Transports transports;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
940
libi2pd/Tunnel.cpp
Normal file
940
libi2pd/Tunnel.cpp
Normal file
|
@ -0,0 +1,940 @@
|
|||
#include <string.h>
|
||||
#include "I2PEndian.h"
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "Crypto.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Log.h"
|
||||
#include "Timestamp.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Transports.h"
|
||||
#include "NetDb.h"
|
||||
#include "Config.h"
|
||||
#include "Tunnel.h"
|
||||
#include "TunnelPool.h"
|
||||
#ifdef WITH_EVENTS
|
||||
#include "Event.h"
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
Tunnel::Tunnel (std::shared_ptr<const TunnelConfig> config):
|
||||
TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()),
|
||||
m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false),
|
||||
m_Latency (0)
|
||||
{
|
||||
}
|
||||
|
||||
Tunnel::~Tunnel ()
|
||||
{
|
||||
}
|
||||
|
||||
void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
std::string peers = i2p::context.GetIdentity()->GetIdentHash().ToBase64();
|
||||
#endif
|
||||
auto numHops = m_Config->GetNumHops ();
|
||||
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops;
|
||||
auto msg = NewI2NPShortMessage ();
|
||||
*msg->GetPayload () = numRecords;
|
||||
msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1;
|
||||
// shuffle records
|
||||
std::vector<int> recordIndicies;
|
||||
for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i);
|
||||
std::random_shuffle (recordIndicies.begin(), recordIndicies.end());
|
||||
|
||||
// create real records
|
||||
uint8_t * records = msg->GetPayload () + 1;
|
||||
TunnelHopConfig * hop = m_Config->GetFirstHop ();
|
||||
int i = 0;
|
||||
BN_CTX * ctx = BN_CTX_new ();
|
||||
while (hop)
|
||||
{
|
||||
uint32_t msgID;
|
||||
if (hop->next) // we set replyMsgID for last hop only
|
||||
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||
else
|
||||
msgID = replyMsgID;
|
||||
int idx = recordIndicies[i];
|
||||
hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx);
|
||||
hop->recordIndex = idx;
|
||||
i++;
|
||||
#ifdef WITH_EVENTS
|
||||
peers += ":" + hop->ident->GetIdentHash().ToBase64();
|
||||
#endif
|
||||
hop = hop->next;
|
||||
}
|
||||
BN_CTX_free (ctx);
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnel.build", this, peers);
|
||||
#endif
|
||||
// fill up fake records with random data
|
||||
for (int i = numHops; i < numRecords; i++)
|
||||
{
|
||||
int idx = recordIndicies[i];
|
||||
RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE);
|
||||
}
|
||||
|
||||
// decrypt real records
|
||||
i2p::crypto::CBCDecryption decryption;
|
||||
hop = m_Config->GetLastHop ()->prev;
|
||||
while (hop)
|
||||
{
|
||||
decryption.SetKey (hop->replyKey);
|
||||
// decrypt records after current hop
|
||||
TunnelHopConfig * hop1 = hop->next;
|
||||
while (hop1)
|
||||
{
|
||||
decryption.SetIV (hop->replyIV);
|
||||
uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
|
||||
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
|
||||
hop1 = hop1->next;
|
||||
}
|
||||
hop = hop->prev;
|
||||
}
|
||||
msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild);
|
||||
|
||||
// send message
|
||||
if (outboundTunnel)
|
||||
outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg);
|
||||
else
|
||||
i2p::transport::transports.SendMessage (GetNextIdentHash (), msg);
|
||||
}
|
||||
|
||||
bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records.");
|
||||
|
||||
i2p::crypto::CBCDecryption decryption;
|
||||
TunnelHopConfig * hop = m_Config->GetLastHop ();
|
||||
while (hop)
|
||||
{
|
||||
decryption.SetKey (hop->replyKey);
|
||||
// decrypt records before and including current hop
|
||||
TunnelHopConfig * hop1 = hop;
|
||||
while (hop1)
|
||||
{
|
||||
auto idx = hop1->recordIndex;
|
||||
if (idx >= 0 && idx < msg[0])
|
||||
{
|
||||
uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE;
|
||||
decryption.SetIV (hop->replyIV);
|
||||
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range");
|
||||
hop1 = hop1->prev;
|
||||
}
|
||||
hop = hop->prev;
|
||||
}
|
||||
|
||||
bool established = true;
|
||||
hop = m_Config->GetFirstHop ();
|
||||
while (hop)
|
||||
{
|
||||
const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE;
|
||||
uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET];
|
||||
LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret);
|
||||
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
|
||||
if (profile)
|
||||
profile->TunnelBuildResponse (ret);
|
||||
if (ret)
|
||||
// if any of participants declined the tunnel is not established
|
||||
established = false;
|
||||
hop = hop->next;
|
||||
}
|
||||
if (established)
|
||||
{
|
||||
// create tunnel decryptions from layer and iv keys in reverse order
|
||||
hop = m_Config->GetLastHop ();
|
||||
while (hop)
|
||||
{
|
||||
auto tunnelHop = new TunnelHop;
|
||||
tunnelHop->ident = hop->ident;
|
||||
tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey);
|
||||
m_Hops.push_back (std::unique_ptr<TunnelHop>(tunnelHop));
|
||||
hop = hop->prev;
|
||||
}
|
||||
m_Config = nullptr;
|
||||
}
|
||||
if (established) m_State = eTunnelStateEstablished;
|
||||
return established;
|
||||
}
|
||||
|
||||
bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const
|
||||
{
|
||||
auto latency = GetMeanLatency();
|
||||
return latency >= lower && latency <= upper;
|
||||
}
|
||||
|
||||
void Tunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
|
||||
{
|
||||
const uint8_t * inPayload = in->GetPayload () + 4;
|
||||
uint8_t * outPayload = out->GetPayload () + 4;
|
||||
for (auto& it: m_Hops)
|
||||
{
|
||||
it->decryption.Decrypt (inPayload, outPayload);
|
||||
inPayload = outPayload;
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||
{
|
||||
LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions");
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > Tunnel::GetPeers () const
|
||||
{
|
||||
auto peers = GetInvertedPeers ();
|
||||
std::reverse (peers.begin (), peers.end ());
|
||||
return peers;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > Tunnel::GetInvertedPeers () const
|
||||
{
|
||||
// hops are in inverted order
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > ret;
|
||||
for (auto& it: m_Hops)
|
||||
ret.push_back (it->ident);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Tunnel::SetState(TunnelState state)
|
||||
{
|
||||
m_State = state;
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnel.state", this, state);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void Tunnel::PrintHops (std::stringstream& s) const
|
||||
{
|
||||
// hops are in inverted order, we must print in direct order
|
||||
for (auto it = m_Hops.rbegin (); it != m_Hops.rend (); it++)
|
||||
{
|
||||
s << " ⇒ ";
|
||||
s << i2p::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ());
|
||||
}
|
||||
}
|
||||
|
||||
void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg)
|
||||
{
|
||||
if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
|
||||
auto newMsg = CreateEmptyTunnelDataMsg ();
|
||||
EncryptTunnelMsg (msg, newMsg);
|
||||
newMsg->from = shared_from_this ();
|
||||
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
|
||||
}
|
||||
|
||||
void InboundTunnel::Print (std::stringstream& s) const
|
||||
{
|
||||
PrintHops (s);
|
||||
s << " ⇒ " << GetTunnelID () << ":me";
|
||||
}
|
||||
|
||||
ZeroHopsInboundTunnel::ZeroHopsInboundTunnel ():
|
||||
InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
|
||||
m_NumReceivedBytes (0)
|
||||
{
|
||||
}
|
||||
|
||||
void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
m_NumReceivedBytes += msg->GetLength ();
|
||||
msg->from = shared_from_this ();
|
||||
HandleI2NPMessage (msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ZeroHopsInboundTunnel::Print (std::stringstream& s) const
|
||||
{
|
||||
s << " ⇒ " << GetTunnelID () << ":me";
|
||||
}
|
||||
|
||||
void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg)
|
||||
{
|
||||
TunnelMessageBlock block;
|
||||
if (gwHash)
|
||||
{
|
||||
block.hash = gwHash;
|
||||
if (gwTunnel)
|
||||
{
|
||||
block.deliveryType = eDeliveryTypeTunnel;
|
||||
block.tunnelID = gwTunnel;
|
||||
}
|
||||
else
|
||||
block.deliveryType = eDeliveryTypeRouter;
|
||||
}
|
||||
else
|
||||
block.deliveryType = eDeliveryTypeLocal;
|
||||
block.data = msg;
|
||||
|
||||
SendTunnelDataMsg ({block});
|
||||
}
|
||||
|
||||
void OutboundTunnel::SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_SendMutex);
|
||||
for (auto& it : msgs)
|
||||
m_Gateway.PutTunnelDataMsg (it);
|
||||
m_Gateway.SendBuffer ();
|
||||
}
|
||||
|
||||
void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg)
|
||||
{
|
||||
LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ());
|
||||
}
|
||||
|
||||
void OutboundTunnel::Print (std::stringstream& s) const
|
||||
{
|
||||
s << GetTunnelID () << ":me";
|
||||
PrintHops (s);
|
||||
s << " ⇒ ";
|
||||
}
|
||||
|
||||
ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel ():
|
||||
OutboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
|
||||
m_NumSentBytes (0)
|
||||
{
|
||||
}
|
||||
|
||||
void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs)
|
||||
{
|
||||
for (auto& msg : msgs)
|
||||
{
|
||||
switch (msg.deliveryType)
|
||||
{
|
||||
case eDeliveryTypeLocal:
|
||||
i2p::HandleI2NPMessage (msg.data);
|
||||
break;
|
||||
case eDeliveryTypeTunnel:
|
||||
i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
|
||||
break;
|
||||
case eDeliveryTypeRouter:
|
||||
i2p::transport::transports.SendMessage (msg.hash, msg.data);
|
||||
break;
|
||||
default:
|
||||
LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const
|
||||
{
|
||||
s << GetTunnelID () << ":me ⇒ ";
|
||||
}
|
||||
|
||||
Tunnels tunnels;
|
||||
|
||||
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr),
|
||||
m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0)
|
||||
{
|
||||
}
|
||||
|
||||
Tunnels::~Tunnels ()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<TunnelBase> Tunnels::GetTunnel (uint32_t tunnelID)
|
||||
{
|
||||
auto it = m_Tunnels.find(tunnelID);
|
||||
if (it != m_Tunnels.end ())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<InboundTunnel> Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID)
|
||||
{
|
||||
return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels);
|
||||
}
|
||||
|
||||
std::shared_ptr<OutboundTunnel> Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID)
|
||||
{
|
||||
return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels);
|
||||
}
|
||||
|
||||
template<class TTunnel>
|
||||
std::shared_ptr<TTunnel> Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels)
|
||||
{
|
||||
auto it = pendingTunnels.find(replyMsgID);
|
||||
if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending)
|
||||
{
|
||||
it->second->SetState (eTunnelStateBuildReplyReceived);
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<InboundTunnel> Tunnels::GetNextInboundTunnel ()
|
||||
{
|
||||
std::shared_ptr<InboundTunnel> tunnel;
|
||||
size_t minReceived = 0;
|
||||
for (const auto& it : m_InboundTunnels)
|
||||
{
|
||||
if (!it->IsEstablished ()) continue;
|
||||
if (!tunnel || it->GetNumReceivedBytes () < minReceived)
|
||||
{
|
||||
tunnel = it;
|
||||
minReceived = it->GetNumReceivedBytes ();
|
||||
}
|
||||
}
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel ()
|
||||
{
|
||||
if (m_OutboundTunnels.empty ()) return nullptr;
|
||||
uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0;
|
||||
std::shared_ptr<OutboundTunnel> tunnel;
|
||||
for (const auto& it: m_OutboundTunnels)
|
||||
{
|
||||
if (it->IsEstablished ())
|
||||
{
|
||||
tunnel = it;
|
||||
i++;
|
||||
}
|
||||
if (i > ind && tunnel) break;
|
||||
}
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (int numInboundHops,
|
||||
int numOutboundHops, int numInboundTunnels, int numOutboundTunnels)
|
||||
{
|
||||
auto pool = std::make_shared<TunnelPool> (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels);
|
||||
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||
m_Pools.push_back (pool);
|
||||
return pool;
|
||||
}
|
||||
|
||||
void Tunnels::DeleteTunnelPool (std::shared_ptr<TunnelPool> pool)
|
||||
{
|
||||
if (pool)
|
||||
{
|
||||
StopTunnelPool (pool);
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||
m_Pools.remove (pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::StopTunnelPool (std::shared_ptr<TunnelPool> pool)
|
||||
{
|
||||
if (pool)
|
||||
{
|
||||
pool->SetActive (false);
|
||||
pool->DetachTunnels ();
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
|
||||
{
|
||||
if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second)
|
||||
m_TransitTunnels.push_back (tunnel);
|
||||
else
|
||||
LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists");
|
||||
}
|
||||
|
||||
void Tunnels::Start ()
|
||||
{
|
||||
m_IsRunning = true;
|
||||
m_Thread = new std::thread (std::bind (&Tunnels::Run, this));
|
||||
}
|
||||
|
||||
void Tunnels::Stop ()
|
||||
{
|
||||
m_IsRunning = false;
|
||||
m_Queue.WakeUp ();
|
||||
if (m_Thread)
|
||||
{
|
||||
m_Thread->join ();
|
||||
delete m_Thread;
|
||||
m_Thread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::Run ()
|
||||
{
|
||||
std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready
|
||||
|
||||
uint64_t lastTs = 0;
|
||||
while (m_IsRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
|
||||
if (msg)
|
||||
{
|
||||
uint32_t prevTunnelID = 0, tunnelID = 0;
|
||||
std::shared_ptr<TunnelBase> prevTunnel;
|
||||
do
|
||||
{
|
||||
std::shared_ptr<TunnelBase> tunnel;
|
||||
uint8_t typeID = msg->GetTypeID ();
|
||||
switch (typeID)
|
||||
{
|
||||
case eI2NPTunnelData:
|
||||
case eI2NPTunnelGateway:
|
||||
{
|
||||
tunnelID = bufbe32toh (msg->GetPayload ());
|
||||
if (tunnelID == prevTunnelID)
|
||||
tunnel = prevTunnel;
|
||||
else if (prevTunnel)
|
||||
prevTunnel->FlushTunnelDataMsgs ();
|
||||
|
||||
if (!tunnel)
|
||||
tunnel = GetTunnel (tunnelID);
|
||||
if (tunnel)
|
||||
{
|
||||
if (typeID == eI2NPTunnelData)
|
||||
tunnel->HandleTunnelDataMsg (msg);
|
||||
else // tunnel gateway assumed
|
||||
HandleTunnelGatewayMsg (tunnel, msg);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogWarning, "Tunnel: tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID);
|
||||
|
||||
break;
|
||||
}
|
||||
case eI2NPVariableTunnelBuild:
|
||||
case eI2NPVariableTunnelBuildReply:
|
||||
case eI2NPTunnelBuild:
|
||||
case eI2NPTunnelBuildReply:
|
||||
HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ());
|
||||
break;
|
||||
default:
|
||||
LogPrint (eLogWarning, "Tunnel: unexpected messsage type ", (int) typeID);
|
||||
}
|
||||
|
||||
msg = m_Queue.Get ();
|
||||
if (msg)
|
||||
{
|
||||
prevTunnelID = tunnelID;
|
||||
prevTunnel = tunnel;
|
||||
}
|
||||
else if (tunnel)
|
||||
tunnel->FlushTunnelDataMsgs ();
|
||||
}
|
||||
while (msg);
|
||||
}
|
||||
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
if (ts - lastTs >= 15) // manage tunnels every 15 seconds
|
||||
{
|
||||
ManageTunnels ();
|
||||
lastTs = ts;
|
||||
}
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (!tunnel)
|
||||
{
|
||||
LogPrint (eLogError, "Tunnel: missing tunnel for gateway");
|
||||
return;
|
||||
}
|
||||
const uint8_t * payload = msg->GetPayload ();
|
||||
uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET);
|
||||
// we make payload as new I2NP message to send
|
||||
msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
|
||||
if (msg->offset + len > msg->len)
|
||||
{
|
||||
LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len);
|
||||
return;
|
||||
}
|
||||
msg->len = msg->offset + len;
|
||||
auto typeID = msg->GetTypeID ();
|
||||
LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID);
|
||||
|
||||
if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply)
|
||||
// transit DatabaseStore my contain new/updated RI
|
||||
// or DatabaseSearchReply with new routers
|
||||
i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg));
|
||||
tunnel->SendTunnelDataMsg (msg);
|
||||
}
|
||||
|
||||
void Tunnels::ManageTunnels ()
|
||||
{
|
||||
ManagePendingTunnels ();
|
||||
ManageInboundTunnels ();
|
||||
ManageOutboundTunnels ();
|
||||
ManageTransitTunnels ();
|
||||
ManageTunnelPools ();
|
||||
}
|
||||
|
||||
void Tunnels::ManagePendingTunnels ()
|
||||
{
|
||||
ManagePendingTunnels (m_PendingInboundTunnels);
|
||||
ManagePendingTunnels (m_PendingOutboundTunnels);
|
||||
}
|
||||
|
||||
template<class PendingTunnels>
|
||||
void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels)
|
||||
{
|
||||
// check pending tunnel. delete failed or timeout
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();)
|
||||
{
|
||||
auto tunnel = it->second;
|
||||
auto pool = tunnel->GetTunnelPool();
|
||||
switch (tunnel->GetState ())
|
||||
{
|
||||
case eTunnelStatePending:
|
||||
if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted");
|
||||
// update stats
|
||||
auto config = tunnel->GetTunnelConfig ();
|
||||
if (config)
|
||||
{
|
||||
auto hop = config->GetFirstHop ();
|
||||
while (hop)
|
||||
{
|
||||
if (hop->ident)
|
||||
{
|
||||
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
|
||||
if (profile)
|
||||
profile->TunnelNonReplied ();
|
||||
}
|
||||
hop = hop->next;
|
||||
}
|
||||
}
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed);
|
||||
#endif
|
||||
// for i2lua
|
||||
if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultTimeout);
|
||||
// delete
|
||||
it = pendingTunnels.erase (it);
|
||||
m_NumFailedTunnelCreations++;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
break;
|
||||
case eTunnelStateBuildFailed:
|
||||
LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted");
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed);
|
||||
#endif
|
||||
// for i2lua
|
||||
if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultRejected);
|
||||
|
||||
it = pendingTunnels.erase (it);
|
||||
m_NumFailedTunnelCreations++;
|
||||
break;
|
||||
case eTunnelStateBuildReplyReceived:
|
||||
// intermediate state, will be either established of build failed
|
||||
++it;
|
||||
break;
|
||||
default:
|
||||
// success
|
||||
it = pendingTunnels.erase (it);
|
||||
m_NumSuccesiveTunnelCreations++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageOutboundTunnels ()
|
||||
{
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
{
|
||||
for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
|
||||
{
|
||||
auto tunnel = *it;
|
||||
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired");
|
||||
auto pool = tunnel->GetTunnelPool ();
|
||||
if (pool)
|
||||
pool->TunnelExpired (tunnel);
|
||||
// we don't have outbound tunnels in m_Tunnels
|
||||
it = m_OutboundTunnels.erase (it);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tunnel->IsEstablished ())
|
||||
{
|
||||
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
tunnel->SetIsRecreated ();
|
||||
auto pool = tunnel->GetTunnelPool ();
|
||||
if (pool)
|
||||
pool->RecreateOutboundTunnel (tunnel);
|
||||
}
|
||||
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
tunnel->SetState (eTunnelStateExpiring);
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_OutboundTunnels.size () < 3)
|
||||
{
|
||||
// trying to create one more oubound tunnel
|
||||
auto inboundTunnel = GetNextInboundTunnel ();
|
||||
auto router = i2p::transport::transports.RoutesRestricted() ?
|
||||
i2p::transport::transports.GetRestrictedPeer() :
|
||||
i2p::data::netdb.GetRandomRouter ();
|
||||
if (!inboundTunnel || !router) return;
|
||||
LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel");
|
||||
CreateTunnel<OutboundTunnel> (
|
||||
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () },
|
||||
inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageInboundTunnels ()
|
||||
{
|
||||
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
{
|
||||
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
|
||||
{
|
||||
auto tunnel = *it;
|
||||
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired");
|
||||
auto pool = tunnel->GetTunnelPool ();
|
||||
if (pool)
|
||||
pool->TunnelExpired (tunnel);
|
||||
m_Tunnels.erase (tunnel->GetTunnelID ());
|
||||
it = m_InboundTunnels.erase (it);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tunnel->IsEstablished ())
|
||||
{
|
||||
if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
tunnel->SetIsRecreated ();
|
||||
auto pool = tunnel->GetTunnelPool ();
|
||||
if (pool)
|
||||
pool->RecreateInboundTunnel (tunnel);
|
||||
}
|
||||
|
||||
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
tunnel->SetState (eTunnelStateExpiring);
|
||||
else // we don't need to cleanup expiring tunnels
|
||||
tunnel->Cleanup ();
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_InboundTunnels.empty ())
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel");
|
||||
CreateZeroHopsInboundTunnel ();
|
||||
CreateZeroHopsOutboundTunnel ();
|
||||
if (!m_ExploratoryPool)
|
||||
{
|
||||
int ibLen; i2p::config::GetOption("exploratory.inbound.length", ibLen);
|
||||
int obLen; i2p::config::GetOption("exploratory.outbound.length", obLen);
|
||||
int ibNum; i2p::config::GetOption("exploratory.inbound.quantity", ibNum);
|
||||
int obNum; i2p::config::GetOption("exploratory.outbound.quantity", obNum);
|
||||
m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum);
|
||||
m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3)
|
||||
{
|
||||
// trying to create one more inbound tunnel
|
||||
auto router = i2p::transport::transports.RoutesRestricted() ?
|
||||
i2p::transport::transports.GetRestrictedPeer() :
|
||||
i2p::data::netdb.GetRandomRouter ();
|
||||
if (!router) {
|
||||
LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel");
|
||||
return;
|
||||
}
|
||||
LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel");
|
||||
CreateTunnel<InboundTunnel> (
|
||||
std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageTransitTunnels ()
|
||||
{
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
|
||||
{
|
||||
auto tunnel = *it;
|
||||
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired");
|
||||
m_Tunnels.erase (tunnel->GetTunnelID ());
|
||||
it = m_TransitTunnels.erase (it);
|
||||
}
|
||||
else
|
||||
{
|
||||
tunnel->Cleanup ();
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::ManageTunnelPools ()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_PoolsMutex);
|
||||
for (auto& pool : m_Pools)
|
||||
{
|
||||
if (pool && pool->IsActive ())
|
||||
{
|
||||
pool->CreateTunnels ();
|
||||
pool->TestTunnels ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnels::PostTunnelData (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (msg) m_Queue.Put (msg);
|
||||
}
|
||||
|
||||
void Tunnels::PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
|
||||
{
|
||||
m_Queue.Put (msgs);
|
||||
}
|
||||
|
||||
template<class TTunnel>
|
||||
std::shared_ptr<TTunnel> Tunnels::CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel)
|
||||
{
|
||||
auto newTunnel = std::make_shared<TTunnel> (config);
|
||||
uint32_t replyMsgID;
|
||||
RAND_bytes ((uint8_t *)&replyMsgID, 4);
|
||||
AddPendingTunnel (replyMsgID, newTunnel);
|
||||
newTunnel->Build (replyMsgID, outboundTunnel);
|
||||
return newTunnel;
|
||||
}
|
||||
|
||||
std::shared_ptr<InboundTunnel> Tunnels::CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel)
|
||||
{
|
||||
if (config)
|
||||
return CreateTunnel<InboundTunnel>(config, outboundTunnel);
|
||||
else
|
||||
return CreateZeroHopsInboundTunnel ();
|
||||
}
|
||||
|
||||
std::shared_ptr<OutboundTunnel> Tunnels::CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config)
|
||||
{
|
||||
if (config)
|
||||
return CreateTunnel<OutboundTunnel>(config);
|
||||
else
|
||||
return CreateZeroHopsOutboundTunnel ();
|
||||
}
|
||||
|
||||
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel)
|
||||
{
|
||||
m_PendingInboundTunnels[replyMsgID] = tunnel;
|
||||
}
|
||||
|
||||
void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel)
|
||||
{
|
||||
m_PendingOutboundTunnels[replyMsgID] = tunnel;
|
||||
}
|
||||
|
||||
void Tunnels::AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel)
|
||||
{
|
||||
// we don't need to insert it to m_Tunnels
|
||||
m_OutboundTunnels.push_back (newTunnel);
|
||||
auto pool = newTunnel->GetTunnelPool ();
|
||||
if (pool && pool->IsActive ())
|
||||
pool->TunnelCreated (newTunnel);
|
||||
else
|
||||
newTunnel->SetTunnelPool (nullptr);
|
||||
}
|
||||
|
||||
void Tunnels::AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel)
|
||||
{
|
||||
if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second)
|
||||
{
|
||||
m_InboundTunnels.push_back (newTunnel);
|
||||
auto pool = newTunnel->GetTunnelPool ();
|
||||
if (!pool)
|
||||
{
|
||||
// build symmetric outbound tunnel
|
||||
CreateTunnel<OutboundTunnel> (std::make_shared<TunnelConfig>(newTunnel->GetInvertedPeers (),
|
||||
newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()),
|
||||
GetNextOutboundTunnel ());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pool->IsActive ())
|
||||
pool->TunnelCreated (newTunnel);
|
||||
else
|
||||
newTunnel->SetTunnelPool (nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists");
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<ZeroHopsInboundTunnel> Tunnels::CreateZeroHopsInboundTunnel ()
|
||||
{
|
||||
auto inboundTunnel = std::make_shared<ZeroHopsInboundTunnel> ();
|
||||
inboundTunnel->SetState (eTunnelStateEstablished);
|
||||
m_InboundTunnels.push_back (inboundTunnel);
|
||||
m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel;
|
||||
return inboundTunnel;
|
||||
}
|
||||
|
||||
std::shared_ptr<ZeroHopsOutboundTunnel> Tunnels::CreateZeroHopsOutboundTunnel ()
|
||||
{
|
||||
auto outboundTunnel = std::make_shared<ZeroHopsOutboundTunnel> ();
|
||||
outboundTunnel->SetState (eTunnelStateEstablished);
|
||||
m_OutboundTunnels.push_back (outboundTunnel);
|
||||
// we don't insert into m_Tunnels
|
||||
return outboundTunnel;
|
||||
}
|
||||
|
||||
int Tunnels::GetTransitTunnelsExpirationTimeout ()
|
||||
{
|
||||
int timeout = 0;
|
||||
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
|
||||
// TODO: possible race condition with I2PControl
|
||||
for (const auto& it : m_TransitTunnels)
|
||||
{
|
||||
int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
|
||||
if (t > timeout) timeout = t;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
size_t Tunnels::CountTransitTunnels() const
|
||||
{
|
||||
// TODO: locking
|
||||
return m_TransitTunnels.size();
|
||||
}
|
||||
|
||||
size_t Tunnels::CountInboundTunnels() const
|
||||
{
|
||||
// TODO: locking
|
||||
return m_InboundTunnels.size();
|
||||
}
|
||||
|
||||
size_t Tunnels::CountOutboundTunnels() const
|
||||
{
|
||||
// TODO: locking
|
||||
return m_OutboundTunnels.size();
|
||||
}
|
||||
}
|
||||
}
|
308
libi2pd/Tunnel.h
Normal file
308
libi2pd/Tunnel.h
Normal file
|
@ -0,0 +1,308 @@
|
|||
#ifndef TUNNEL_H__
|
||||
#define TUNNEL_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "Queue.h"
|
||||
#include "Crypto.h"
|
||||
#include "TunnelConfig.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "TransitTunnel.h"
|
||||
#include "TunnelEndpoint.h"
|
||||
#include "TunnelGateway.h"
|
||||
#include "TunnelBase.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Event.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
|
||||
template<typename TunnelT>
|
||||
static void EmitTunnelEvent(const std::string & ev, const TunnelT & t)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}});
|
||||
#else
|
||||
(void) ev;
|
||||
(void) t;
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename TunnelT, typename T>
|
||||
static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const T & val)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", std::to_string(val)}, {"inbound", std::to_string(t->IsInbound())}});
|
||||
#else
|
||||
(void) ev;
|
||||
(void) t;
|
||||
(void) val;
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename TunnelT>
|
||||
static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const std::string & val)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", val}, {"inbound", std::to_string(t->IsInbound())}});
|
||||
#else
|
||||
(void) ev;
|
||||
(void) t;
|
||||
(void) val;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes
|
||||
const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute
|
||||
const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes
|
||||
const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds
|
||||
const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message
|
||||
|
||||
enum TunnelState
|
||||
{
|
||||
eTunnelStatePending,
|
||||
eTunnelStateBuildReplyReceived,
|
||||
eTunnelStateBuildFailed,
|
||||
eTunnelStateEstablished,
|
||||
eTunnelStateTestFailed,
|
||||
eTunnelStateFailed,
|
||||
eTunnelStateExpiring
|
||||
};
|
||||
|
||||
class OutboundTunnel;
|
||||
class InboundTunnel;
|
||||
class Tunnel: public TunnelBase
|
||||
{
|
||||
struct TunnelHop
|
||||
{
|
||||
std::shared_ptr<const i2p::data::IdentityEx> ident;
|
||||
i2p::crypto::TunnelDecryption decryption;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
Tunnel (std::shared_ptr<const TunnelConfig> config);
|
||||
~Tunnel ();
|
||||
|
||||
void Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
|
||||
|
||||
std::shared_ptr<const 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;
|
||||
TunnelState GetState () const { return m_State; };
|
||||
void SetState (TunnelState state);
|
||||
bool IsEstablished () const { return m_State == eTunnelStateEstablished; };
|
||||
bool IsFailed () const { return m_State == eTunnelStateFailed; };
|
||||
bool IsRecreated () const { return m_IsRecreated; };
|
||||
void SetIsRecreated () { m_IsRecreated = true; };
|
||||
virtual bool IsInbound() const = 0;
|
||||
|
||||
std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
|
||||
void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; };
|
||||
|
||||
bool HandleTunnelBuildResponse (uint8_t * msg, size_t len);
|
||||
|
||||
virtual void Print (std::stringstream&) const {};
|
||||
|
||||
// implements TunnelBase
|
||||
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
|
||||
|
||||
/** @brief add latency sample */
|
||||
void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; }
|
||||
/** @brief get this tunnel's estimated latency */
|
||||
uint64_t GetMeanLatency() const { return m_Latency; }
|
||||
/** @breif return true if this tunnel's latency fits in range [lowerbound, upperbound] */
|
||||
bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const;
|
||||
|
||||
bool LatencyIsKnown() const { return m_Latency > 0; }
|
||||
protected:
|
||||
|
||||
void PrintHops (std::stringstream& s) const;
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<const TunnelConfig> m_Config;
|
||||
std::vector<std::unique_ptr<TunnelHop> > m_Hops;
|
||||
std::shared_ptr<TunnelPool> m_Pool; // pool, tunnel belongs to, or null
|
||||
TunnelState m_State;
|
||||
bool m_IsRecreated;
|
||||
uint64_t m_Latency; // in milliseconds
|
||||
};
|
||||
|
||||
class OutboundTunnel: public Tunnel
|
||||
{
|
||||
public:
|
||||
|
||||
OutboundTunnel (std::shared_ptr<const TunnelConfig> config):
|
||||
Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {};
|
||||
|
||||
void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
virtual void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
|
||||
const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; };
|
||||
virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); };
|
||||
void Print (std::stringstream& s) const;
|
||||
|
||||
// implements TunnelBase
|
||||
void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg);
|
||||
|
||||
bool IsInbound() const { return false; }
|
||||
|
||||
private:
|
||||
|
||||
std::mutex m_SendMutex;
|
||||
TunnelGateway m_Gateway;
|
||||
i2p::data::IdentHash m_EndpointIdentHash;
|
||||
};
|
||||
|
||||
class InboundTunnel: public Tunnel, public std::enable_shared_from_this<InboundTunnel>
|
||||
{
|
||||
public:
|
||||
|
||||
InboundTunnel (std::shared_ptr<const TunnelConfig> config): Tunnel (config), m_Endpoint (true) {};
|
||||
void HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg);
|
||||
virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
|
||||
void Print (std::stringstream& s) const;
|
||||
bool IsInbound() const { return true; }
|
||||
|
||||
// override TunnelBase
|
||||
void Cleanup () { m_Endpoint.Cleanup (); };
|
||||
|
||||
private:
|
||||
|
||||
TunnelEndpoint m_Endpoint;
|
||||
};
|
||||
|
||||
class ZeroHopsInboundTunnel: public InboundTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
ZeroHopsInboundTunnel ();
|
||||
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg);
|
||||
void Print (std::stringstream& s) const;
|
||||
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||
|
||||
private:
|
||||
|
||||
size_t m_NumReceivedBytes;
|
||||
};
|
||||
|
||||
class ZeroHopsOutboundTunnel: public OutboundTunnel
|
||||
{
|
||||
public:
|
||||
|
||||
ZeroHopsOutboundTunnel ();
|
||||
void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs);
|
||||
void Print (std::stringstream& s) const;
|
||||
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||
|
||||
private:
|
||||
|
||||
size_t m_NumSentBytes;
|
||||
};
|
||||
|
||||
class Tunnels
|
||||
{
|
||||
public:
|
||||
|
||||
Tunnels ();
|
||||
~Tunnels ();
|
||||
void Start ();
|
||||
void Stop ();
|
||||
|
||||
std::shared_ptr<InboundTunnel> GetPendingInboundTunnel (uint32_t replyMsgID);
|
||||
std::shared_ptr<OutboundTunnel> GetPendingOutboundTunnel (uint32_t replyMsgID);
|
||||
std::shared_ptr<InboundTunnel> GetNextInboundTunnel ();
|
||||
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel ();
|
||||
std::shared_ptr<TunnelPool> GetExploratoryPool () const { return m_ExploratoryPool; };
|
||||
std::shared_ptr<TunnelBase> GetTunnel (uint32_t tunnelID);
|
||||
int GetTransitTunnelsExpirationTimeout ();
|
||||
void AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
|
||||
void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel);
|
||||
void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel);
|
||||
std::shared_ptr<InboundTunnel> CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel);
|
||||
std::shared_ptr<OutboundTunnel> CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config);
|
||||
void PostTunnelData (std::shared_ptr<I2NPMessage> msg);
|
||||
void PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
|
||||
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel);
|
||||
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel);
|
||||
std::shared_ptr<TunnelPool> CreateTunnelPool (int numInboundHops,
|
||||
int numOuboundHops, int numInboundTunnels, int numOutboundTunnels);
|
||||
void DeleteTunnelPool (std::shared_ptr<TunnelPool> pool);
|
||||
void StopTunnelPool (std::shared_ptr<TunnelPool> pool);
|
||||
|
||||
private:
|
||||
|
||||
template<class TTunnel>
|
||||
std::shared_ptr<TTunnel> CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr);
|
||||
|
||||
template<class TTunnel>
|
||||
std::shared_ptr<TTunnel> GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels);
|
||||
|
||||
void HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
void Run ();
|
||||
void ManageTunnels ();
|
||||
void ManageOutboundTunnels ();
|
||||
void ManageInboundTunnels ();
|
||||
void ManageTransitTunnels ();
|
||||
void ManagePendingTunnels ();
|
||||
template<class PendingTunnels>
|
||||
void ManagePendingTunnels (PendingTunnels& pendingTunnels);
|
||||
void ManageTunnelPools ();
|
||||
|
||||
std::shared_ptr<ZeroHopsInboundTunnel> CreateZeroHopsInboundTunnel ();
|
||||
std::shared_ptr<ZeroHopsOutboundTunnel> CreateZeroHopsOutboundTunnel ();
|
||||
|
||||
private:
|
||||
|
||||
bool m_IsRunning;
|
||||
std::thread * m_Thread;
|
||||
std::map<uint32_t, std::shared_ptr<InboundTunnel> > m_PendingInboundTunnels; // by replyMsgID
|
||||
std::map<uint32_t, std::shared_ptr<OutboundTunnel> > m_PendingOutboundTunnels; // by replyMsgID
|
||||
std::list<std::shared_ptr<InboundTunnel> > m_InboundTunnels;
|
||||
std::list<std::shared_ptr<OutboundTunnel> > m_OutboundTunnels;
|
||||
std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<TunnelBase> > m_Tunnels; // tunnelID->tunnel known by this id
|
||||
std::mutex m_PoolsMutex;
|
||||
std::list<std::shared_ptr<TunnelPool>> m_Pools;
|
||||
std::shared_ptr<TunnelPool> m_ExploratoryPool;
|
||||
i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_Queue;
|
||||
|
||||
// some stats
|
||||
int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations;
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
|
||||
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
|
||||
const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; };
|
||||
|
||||
size_t CountTransitTunnels() const;
|
||||
size_t CountInboundTunnels() const;
|
||||
size_t CountOutboundTunnels() const;
|
||||
|
||||
int GetQueueSize () { return m_Queue.GetSize (); };
|
||||
int GetTunnelCreationSuccessRate () const // in percents
|
||||
{
|
||||
int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations;
|
||||
return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0;
|
||||
}
|
||||
};
|
||||
|
||||
extern Tunnels tunnels;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
73
libi2pd/TunnelBase.h
Normal file
73
libi2pd/TunnelBase.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
#ifndef TUNNEL_BASE_H__
|
||||
#define TUNNEL_BASE_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <memory>
|
||||
#include "Timestamp.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Identity.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
const size_t TUNNEL_DATA_MSG_SIZE = 1028;
|
||||
const size_t TUNNEL_DATA_ENCRYPTED_SIZE = 1008;
|
||||
const size_t TUNNEL_DATA_MAX_PAYLOAD_SIZE = 1003;
|
||||
|
||||
enum TunnelDeliveryType
|
||||
{
|
||||
eDeliveryTypeLocal = 0,
|
||||
eDeliveryTypeTunnel = 1,
|
||||
eDeliveryTypeRouter = 2
|
||||
};
|
||||
struct TunnelMessageBlock
|
||||
{
|
||||
TunnelDeliveryType deliveryType;
|
||||
i2p::data::IdentHash hash;
|
||||
uint32_t tunnelID;
|
||||
std::shared_ptr<I2NPMessage> data;
|
||||
};
|
||||
|
||||
class TunnelBase
|
||||
{
|
||||
public:
|
||||
|
||||
TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent):
|
||||
m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent),
|
||||
m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {};
|
||||
virtual ~TunnelBase () {};
|
||||
virtual void Cleanup () {};
|
||||
|
||||
virtual void HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg) = 0;
|
||||
virtual void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) = 0;
|
||||
virtual void FlushTunnelDataMsgs () {};
|
||||
virtual void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) = 0;
|
||||
uint32_t GetNextTunnelID () const { return m_NextTunnelID; };
|
||||
const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; };
|
||||
virtual uint32_t GetTunnelID () const { return m_TunnelID; }; // as known at our side
|
||||
|
||||
uint32_t GetCreationTime () const { return m_CreationTime; };
|
||||
void SetCreationTime (uint32_t t) { m_CreationTime = t; };
|
||||
|
||||
private:
|
||||
|
||||
uint32_t m_TunnelID, m_NextTunnelID;
|
||||
i2p::data::IdentHash m_NextIdent;
|
||||
uint32_t m_CreationTime; // seconds since epoch
|
||||
};
|
||||
|
||||
struct TunnelCreationTimeCmp
|
||||
{
|
||||
bool operator() (std::shared_ptr<const TunnelBase> t1, std::shared_ptr<const TunnelBase> t2) const
|
||||
{
|
||||
if (t1->GetCreationTime () != t2->GetCreationTime ())
|
||||
return t1->GetCreationTime () > t2->GetCreationTime ();
|
||||
else
|
||||
return t1 < t2;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
252
libi2pd/TunnelConfig.h
Normal file
252
libi2pd/TunnelConfig.h
Normal file
|
@ -0,0 +1,252 @@
|
|||
#ifndef TUNNEL_CONFIG_H__
|
||||
#define TUNNEL_CONFIG_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "Crypto.h"
|
||||
#include "Identity.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Timestamp.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
struct TunnelHopConfig
|
||||
{
|
||||
std::shared_ptr<const i2p::data::IdentityEx> ident;
|
||||
i2p::data::IdentHash nextIdent;
|
||||
uint32_t tunnelID, nextTunnelID;
|
||||
uint8_t layerKey[32];
|
||||
uint8_t ivKey[32];
|
||||
uint8_t replyKey[32];
|
||||
uint8_t replyIV[16];
|
||||
bool isGateway, isEndpoint;
|
||||
|
||||
TunnelHopConfig * next, * prev;
|
||||
int recordIndex; // record # in tunnel build message
|
||||
|
||||
TunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r)
|
||||
{
|
||||
RAND_bytes (layerKey, 32);
|
||||
RAND_bytes (ivKey, 32);
|
||||
RAND_bytes (replyKey, 32);
|
||||
RAND_bytes (replyIV, 16);
|
||||
RAND_bytes ((uint8_t *)&tunnelID, 4);
|
||||
isGateway = true;
|
||||
isEndpoint = true;
|
||||
ident = r;
|
||||
//nextRouter = nullptr;
|
||||
nextTunnelID = 0;
|
||||
|
||||
next = nullptr;
|
||||
prev = nullptr;
|
||||
}
|
||||
|
||||
void SetNextIdent (const i2p::data::IdentHash& ident)
|
||||
{
|
||||
nextIdent = ident;
|
||||
isEndpoint = false;
|
||||
RAND_bytes ((uint8_t *)&nextTunnelID, 4);
|
||||
}
|
||||
|
||||
void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent)
|
||||
{
|
||||
nextIdent = replyIdent;
|
||||
nextTunnelID = replyTunnelID;
|
||||
isEndpoint = true;
|
||||
}
|
||||
|
||||
void SetNext (TunnelHopConfig * n)
|
||||
{
|
||||
next = n;
|
||||
if (next)
|
||||
{
|
||||
next->prev = this;
|
||||
next->isGateway = false;
|
||||
isEndpoint = false;
|
||||
nextIdent = next->ident->GetIdentHash ();
|
||||
nextTunnelID = next->tunnelID;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPrev (TunnelHopConfig * p)
|
||||
{
|
||||
prev = p;
|
||||
if (prev)
|
||||
{
|
||||
prev->next = this;
|
||||
prev->isEndpoint = false;
|
||||
isGateway = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const
|
||||
{
|
||||
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);
|
||||
uint8_t flag = 0;
|
||||
if (isGateway) flag |= 0x80;
|
||||
if (isEndpoint) flag |= 0x40;
|
||||
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);
|
||||
i2p::crypto::ElGamalEncrypt (ident->GetEncryptionPublicKey (), clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx);
|
||||
memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
|
||||
}
|
||||
};
|
||||
|
||||
class TunnelConfig
|
||||
{
|
||||
public:
|
||||
|
||||
TunnelConfig (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers) // inbound
|
||||
{
|
||||
CreatePeers (peers);
|
||||
m_LastHop->SetNextIdent (i2p::context.GetIdentHash ());
|
||||
}
|
||||
|
||||
TunnelConfig (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers,
|
||||
uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound
|
||||
{
|
||||
CreatePeers (peers);
|
||||
m_FirstHop->isGateway = false;
|
||||
m_LastHop->SetReplyHop (replyTunnelID, replyIdent);
|
||||
}
|
||||
|
||||
~TunnelConfig ()
|
||||
{
|
||||
TunnelHopConfig * hop = m_FirstHop;
|
||||
|
||||
while (hop)
|
||||
{
|
||||
auto tmp = hop;
|
||||
hop = hop->next;
|
||||
delete tmp;
|
||||
}
|
||||
}
|
||||
|
||||
TunnelHopConfig * GetFirstHop () const
|
||||
{
|
||||
return m_FirstHop;
|
||||
}
|
||||
|
||||
TunnelHopConfig * GetLastHop () const
|
||||
{
|
||||
return m_LastHop;
|
||||
}
|
||||
|
||||
int GetNumHops () const
|
||||
{
|
||||
int num = 0;
|
||||
TunnelHopConfig * hop = m_FirstHop;
|
||||
while (hop)
|
||||
{
|
||||
num++;
|
||||
hop = hop->next;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
bool IsEmpty () const
|
||||
{
|
||||
return !m_FirstHop;
|
||||
}
|
||||
|
||||
virtual bool IsInbound () const { return m_FirstHop->isGateway; }
|
||||
|
||||
virtual uint32_t GetTunnelID () const
|
||||
{
|
||||
if (!m_FirstHop) return 0;
|
||||
return IsInbound () ? m_LastHop->nextTunnelID : m_FirstHop->tunnelID;
|
||||
}
|
||||
|
||||
virtual uint32_t GetNextTunnelID () const
|
||||
{
|
||||
if (!m_FirstHop) return 0;
|
||||
return m_FirstHop->tunnelID;
|
||||
}
|
||||
|
||||
virtual const i2p::data::IdentHash& GetNextIdentHash () const
|
||||
{
|
||||
return m_FirstHop->ident->GetIdentHash ();
|
||||
}
|
||||
|
||||
virtual const i2p::data::IdentHash& GetLastIdentHash () const
|
||||
{
|
||||
return m_LastHop->ident->GetIdentHash ();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > GetPeers () const
|
||||
{
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers;
|
||||
TunnelHopConfig * hop = m_FirstHop;
|
||||
while (hop)
|
||||
{
|
||||
peers.push_back (hop->ident);
|
||||
hop = hop->next;
|
||||
}
|
||||
return peers;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// this constructor can't be called from outside
|
||||
TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<class Peers>
|
||||
void CreatePeers (const Peers& peers)
|
||||
{
|
||||
TunnelHopConfig * prev = nullptr;
|
||||
for (const auto& it: peers)
|
||||
{
|
||||
auto hop = new TunnelHopConfig (it);
|
||||
if (prev)
|
||||
prev->SetNext (hop);
|
||||
else
|
||||
m_FirstHop = hop;
|
||||
prev = hop;
|
||||
}
|
||||
m_LastHop = prev;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
TunnelHopConfig * m_FirstHop, * m_LastHop;
|
||||
};
|
||||
|
||||
class ZeroHopsTunnelConfig: public TunnelConfig
|
||||
{
|
||||
public:
|
||||
|
||||
ZeroHopsTunnelConfig () { RAND_bytes ((uint8_t *)&m_TunnelID, 4);};
|
||||
|
||||
bool IsInbound () const { return true; }; // TODO:
|
||||
uint32_t GetTunnelID () const { return m_TunnelID; };
|
||||
uint32_t GetNextTunnelID () const { return m_TunnelID; };
|
||||
const i2p::data::IdentHash& GetNextIdentHash () const { return i2p::context.GetIdentHash (); };
|
||||
const i2p::data::IdentHash& GetLastIdentHash () const { return i2p::context.GetIdentHash (); };
|
||||
|
||||
|
||||
private:
|
||||
|
||||
uint32_t m_TunnelID;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
298
libi2pd/TunnelEndpoint.cpp
Normal file
298
libi2pd/TunnelEndpoint.cpp
Normal file
|
@ -0,0 +1,298 @@
|
|||
#include "I2PEndian.h"
|
||||
#include <string.h>
|
||||
#include "Crypto.h"
|
||||
#include "Log.h"
|
||||
#include "NetDb.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "Transports.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Timestamp.h"
|
||||
#include "TunnelEndpoint.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
TunnelEndpoint::~TunnelEndpoint ()
|
||||
{
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE;
|
||||
|
||||
uint8_t * decrypted = msg->GetPayload () + 20; // 4 + 16
|
||||
uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // witout 4-byte checksum
|
||||
if (zero)
|
||||
{
|
||||
uint8_t * fragment = zero + 1;
|
||||
// verify checksum
|
||||
memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end
|
||||
uint8_t hash[32];
|
||||
SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv
|
||||
if (memcmp (hash, decrypted, 4))
|
||||
{
|
||||
LogPrint (eLogError, "TunnelMessage: checksum verification failed");
|
||||
return;
|
||||
}
|
||||
// process fragments
|
||||
while (fragment < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
|
||||
{
|
||||
uint8_t flag = fragment[0];
|
||||
fragment++;
|
||||
|
||||
bool isFollowOnFragment = flag & 0x80, isLastFragment = true;
|
||||
uint32_t msgID = 0;
|
||||
int fragmentNum = 0;
|
||||
TunnelMessageBlockEx m;
|
||||
if (!isFollowOnFragment)
|
||||
{
|
||||
// first fragment
|
||||
|
||||
m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03);
|
||||
switch (m.deliveryType)
|
||||
{
|
||||
case eDeliveryTypeLocal: // 0
|
||||
break;
|
||||
case eDeliveryTypeTunnel: // 1
|
||||
m.tunnelID = bufbe32toh (fragment);
|
||||
fragment += 4; // tunnelID
|
||||
m.hash = i2p::data::IdentHash (fragment);
|
||||
fragment += 32; // hash
|
||||
break;
|
||||
case eDeliveryTypeRouter: // 2
|
||||
m.hash = i2p::data::IdentHash (fragment);
|
||||
fragment += 32; // to hash
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
bool isFragmented = flag & 0x08;
|
||||
if (isFragmented)
|
||||
{
|
||||
// Message ID
|
||||
msgID = bufbe32toh (fragment);
|
||||
fragment += 4;
|
||||
isLastFragment = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// follow on
|
||||
msgID = bufbe32toh (fragment); // MessageID
|
||||
fragment += 4;
|
||||
fragmentNum = (flag >> 1) & 0x3F; // 6 bits
|
||||
isLastFragment = flag & 0x01;
|
||||
}
|
||||
|
||||
uint16_t size = bufbe16toh (fragment);
|
||||
fragment += 2;
|
||||
|
||||
msg->offset = fragment - msg->buf;
|
||||
msg->len = msg->offset + size;
|
||||
if (msg->len > msg->maxLen)
|
||||
{
|
||||
LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size);
|
||||
return;
|
||||
}
|
||||
if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
|
||||
{
|
||||
// this is not last message. we have to copy it
|
||||
m.data = NewI2NPTunnelMessage ();
|
||||
m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header
|
||||
m.data->len += TUNNEL_GATEWAY_HEADER_SIZE;
|
||||
*(m.data) = *msg;
|
||||
}
|
||||
else
|
||||
m.data = msg;
|
||||
|
||||
if (!isFollowOnFragment && isLastFragment)
|
||||
HandleNextMessage (m);
|
||||
else
|
||||
{
|
||||
if (msgID) // msgID is presented, assume message is fragmented
|
||||
{
|
||||
if (!isFollowOnFragment) // create new incomlete message
|
||||
{
|
||||
m.nextFragmentNum = 1;
|
||||
m.receiveTime = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
auto ret = m_IncompleteMessages.insert (std::pair<uint32_t, TunnelMessageBlockEx>(msgID, m));
|
||||
if (ret.second)
|
||||
HandleOutOfSequenceFragments (msgID, ret.first->second);
|
||||
else
|
||||
LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists");
|
||||
}
|
||||
else
|
||||
{
|
||||
m.nextFragmentNum = fragmentNum;
|
||||
HandleFollowOnFragment (msgID, isLastFragment, m);
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented");
|
||||
}
|
||||
|
||||
fragment += size;
|
||||
}
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "TunnelMessage: zero not found");
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m)
|
||||
{
|
||||
auto fragment = m.data->GetBuffer ();
|
||||
auto size = m.data->GetLength ();
|
||||
auto it = m_IncompleteMessages.find (msgID);
|
||||
if (it != m_IncompleteMessages.end())
|
||||
{
|
||||
auto& msg = it->second;
|
||||
if (m.nextFragmentNum == msg.nextFragmentNum)
|
||||
{
|
||||
if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long
|
||||
{
|
||||
if (msg.data->len + size > msg.data->maxLen)
|
||||
{
|
||||
// LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough");
|
||||
auto newMsg = NewI2NPMessage ();
|
||||
*newMsg = *(msg.data);
|
||||
msg.data = newMsg;
|
||||
}
|
||||
if (msg.data->Concat (fragment, size) < size) // concatenate fragment
|
||||
LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen);
|
||||
if (isLastFragment)
|
||||
{
|
||||
// message complete
|
||||
HandleNextMessage (msg);
|
||||
m_IncompleteMessages.erase (it);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.nextFragmentNum++;
|
||||
HandleOutOfSequenceFragments (msgID, msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped");
|
||||
m_IncompleteMessages.erase (it);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved");
|
||||
AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved");
|
||||
AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data);
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr<I2NPMessage> data)
|
||||
{
|
||||
if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second)
|
||||
LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID);
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg)
|
||||
{
|
||||
while (ConcatNextOutOfSequenceFragment (msgID, msg))
|
||||
{
|
||||
if (!msg.nextFragmentNum) // message complete
|
||||
{
|
||||
HandleNextMessage (msg);
|
||||
m_IncompleteMessages.erase (msgID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg)
|
||||
{
|
||||
auto it = m_OutOfSequenceFragments.find ({msgID, msg.nextFragmentNum});
|
||||
if (it != m_OutOfSequenceFragments.end ())
|
||||
{
|
||||
LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found");
|
||||
size_t size = it->second.data->GetLength ();
|
||||
if (msg.data->len + size > msg.data->maxLen)
|
||||
{
|
||||
LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough");
|
||||
auto newMsg = NewI2NPMessage ();
|
||||
*newMsg = *(msg.data);
|
||||
msg.data = newMsg;
|
||||
}
|
||||
if (msg.data->Concat (it->second.data->GetBuffer (), size) < size) // concatenate out-of-sync fragment
|
||||
LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen);
|
||||
if (it->second.isLastFragment)
|
||||
// message complete
|
||||
msg.nextFragmentNum = 0;
|
||||
else
|
||||
msg.nextFragmentNum++;
|
||||
m_OutOfSequenceFragments.erase (it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg)
|
||||
{
|
||||
if (!m_IsInbound && msg.data->IsExpired ())
|
||||
{
|
||||
LogPrint (eLogInfo, "TunnelMessage: message expired");
|
||||
return;
|
||||
}
|
||||
uint8_t typeID = msg.data->GetTypeID ();
|
||||
LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID);
|
||||
// catch RI or reply with new list of routers
|
||||
if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) &&
|
||||
!m_IsInbound && msg.deliveryType != eDeliveryTypeLocal)
|
||||
i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg.data));
|
||||
|
||||
switch (msg.deliveryType)
|
||||
{
|
||||
case eDeliveryTypeLocal:
|
||||
i2p::HandleI2NPMessage (msg.data);
|
||||
break;
|
||||
case eDeliveryTypeTunnel:
|
||||
if (!m_IsInbound) // outbound transit tunnel
|
||||
i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
|
||||
else
|
||||
LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped");
|
||||
break;
|
||||
case eDeliveryTypeRouter:
|
||||
if (!m_IsInbound) // outbound transit tunnel
|
||||
i2p::transport::transports.SendMessage (msg.hash, msg.data);
|
||||
else // we shouldn't send this message. possible leakage
|
||||
LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped");
|
||||
break;
|
||||
default:
|
||||
LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType);
|
||||
};
|
||||
}
|
||||
|
||||
void TunnelEndpoint::Cleanup ()
|
||||
{
|
||||
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
|
||||
// out-of-sequence fragments
|
||||
for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();)
|
||||
{
|
||||
if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT)
|
||||
it = m_OutOfSequenceFragments.erase (it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
// incomplete messages
|
||||
for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();)
|
||||
{
|
||||
if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT)
|
||||
it = m_IncompleteMessages.erase (it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
libi2pd/TunnelEndpoint.h
Normal file
57
libi2pd/TunnelEndpoint.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef TUNNEL_ENDPOINT_H__
|
||||
#define TUNNEL_ENDPOINT_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelBase.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class TunnelEndpoint
|
||||
{
|
||||
struct TunnelMessageBlockEx: public TunnelMessageBlock
|
||||
{
|
||||
uint64_t receiveTime; // milliseconds since epoch
|
||||
uint8_t nextFragmentNum;
|
||||
};
|
||||
|
||||
struct Fragment
|
||||
{
|
||||
bool isLastFragment;
|
||||
std::shared_ptr<I2NPMessage> data;
|
||||
uint64_t receiveTime; // milliseconds since epoch
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {};
|
||||
~TunnelEndpoint ();
|
||||
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
|
||||
void Cleanup ();
|
||||
|
||||
void HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
private:
|
||||
|
||||
void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m);
|
||||
void HandleNextMessage (const TunnelMessageBlock& msg);
|
||||
|
||||
void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr<I2NPMessage> data);
|
||||
bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added
|
||||
void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg);
|
||||
|
||||
private:
|
||||
|
||||
std::map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages;
|
||||
std::map<std::pair<uint32_t, uint8_t>, Fragment> m_OutOfSequenceFragments; // (msgID, fragment#)->fragment
|
||||
bool m_IsInbound;
|
||||
size_t m_NumReceivedBytes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
221
libi2pd/TunnelGateway.cpp
Normal file
221
libi2pd/TunnelGateway.cpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
#include <string.h>
|
||||
#include "Crypto.h"
|
||||
#include "I2PEndian.h"
|
||||
#include "Log.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Transports.h"
|
||||
#include "TunnelGateway.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
TunnelGatewayBuffer::TunnelGatewayBuffer ():
|
||||
m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0)
|
||||
{
|
||||
RAND_bytes (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE);
|
||||
for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++)
|
||||
if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1;
|
||||
}
|
||||
|
||||
TunnelGatewayBuffer::~TunnelGatewayBuffer ()
|
||||
{
|
||||
ClearTunnelDataMsgs ();
|
||||
}
|
||||
|
||||
void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block)
|
||||
{
|
||||
bool messageCreated = false;
|
||||
if (!m_CurrentTunnelDataMsg)
|
||||
{
|
||||
CreateCurrentTunnelDataMessage ();
|
||||
messageCreated = true;
|
||||
}
|
||||
|
||||
// create delivery instructions
|
||||
uint8_t di[43]; // max delivery instruction length is 43 for tunnel
|
||||
size_t diLen = 1;// flag
|
||||
if (block.deliveryType != eDeliveryTypeLocal) // tunnel or router
|
||||
{
|
||||
if (block.deliveryType == eDeliveryTypeTunnel)
|
||||
{
|
||||
htobe32buf (di + diLen, block.tunnelID);
|
||||
diLen += 4; // tunnelID
|
||||
}
|
||||
|
||||
memcpy (di + diLen, block.hash, 32);
|
||||
diLen += 32; //len
|
||||
}
|
||||
di[0] = block.deliveryType << 5; // set delivery type
|
||||
|
||||
// create fragments
|
||||
const std::shared_ptr<I2NPMessage> & msg = block.data;
|
||||
size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length
|
||||
if (fullMsgLen <= m_RemainingSize)
|
||||
{
|
||||
// message fits. First and last fragment
|
||||
htobe16buf (di + diLen, msg->GetLength ());
|
||||
diLen += 2; // size
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen);
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), msg->GetLength ());
|
||||
m_CurrentTunnelDataMsg->len += diLen + msg->GetLength ();
|
||||
m_RemainingSize -= diLen + msg->GetLength ();
|
||||
if (!m_RemainingSize)
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!messageCreated) // check if we should complete previous message
|
||||
{
|
||||
size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
// length of bytes don't fit full tunnel message
|
||||
// every follow-on fragment adds 7 bytes
|
||||
size_t nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
if (!nonFit || nonFit > m_RemainingSize)
|
||||
{
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
CreateCurrentTunnelDataMessage ();
|
||||
}
|
||||
}
|
||||
if (diLen + 6 <= m_RemainingSize)
|
||||
{
|
||||
// delivery instructions fit
|
||||
uint32_t msgID;
|
||||
memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); // in network bytes order
|
||||
size_t size = m_RemainingSize - diLen - 6; // 6 = 4 (msgID) + 2 (size)
|
||||
|
||||
// first fragment
|
||||
di[0] |= 0x08; // fragmented
|
||||
htobuf32 (di + diLen, msgID);
|
||||
diLen += 4; // Message ID
|
||||
htobe16buf (di + diLen, size);
|
||||
diLen += 2; // size
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen);
|
||||
memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), size);
|
||||
m_CurrentTunnelDataMsg->len += diLen + size;
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
// follow on fragments
|
||||
int fragmentNumber = 1;
|
||||
while (size < msg->GetLength ())
|
||||
{
|
||||
CreateCurrentTunnelDataMessage ();
|
||||
uint8_t * buf = m_CurrentTunnelDataMsg->GetBuffer ();
|
||||
buf[0] = 0x80 | (fragmentNumber << 1); // frag
|
||||
bool isLastFragment = false;
|
||||
size_t s = msg->GetLength () - size;
|
||||
if (s > TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7) // 7 follow on instructions
|
||||
s = TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7;
|
||||
else // last fragment
|
||||
{
|
||||
buf[0] |= 0x01;
|
||||
isLastFragment = true;
|
||||
}
|
||||
htobuf32 (buf + 1, msgID); //Message ID
|
||||
htobe16buf (buf + 5, s); // size
|
||||
memcpy (buf + 7, msg->GetBuffer () + size, s);
|
||||
m_CurrentTunnelDataMsg->len += s+7;
|
||||
if (isLastFragment)
|
||||
{
|
||||
if(m_RemainingSize < (s+7)) {
|
||||
LogPrint (eLogError, "TunnelGateway: remaining size overflow: ", m_RemainingSize, " < ", s+7);
|
||||
} else {
|
||||
m_RemainingSize -= s+7;
|
||||
if (m_RemainingSize == 0)
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
}
|
||||
}
|
||||
else
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
size += s;
|
||||
fragmentNumber++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// delivery instructions don't fit. Create new message
|
||||
CompleteCurrentTunnelDataMessage ();
|
||||
PutI2NPMsg (block);
|
||||
// don't delete msg because it's taken care inside
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelGatewayBuffer::ClearTunnelDataMsgs ()
|
||||
{
|
||||
m_TunnelDataMsgs.clear ();
|
||||
m_CurrentTunnelDataMsg = nullptr;
|
||||
}
|
||||
|
||||
void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage ()
|
||||
{
|
||||
m_CurrentTunnelDataMsg = nullptr;
|
||||
m_CurrentTunnelDataMsg = NewI2NPShortMessage ();
|
||||
m_CurrentTunnelDataMsg->Align (12);
|
||||
// we reserve space for padding
|
||||
m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE;
|
||||
m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset;
|
||||
m_RemainingSize = TUNNEL_DATA_MAX_PAYLOAD_SIZE;
|
||||
}
|
||||
|
||||
void TunnelGatewayBuffer::CompleteCurrentTunnelDataMessage ()
|
||||
{
|
||||
if (!m_CurrentTunnelDataMsg) return;
|
||||
uint8_t * payload = m_CurrentTunnelDataMsg->GetBuffer ();
|
||||
size_t size = m_CurrentTunnelDataMsg->len - m_CurrentTunnelDataMsg->offset;
|
||||
|
||||
m_CurrentTunnelDataMsg->offset = m_CurrentTunnelDataMsg->len - TUNNEL_DATA_MSG_SIZE - I2NP_HEADER_SIZE;
|
||||
uint8_t * buf = m_CurrentTunnelDataMsg->GetPayload ();
|
||||
RAND_bytes (buf + 4, 16); // original IV
|
||||
memcpy (payload + size, buf + 4, 16); // copy IV for checksum
|
||||
uint8_t hash[32];
|
||||
SHA256(payload, size+16, hash);
|
||||
memcpy (buf+20, hash, 4); // checksum
|
||||
payload[-1] = 0; // zero
|
||||
ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1
|
||||
if (paddingSize > 0)
|
||||
{
|
||||
// non-zero padding
|
||||
auto randomOffset = rand () % (TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize + 1);
|
||||
memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize);
|
||||
}
|
||||
|
||||
// we can't fill message header yet because encryption is required
|
||||
m_TunnelDataMsgs.push_back (m_CurrentTunnelDataMsg);
|
||||
m_CurrentTunnelDataMsg = nullptr;
|
||||
}
|
||||
|
||||
void TunnelGateway::SendTunnelDataMsg (const TunnelMessageBlock& block)
|
||||
{
|
||||
if (block.data)
|
||||
{
|
||||
PutTunnelDataMsg (block);
|
||||
SendBuffer ();
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelGateway::PutTunnelDataMsg (const TunnelMessageBlock& block)
|
||||
{
|
||||
if (block.data)
|
||||
m_Buffer.PutI2NPMsg (block);
|
||||
}
|
||||
|
||||
void TunnelGateway::SendBuffer ()
|
||||
{
|
||||
m_Buffer.CompleteCurrentTunnelDataMessage ();
|
||||
std::vector<std::shared_ptr<I2NPMessage> > newTunnelMsgs;
|
||||
const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs ();
|
||||
for (auto& tunnelMsg : tunnelDataMsgs)
|
||||
{
|
||||
auto newMsg = CreateEmptyTunnelDataMsg ();
|
||||
m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg);
|
||||
htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ());
|
||||
newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
|
||||
newTunnelMsgs.push_back (newMsg);
|
||||
m_NumSentBytes += TUNNEL_DATA_MSG_SIZE;
|
||||
}
|
||||
m_Buffer.ClearTunnelDataMsgs ();
|
||||
i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
56
libi2pd/TunnelGateway.h
Normal file
56
libi2pd/TunnelGateway.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
#ifndef TUNNEL_GATEWAY_H__
|
||||
#define TUNNEL_GATEWAY_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelBase.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class TunnelGatewayBuffer
|
||||
{
|
||||
public:
|
||||
TunnelGatewayBuffer ();
|
||||
~TunnelGatewayBuffer ();
|
||||
void PutI2NPMsg (const TunnelMessageBlock& block);
|
||||
const std::vector<std::shared_ptr<const I2NPMessage> >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; };
|
||||
void ClearTunnelDataMsgs ();
|
||||
void CompleteCurrentTunnelDataMessage ();
|
||||
|
||||
private:
|
||||
|
||||
void CreateCurrentTunnelDataMessage ();
|
||||
|
||||
private:
|
||||
|
||||
std::vector<std::shared_ptr<const I2NPMessage> > m_TunnelDataMsgs;
|
||||
std::shared_ptr<I2NPMessage> m_CurrentTunnelDataMsg;
|
||||
size_t m_RemainingSize;
|
||||
uint8_t m_NonZeroRandomBuffer[TUNNEL_DATA_MAX_PAYLOAD_SIZE];
|
||||
};
|
||||
|
||||
class TunnelGateway
|
||||
{
|
||||
public:
|
||||
|
||||
TunnelGateway (TunnelBase * tunnel):
|
||||
m_Tunnel (tunnel), m_NumSentBytes (0) {};
|
||||
void SendTunnelDataMsg (const TunnelMessageBlock& block);
|
||||
void PutTunnelDataMsg (const TunnelMessageBlock& block);
|
||||
void SendBuffer ();
|
||||
size_t GetNumSentBytes () const { return m_NumSentBytes; };
|
||||
|
||||
private:
|
||||
|
||||
TunnelBase * m_Tunnel;
|
||||
TunnelGatewayBuffer m_Buffer;
|
||||
size_t m_NumSentBytes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
597
libi2pd/TunnelPool.cpp
Normal file
597
libi2pd/TunnelPool.cpp
Normal file
|
@ -0,0 +1,597 @@
|
|||
#include <algorithm>
|
||||
#include "I2PEndian.h"
|
||||
#include "Crypto.h"
|
||||
#include "Tunnel.h"
|
||||
#include "NetDb.h"
|
||||
#include "Timestamp.h"
|
||||
#include "Garlic.h"
|
||||
#include "Transports.h"
|
||||
#include "Log.h"
|
||||
#include "Tunnel.h"
|
||||
#include "TunnelPool.h"
|
||||
#include "Destination.h"
|
||||
#ifdef WITH_EVENTS
|
||||
#include "Event.h"
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
|
||||
TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels):
|
||||
m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops),
|
||||
m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true),
|
||||
m_CustomPeerSelector(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
TunnelPool::~TunnelPool ()
|
||||
{
|
||||
DetachTunnels ();
|
||||
}
|
||||
|
||||
void TunnelPool::SetExplicitPeers (std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers)
|
||||
{
|
||||
m_ExplicitPeers = explicitPeers;
|
||||
if (m_ExplicitPeers)
|
||||
{
|
||||
int size = m_ExplicitPeers->size ();
|
||||
if (m_NumInboundHops > size)
|
||||
{
|
||||
m_NumInboundHops = size;
|
||||
LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers");
|
||||
}
|
||||
if (m_NumOutboundHops > size)
|
||||
{
|
||||
m_NumOutboundHops = size;
|
||||
LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has beed adjusted to ", size, " for explicit peers");
|
||||
}
|
||||
m_NumInboundTunnels = 1;
|
||||
m_NumOutboundTunnels = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelPool::DetachTunnels ()
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
for (auto& it: m_InboundTunnels)
|
||||
it->SetTunnelPool (nullptr);
|
||||
m_InboundTunnels.clear ();
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
for (auto& it: m_OutboundTunnels)
|
||||
it->SetTunnelPool (nullptr);
|
||||
m_OutboundTunnels.clear ();
|
||||
}
|
||||
m_Tests.clear ();
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelCreated (std::shared_ptr<InboundTunnel> createdTunnel)
|
||||
{
|
||||
if (!m_IsActive) return;
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnels.created", createdTunnel);
|
||||
#endif
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
m_InboundTunnels.insert (createdTunnel);
|
||||
}
|
||||
if (m_LocalDestination)
|
||||
m_LocalDestination->SetLeaseSetUpdated ();
|
||||
|
||||
OnTunnelBuildResult(createdTunnel, eBuildResultOkay);
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelExpired (std::shared_ptr<InboundTunnel> expiredTunnel)
|
||||
{
|
||||
if (expiredTunnel)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnels.expired", expiredTunnel);
|
||||
#endif
|
||||
expiredTunnel->SetTunnelPool (nullptr);
|
||||
for (auto& it: m_Tests)
|
||||
if (it.second.second == expiredTunnel) it.second.second = nullptr;
|
||||
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
m_InboundTunnels.erase (expiredTunnel);
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelCreated (std::shared_ptr<OutboundTunnel> createdTunnel)
|
||||
{
|
||||
if (!m_IsActive) return;
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnels.created", createdTunnel);
|
||||
#endif
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
m_OutboundTunnels.insert (createdTunnel);
|
||||
}
|
||||
OnTunnelBuildResult(createdTunnel, eBuildResultOkay);
|
||||
|
||||
//CreatePairedInboundTunnel (createdTunnel);
|
||||
}
|
||||
|
||||
void TunnelPool::TunnelExpired (std::shared_ptr<OutboundTunnel> expiredTunnel)
|
||||
{
|
||||
if (expiredTunnel)
|
||||
{
|
||||
#ifdef WITH_EVENTS
|
||||
EmitTunnelEvent("tunnels.expired", expiredTunnel);
|
||||
#endif
|
||||
expiredTunnel->SetTunnelPool (nullptr);
|
||||
for (auto& it: m_Tests)
|
||||
if (it.second.first == expiredTunnel) it.second.first = nullptr;
|
||||
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
m_OutboundTunnels.erase (expiredTunnel);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<InboundTunnel> > TunnelPool::GetInboundTunnels (int num) const
|
||||
{
|
||||
std::vector<std::shared_ptr<InboundTunnel> > v;
|
||||
int i = 0;
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
for (const auto& it : m_InboundTunnels)
|
||||
{
|
||||
if (i >= num) break;
|
||||
if (it->IsEstablished ())
|
||||
{
|
||||
v.push_back (it);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
std::shared_ptr<OutboundTunnel> TunnelPool::GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded) const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
return GetNextTunnel (m_OutboundTunnels, excluded);
|
||||
}
|
||||
|
||||
std::shared_ptr<InboundTunnel> TunnelPool::GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded) const
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
return GetNextTunnel (m_InboundTunnels, excluded);
|
||||
}
|
||||
|
||||
template<class TTunnels>
|
||||
typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const
|
||||
{
|
||||
if (tunnels.empty ()) return nullptr;
|
||||
uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0;
|
||||
typename TTunnels::value_type tunnel = nullptr;
|
||||
for (const auto& it: tunnels)
|
||||
{
|
||||
if (it->IsEstablished () && it != excluded)
|
||||
{
|
||||
if(HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) {
|
||||
i ++;
|
||||
continue;
|
||||
}
|
||||
tunnel = it;
|
||||
i++;
|
||||
}
|
||||
if (i > ind && tunnel) break;
|
||||
}
|
||||
if(HasLatencyRequirement() && !tunnel) {
|
||||
ind = rand () % (tunnels.size ()/2 + 1), i = 0;
|
||||
for (const auto& it: tunnels)
|
||||
{
|
||||
if (it->IsEstablished () && it != excluded)
|
||||
{
|
||||
tunnel = it;
|
||||
i++;
|
||||
}
|
||||
if (i > ind && tunnel) break;
|
||||
}
|
||||
}
|
||||
if (!tunnel && excluded && excluded->IsEstablished ()) tunnel = excluded;
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
std::shared_ptr<OutboundTunnel> TunnelPool::GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const
|
||||
{
|
||||
if (old && old->IsEstablished ()) return old;
|
||||
std::shared_ptr<OutboundTunnel> tunnel;
|
||||
if (old)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
for (const auto& it: m_OutboundTunnels)
|
||||
if (it->IsEstablished () && old->GetEndpointIdentHash () == it->GetEndpointIdentHash ())
|
||||
{
|
||||
tunnel = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tunnel)
|
||||
tunnel = GetNextOutboundTunnel ();
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
void TunnelPool::CreateTunnels ()
|
||||
{
|
||||
int num = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
for (const auto& it : m_OutboundTunnels)
|
||||
if (it->IsEstablished ()) num++;
|
||||
}
|
||||
for (int i = num; i < m_NumOutboundTunnels; i++)
|
||||
CreateOutboundTunnel ();
|
||||
|
||||
num = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
for (const auto& it : m_InboundTunnels)
|
||||
if (it->IsEstablished ()) num++;
|
||||
}
|
||||
for (int i = num; i < m_NumInboundTunnels; i++)
|
||||
CreateInboundTunnel ();
|
||||
|
||||
if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB
|
||||
m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately
|
||||
}
|
||||
|
||||
void TunnelPool::TestTunnels ()
|
||||
{
|
||||
decltype(m_Tests) tests;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_TestsMutex);
|
||||
tests.swap(m_Tests);
|
||||
}
|
||||
|
||||
for (auto& it: tests)
|
||||
{
|
||||
LogPrint (eLogWarning, "Tunnels: test of tunnel ", it.first, " failed");
|
||||
// if test failed again with another tunnel we consider it failed
|
||||
if (it.second.first)
|
||||
{
|
||||
if (it.second.first->GetState () == eTunnelStateTestFailed)
|
||||
{
|
||||
it.second.first->SetState (eTunnelStateFailed);
|
||||
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
|
||||
m_OutboundTunnels.erase (it.second.first);
|
||||
}
|
||||
else
|
||||
it.second.first->SetState (eTunnelStateTestFailed);
|
||||
}
|
||||
if (it.second.second)
|
||||
{
|
||||
if (it.second.second->GetState () == eTunnelStateTestFailed)
|
||||
{
|
||||
it.second.second->SetState (eTunnelStateFailed);
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
|
||||
m_InboundTunnels.erase (it.second.second);
|
||||
}
|
||||
if (m_LocalDestination)
|
||||
m_LocalDestination->SetLeaseSetUpdated ();
|
||||
}
|
||||
else
|
||||
it.second.second->SetState (eTunnelStateTestFailed);
|
||||
}
|
||||
}
|
||||
|
||||
// new tests
|
||||
auto it1 = m_OutboundTunnels.begin ();
|
||||
auto it2 = m_InboundTunnels.begin ();
|
||||
while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ())
|
||||
{
|
||||
bool failed = false;
|
||||
if ((*it1)->IsFailed ())
|
||||
{
|
||||
failed = true;
|
||||
++it1;
|
||||
}
|
||||
if ((*it2)->IsFailed ())
|
||||
{
|
||||
failed = true;
|
||||
++it2;
|
||||
}
|
||||
if (!failed)
|
||||
{
|
||||
uint32_t msgID;
|
||||
RAND_bytes ((uint8_t *)&msgID, 4);
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_TestsMutex);
|
||||
m_Tests[msgID] = std::make_pair (*it1, *it2);
|
||||
}
|
||||
(*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (),
|
||||
CreateDeliveryStatusMsg (msgID));
|
||||
++it1; ++it2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TunnelPool::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
if (m_LocalDestination)
|
||||
m_LocalDestination->ProcessGarlicMessage (msg);
|
||||
else
|
||||
LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped");
|
||||
}
|
||||
|
||||
void TunnelPool::ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg)
|
||||
{
|
||||
const uint8_t * buf = msg->GetPayload ();
|
||||
uint32_t msgID = bufbe32toh (buf);
|
||||
buf += 4;
|
||||
uint64_t timestamp = bufbe64toh (buf);
|
||||
|
||||
decltype(m_Tests)::mapped_type test;
|
||||
bool found = false;
|
||||
{
|
||||
std::unique_lock<std::mutex> l(m_TestsMutex);
|
||||
auto it = m_Tests.find (msgID);
|
||||
if (it != m_Tests.end ())
|
||||
{
|
||||
found = true;
|
||||
test = it->second;
|
||||
m_Tests.erase (it);
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
// restore from test failed state if any
|
||||
if (test.first->GetState () == eTunnelStateTestFailed)
|
||||
test.first->SetState (eTunnelStateEstablished);
|
||||
if (test.second->GetState () == eTunnelStateTestFailed)
|
||||
test.second->SetState (eTunnelStateEstablished);
|
||||
uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp;
|
||||
LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", dlt, " milliseconds");
|
||||
// update latency
|
||||
uint64_t latency = dlt / 2;
|
||||
test.first->AddLatencySample(latency);
|
||||
test.second->AddLatencySample(latency);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_LocalDestination)
|
||||
m_LocalDestination->ProcessDeliveryStatusMessage (msg);
|
||||
else
|
||||
LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const i2p::data::RouterInfo> TunnelPool::SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop) const
|
||||
{
|
||||
bool isExploratory = (i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ());
|
||||
auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop):
|
||||
i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop);
|
||||
|
||||
if (!hop || hop->GetProfile ()->IsBad ())
|
||||
hop = i2p::data::netdb.GetRandomRouter (prevHop);
|
||||
return hop;
|
||||
}
|
||||
|
||||
bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop)
|
||||
{
|
||||
auto prevHop = i2p::context.GetSharedRouterInfo ();
|
||||
if(i2p::transport::transports.RoutesRestricted())
|
||||
{
|
||||
/** if routes are restricted prepend trusted first hop */
|
||||
auto hop = i2p::transport::transports.GetRestrictedPeer();
|
||||
if(!hop) return false;
|
||||
peers.push_back(hop->GetRouterIdentity());
|
||||
prevHop = hop;
|
||||
}
|
||||
else if (i2p::transport::transports.GetNumPeers () > 25)
|
||||
{
|
||||
auto r = i2p::transport::transports.GetRandomPeer ();
|
||||
if (r && !r->GetProfile ()->IsBad ())
|
||||
{
|
||||
prevHop = r;
|
||||
peers.push_back (r->GetRouterIdentity ());
|
||||
numHops--;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < numHops; i++ )
|
||||
{
|
||||
auto hop = nextHop (prevHop);
|
||||
if (!hop)
|
||||
{
|
||||
LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ());
|
||||
return false;
|
||||
}
|
||||
prevHop = hop;
|
||||
peers.push_back (hop->GetRouterIdentity ());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TunnelPool::SelectPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers, bool isInbound)
|
||||
{
|
||||
int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops;
|
||||
// peers is empty
|
||||
if (numHops <= 0) return true;
|
||||
// custom peer selector in use ?
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_CustomPeerSelectorMutex);
|
||||
if (m_CustomPeerSelector)
|
||||
return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound);
|
||||
}
|
||||
// explicit peers in use
|
||||
if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound);
|
||||
return StandardSelectPeers(peers, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
bool TunnelPool::SelectExplicitPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers, bool isInbound)
|
||||
{
|
||||
int size = m_ExplicitPeers->size ();
|
||||
std::vector<int> peerIndicies;
|
||||
for (int i = 0; i < size; i++) peerIndicies.push_back(i);
|
||||
std::random_shuffle (peerIndicies.begin(), peerIndicies.end());
|
||||
|
||||
int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops;
|
||||
for (int i = 0; i < numHops; i++)
|
||||
{
|
||||
auto& ident = (*m_ExplicitPeers)[peerIndicies[i]];
|
||||
auto r = i2p::data::netdb.FindRouter (ident);
|
||||
if (r)
|
||||
peers.push_back (r->GetRouterIdentity ());
|
||||
else
|
||||
{
|
||||
LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ());
|
||||
i2p::data::netdb.RequestDestination (ident);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TunnelPool::CreateInboundTunnel ()
|
||||
{
|
||||
auto outboundTunnel = GetNextOutboundTunnel ();
|
||||
if (!outboundTunnel)
|
||||
outboundTunnel = tunnels.GetNextOutboundTunnel ();
|
||||
LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel...");
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers;
|
||||
if (SelectPeers (peers, true))
|
||||
{
|
||||
std::shared_ptr<TunnelConfig> config;
|
||||
if (m_NumInboundHops > 0)
|
||||
{
|
||||
std::reverse (peers.begin (), peers.end ());
|
||||
config = std::make_shared<TunnelConfig> (peers);
|
||||
}
|
||||
auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel);
|
||||
tunnel->SetTunnelPool (shared_from_this ());
|
||||
if (tunnel->IsEstablished ()) // zero hops
|
||||
TunnelCreated (tunnel);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Tunnels: Can't create inbound tunnel, no peers available");
|
||||
}
|
||||
|
||||
void TunnelPool::RecreateInboundTunnel (std::shared_ptr<InboundTunnel> tunnel)
|
||||
{
|
||||
auto outboundTunnel = GetNextOutboundTunnel ();
|
||||
if (!outboundTunnel)
|
||||
outboundTunnel = tunnels.GetNextOutboundTunnel ();
|
||||
LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel...");
|
||||
std::shared_ptr<TunnelConfig> config;
|
||||
if (m_NumInboundHops > 0) config = std::make_shared<TunnelConfig>(tunnel->GetPeers ());
|
||||
auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel);
|
||||
newTunnel->SetTunnelPool (shared_from_this());
|
||||
if (newTunnel->IsEstablished ()) // zero hops
|
||||
TunnelCreated (newTunnel);
|
||||
}
|
||||
|
||||
void TunnelPool::CreateOutboundTunnel ()
|
||||
{
|
||||
auto inboundTunnel = GetNextInboundTunnel ();
|
||||
if (!inboundTunnel)
|
||||
inboundTunnel = tunnels.GetNextInboundTunnel ();
|
||||
if (inboundTunnel)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel...");
|
||||
std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers;
|
||||
if (SelectPeers (peers, false))
|
||||
{
|
||||
std::shared_ptr<TunnelConfig> config;
|
||||
if (m_NumOutboundHops > 0)
|
||||
config = std::make_shared<TunnelConfig>(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ());
|
||||
auto tunnel = tunnels.CreateOutboundTunnel (config);
|
||||
tunnel->SetTunnelPool (shared_from_this ());
|
||||
if (tunnel->IsEstablished ()) // zero hops
|
||||
TunnelCreated (tunnel);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available");
|
||||
}
|
||||
else
|
||||
LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found");
|
||||
}
|
||||
|
||||
void TunnelPool::RecreateOutboundTunnel (std::shared_ptr<OutboundTunnel> tunnel)
|
||||
{
|
||||
auto inboundTunnel = GetNextInboundTunnel ();
|
||||
if (!inboundTunnel)
|
||||
inboundTunnel = tunnels.GetNextInboundTunnel ();
|
||||
if (inboundTunnel)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel...");
|
||||
std::shared_ptr<TunnelConfig> config;
|
||||
if (m_NumOutboundHops > 0)
|
||||
config = std::make_shared<TunnelConfig>(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ());
|
||||
auto newTunnel = tunnels.CreateOutboundTunnel (config);
|
||||
newTunnel->SetTunnelPool (shared_from_this ());
|
||||
if (newTunnel->IsEstablished ()) // zero hops
|
||||
TunnelCreated (newTunnel);
|
||||
}
|
||||
else
|
||||
LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found");
|
||||
}
|
||||
|
||||
void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr<OutboundTunnel> outboundTunnel)
|
||||
{
|
||||
LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel...");
|
||||
auto tunnel = tunnels.CreateInboundTunnel (std::make_shared<TunnelConfig>(outboundTunnel->GetInvertedPeers ()), outboundTunnel);
|
||||
tunnel->SetTunnelPool (shared_from_this ());
|
||||
}
|
||||
|
||||
void TunnelPool::SetCustomPeerSelector(ITunnelPeerSelector * selector)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_CustomPeerSelectorMutex);
|
||||
m_CustomPeerSelector = selector;
|
||||
}
|
||||
|
||||
void TunnelPool::UnsetCustomPeerSelector()
|
||||
{
|
||||
SetCustomPeerSelector(nullptr);
|
||||
}
|
||||
|
||||
bool TunnelPool::HasCustomPeerSelector()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_CustomPeerSelectorMutex);
|
||||
return m_CustomPeerSelector != nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<InboundTunnel> TunnelPool::GetLowestLatencyInboundTunnel(std::shared_ptr<InboundTunnel> exclude) const
|
||||
{
|
||||
std::shared_ptr<InboundTunnel> tun = nullptr;
|
||||
std::unique_lock<std::mutex> lock(m_InboundTunnelsMutex);
|
||||
uint64_t min = 1000000;
|
||||
for (const auto & itr : m_InboundTunnels) {
|
||||
if(!itr->LatencyIsKnown()) continue;
|
||||
auto l = itr->GetMeanLatency();
|
||||
if (l >= min) continue;
|
||||
tun = itr;
|
||||
if(tun == exclude) continue;
|
||||
min = l;
|
||||
}
|
||||
return tun;
|
||||
}
|
||||
|
||||
std::shared_ptr<OutboundTunnel> TunnelPool::GetLowestLatencyOutboundTunnel(std::shared_ptr<OutboundTunnel> exclude) const
|
||||
{
|
||||
std::shared_ptr<OutboundTunnel> tun = nullptr;
|
||||
std::unique_lock<std::mutex> lock(m_OutboundTunnelsMutex);
|
||||
uint64_t min = 1000000;
|
||||
for (const auto & itr : m_OutboundTunnels) {
|
||||
if(!itr->LatencyIsKnown()) continue;
|
||||
auto l = itr->GetMeanLatency();
|
||||
if (l >= min) continue;
|
||||
tun = itr;
|
||||
if(tun == exclude) continue;
|
||||
min = l;
|
||||
}
|
||||
return tun;
|
||||
}
|
||||
|
||||
void TunnelPool::OnTunnelBuildResult(std::shared_ptr<Tunnel> tunnel, TunnelBuildResult result)
|
||||
{
|
||||
auto peers = tunnel->GetPeers();
|
||||
if(m_CustomPeerSelector) m_CustomPeerSelector->OnBuildResult(peers, tunnel->IsInbound(), result);
|
||||
}
|
||||
}
|
||||
}
|
139
libi2pd/TunnelPool.h
Normal file
139
libi2pd/TunnelPool.h
Normal file
|
@ -0,0 +1,139 @@
|
|||
#ifndef TUNNEL_POOL__
|
||||
#define TUNNEL_POOL__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "Identity.h"
|
||||
#include "LeaseSet.h"
|
||||
#include "RouterInfo.h"
|
||||
#include "I2NPProtocol.h"
|
||||
#include "TunnelBase.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Garlic.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace tunnel
|
||||
{
|
||||
class Tunnel;
|
||||
class InboundTunnel;
|
||||
class OutboundTunnel;
|
||||
|
||||
|
||||
enum TunnelBuildResult {
|
||||
eBuildResultOkay, // tunnel was built okay
|
||||
eBuildResultRejected, // tunnel build was explicitly rejected
|
||||
eBuildResultTimeout // tunnel build timed out
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<const i2p::data::IdentityEx> Peer;
|
||||
typedef std::vector<Peer> Path;
|
||||
|
||||
/** interface for custom tunnel peer selection algorithm */
|
||||
struct ITunnelPeerSelector
|
||||
{
|
||||
virtual ~ITunnelPeerSelector() {};
|
||||
virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0;
|
||||
virtual bool OnBuildResult(const Path & peers, bool isInbound, TunnelBuildResult result) = 0;
|
||||
};
|
||||
|
||||
|
||||
typedef std::function<std::shared_ptr<const i2p::data::RouterInfo>(std::shared_ptr<const i2p::data::RouterInfo>)> SelectHopFunc;
|
||||
// standard peer selection algorithm
|
||||
bool StandardSelectPeers(Path & path, int hops, bool inbound, SelectHopFunc nextHop);
|
||||
|
||||
class TunnelPool: public std::enable_shared_from_this<TunnelPool> // per local destination
|
||||
{
|
||||
public:
|
||||
|
||||
TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels);
|
||||
~TunnelPool ();
|
||||
|
||||
std::shared_ptr<i2p::garlic::GarlicDestination> GetLocalDestination () const { return m_LocalDestination; };
|
||||
void SetLocalDestination (std::shared_ptr<i2p::garlic::GarlicDestination> destination) { m_LocalDestination = destination; };
|
||||
void SetExplicitPeers (std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers);
|
||||
|
||||
void CreateTunnels ();
|
||||
void TunnelCreated (std::shared_ptr<InboundTunnel> createdTunnel);
|
||||
void TunnelExpired (std::shared_ptr<InboundTunnel> expiredTunnel);
|
||||
void TunnelCreated (std::shared_ptr<OutboundTunnel> createdTunnel);
|
||||
void TunnelExpired (std::shared_ptr<OutboundTunnel> expiredTunnel);
|
||||
void RecreateInboundTunnel (std::shared_ptr<InboundTunnel> tunnel);
|
||||
void RecreateOutboundTunnel (std::shared_ptr<OutboundTunnel> tunnel);
|
||||
std::vector<std::shared_ptr<InboundTunnel> > GetInboundTunnels (int num) const;
|
||||
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded = nullptr) const;
|
||||
std::shared_ptr<InboundTunnel> GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded = nullptr) const;
|
||||
std::shared_ptr<OutboundTunnel> GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const;
|
||||
void TestTunnels ();
|
||||
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
|
||||
void ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg);
|
||||
|
||||
bool IsActive () const { return m_IsActive; };
|
||||
void SetActive (bool isActive) { m_IsActive = isActive; };
|
||||
void DetachTunnels ();
|
||||
|
||||
int GetNumInboundTunnels () const { return m_NumInboundTunnels; };
|
||||
int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; };
|
||||
|
||||
void SetCustomPeerSelector(ITunnelPeerSelector * selector);
|
||||
void UnsetCustomPeerSelector();
|
||||
bool HasCustomPeerSelector();
|
||||
|
||||
/** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */
|
||||
void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; }
|
||||
|
||||
/** @brief return true if this tunnel pool has a latency requirement */
|
||||
bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; }
|
||||
|
||||
/** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */
|
||||
std::shared_ptr<InboundTunnel> GetLowestLatencyInboundTunnel(std::shared_ptr<InboundTunnel> exclude=nullptr) const;
|
||||
std::shared_ptr<OutboundTunnel> GetLowestLatencyOutboundTunnel(std::shared_ptr<OutboundTunnel> exclude=nullptr) const;
|
||||
|
||||
void OnTunnelBuildResult(std::shared_ptr<Tunnel> tunnel, TunnelBuildResult result);
|
||||
|
||||
// for overriding tunnel peer selection
|
||||
std::shared_ptr<const i2p::data::RouterInfo> SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop) const;
|
||||
|
||||
private:
|
||||
|
||||
void CreateInboundTunnel ();
|
||||
void CreateOutboundTunnel ();
|
||||
void CreatePairedInboundTunnel (std::shared_ptr<OutboundTunnel> outboundTunnel);
|
||||
template<class TTunnels>
|
||||
typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const;
|
||||
bool SelectPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& hops, bool isInbound);
|
||||
bool SelectExplicitPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& hops, bool isInbound);
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<i2p::garlic::GarlicDestination> m_LocalDestination;
|
||||
int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels;
|
||||
std::shared_ptr<std::vector<i2p::data::IdentHash> > m_ExplicitPeers;
|
||||
mutable std::mutex m_InboundTunnelsMutex;
|
||||
std::set<std::shared_ptr<InboundTunnel>, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first
|
||||
mutable std::mutex m_OutboundTunnelsMutex;
|
||||
std::set<std::shared_ptr<OutboundTunnel>, TunnelCreationTimeCmp> m_OutboundTunnels;
|
||||
mutable std::mutex m_TestsMutex;
|
||||
std::map<uint32_t, std::pair<std::shared_ptr<OutboundTunnel>, std::shared_ptr<InboundTunnel> > > m_Tests;
|
||||
bool m_IsActive;
|
||||
std::mutex m_CustomPeerSelectorMutex;
|
||||
ITunnelPeerSelector * m_CustomPeerSelector;
|
||||
|
||||
uint64_t m_MinLatency=0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms
|
||||
uint64_t m_MaxLatency=0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms
|
||||
|
||||
public:
|
||||
|
||||
// for HTTP only
|
||||
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
|
||||
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
139
libi2pd/api.cpp
Normal file
139
libi2pd/api.cpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
#include <string>
|
||||
#include <map>
|
||||
#include "Config.h"
|
||||
#include "Log.h"
|
||||
#include "NetDb.h"
|
||||
#include "Transports.h"
|
||||
#include "Tunnel.h"
|
||||
#include "RouterContext.h"
|
||||
#include "Identity.h"
|
||||
#include "Destination.h"
|
||||
#include "Crypto.h"
|
||||
#include "FS.h"
|
||||
#include "api.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace api
|
||||
{
|
||||
void InitI2P (int argc, char* argv[], const char * appName)
|
||||
{
|
||||
i2p::config::Init ();
|
||||
i2p::config::ParseCmdline (argc, argv, true); // ignore unknown options and help
|
||||
i2p::config::Finalize ();
|
||||
|
||||
std::string datadir; i2p::config::GetOption("datadir", datadir);
|
||||
|
||||
i2p::fs::SetAppName (appName);
|
||||
i2p::fs::DetectDataDir(datadir, false);
|
||||
i2p::fs::Init();
|
||||
|
||||
#if defined(__x86_64__)
|
||||
i2p::crypto::InitCrypto (false);
|
||||
#else
|
||||
i2p::crypto::InitCrypto (true);
|
||||
#endif
|
||||
|
||||
int netID; i2p::config::GetOption("netid", netID);
|
||||
i2p::context.SetNetID (netID);
|
||||
|
||||
i2p::context.Init ();
|
||||
}
|
||||
|
||||
void TerminateI2P ()
|
||||
{
|
||||
i2p::crypto::TerminateCrypto ();
|
||||
}
|
||||
|
||||
void StartI2P (std::shared_ptr<std::ostream> logStream)
|
||||
{
|
||||
if (logStream)
|
||||
i2p::log::Logger().SendTo (logStream);
|
||||
else
|
||||
i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log"));
|
||||
i2p::log::Logger().Start ();
|
||||
LogPrint(eLogInfo, "API: starting NetDB");
|
||||
i2p::data::netdb.Start();
|
||||
LogPrint(eLogInfo, "API: starting Transports");
|
||||
i2p::transport::transports.Start();
|
||||
LogPrint(eLogInfo, "API: starting Tunnels");
|
||||
i2p::tunnel::tunnels.Start();
|
||||
}
|
||||
|
||||
void StopI2P ()
|
||||
{
|
||||
LogPrint(eLogInfo, "API: shutting down");
|
||||
LogPrint(eLogInfo, "API: stopping Tunnels");
|
||||
i2p::tunnel::tunnels.Stop();
|
||||
LogPrint(eLogInfo, "API: stopping Transports");
|
||||
i2p::transport::transports.Stop();
|
||||
LogPrint(eLogInfo, "API: stopping NetDB");
|
||||
i2p::data::netdb.Stop();
|
||||
i2p::log::Logger().Stop ();
|
||||
}
|
||||
|
||||
void RunPeerTest ()
|
||||
{
|
||||
i2p::transport::transports.PeerTest ();
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::client::ClientDestination> CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
|
||||
const std::map<std::string, std::string> * params)
|
||||
{
|
||||
auto localDestination = std::make_shared<i2p::client::ClientDestination> (keys, isPublic, params);
|
||||
localDestination->Start ();
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::client::ClientDestination> CreateLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType,
|
||||
const std::map<std::string, std::string> * params)
|
||||
{
|
||||
i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
|
||||
auto localDestination = std::make_shared<i2p::client::ClientDestination> (keys, isPublic, params);
|
||||
localDestination->Start ();
|
||||
return localDestination;
|
||||
}
|
||||
|
||||
void DestroyLocalDestination (std::shared_ptr<i2p::client::ClientDestination> dest)
|
||||
{
|
||||
if (dest)
|
||||
dest->Stop ();
|
||||
}
|
||||
|
||||
void RequestLeaseSet (std::shared_ptr<i2p::client::ClientDestination> dest, const i2p::data::IdentHash& remote)
|
||||
{
|
||||
if (dest)
|
||||
dest->RequestDestination (remote);
|
||||
}
|
||||
|
||||
std::shared_ptr<i2p::stream::Stream> CreateStream (std::shared_ptr<i2p::client::ClientDestination> dest, const i2p::data::IdentHash& remote)
|
||||
{
|
||||
if (!dest) return nullptr;
|
||||
auto leaseSet = dest->FindLeaseSet (remote);
|
||||
if (leaseSet)
|
||||
{
|
||||
auto stream = dest->CreateStream (leaseSet);
|
||||
stream->Send (nullptr, 0); // connect
|
||||
return stream;
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestLeaseSet (dest, remote);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AcceptStream (std::shared_ptr<i2p::client::ClientDestination> dest, const i2p::stream::StreamingDestination::Acceptor& acceptor)
|
||||
{
|
||||
if (dest)
|
||||
dest->AcceptStreams (acceptor);
|
||||
}
|
||||
|
||||
void DestroyStream (std::shared_ptr<i2p::stream::Stream> stream)
|
||||
{
|
||||
if (stream)
|
||||
stream->Close ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
libi2pd/api.h
Normal file
38
libi2pd/api.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef API_H__
|
||||
#define API_H__
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include "Identity.h"
|
||||
#include "Destination.h"
|
||||
#include "Streaming.h"
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace api
|
||||
{
|
||||
// initialization start and stop
|
||||
void InitI2P (int argc, char* argv[], const char * appName);
|
||||
void TerminateI2P ();
|
||||
void StartI2P (std::shared_ptr<std::ostream> logStream = nullptr);
|
||||
// write system log to logStream, if not specified to <appName>.log in application's folder
|
||||
void StopI2P ();
|
||||
void RunPeerTest (); // should be called after UPnP
|
||||
|
||||
// destinations
|
||||
std::shared_ptr<i2p::client::ClientDestination> CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true,
|
||||
const std::map<std::string, std::string> * params = nullptr);
|
||||
std::shared_ptr<i2p::client::ClientDestination> CreateLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256,
|
||||
const std::map<std::string, std::string> * params = nullptr); // transient destinations usually not published
|
||||
void DestroyLocalDestination (std::shared_ptr<i2p::client::ClientDestination> dest);
|
||||
|
||||
// streams
|
||||
void RequestLeaseSet (std::shared_ptr<i2p::client::ClientDestination> dest, const i2p::data::IdentHash& remote);
|
||||
std::shared_ptr<i2p::stream::Stream> CreateStream (std::shared_ptr<i2p::client::ClientDestination> dest, const i2p::data::IdentHash& remote);
|
||||
void AcceptStream (std::shared_ptr<i2p::client::ClientDestination> dest, const i2p::stream::StreamingDestination::Acceptor& acceptor);
|
||||
void DestroyStream (std::shared_ptr<i2p::stream::Stream> stream);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
1
libi2pd/stdafx.cpp
Normal file
1
libi2pd/stdafx.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "stdafx.h"
|
66
libi2pd/stdafx.h
Normal file
66
libi2pd/stdafx.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef STDAFX_H__
|
||||
#define STDAFX_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <cassert>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string.h> // TODO: replace with cstring and std::<old func> through out
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
#include <cctype>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/date_time/local_time/local_time.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/program_options/detail/config_file.hpp>
|
||||
#include <boost/program_options/parsers.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/dsa.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#endif
|
307
libi2pd/util.cpp
Normal file
307
libi2pd/util.cpp
Normal file
|
@ -0,0 +1,307 @@
|
|||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "util.h"
|
||||
#include "Log.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "IPHLPAPI.lib")
|
||||
#endif // _MSC_VER
|
||||
|
||||
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
|
||||
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
|
||||
|
||||
int inet_pton(int af, const char *src, void *dst)
|
||||
{ /* This function was written by Petar Korponai?. See
|
||||
http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */
|
||||
struct sockaddr_storage ss;
|
||||
int size = sizeof (ss);
|
||||
char src_copy[INET6_ADDRSTRLEN + 1];
|
||||
|
||||
ZeroMemory (&ss, sizeof (ss));
|
||||
strncpy (src_copy, src, INET6_ADDRSTRLEN + 1);
|
||||
src_copy[INET6_ADDRSTRLEN] = 0;
|
||||
|
||||
if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0)
|
||||
{
|
||||
switch (af)
|
||||
{
|
||||
case AF_INET:
|
||||
*(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr;
|
||||
return 1;
|
||||
case AF_INET6:
|
||||
*(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else /* !WIN32 => UNIX */
|
||||
#include <sys/types.h>
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
namespace net
|
||||
{
|
||||
#ifdef WIN32
|
||||
int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback)
|
||||
{
|
||||
ULONG outBufLen = 0;
|
||||
PIP_ADAPTER_ADDRESSES pAddresses = nullptr;
|
||||
PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr;
|
||||
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr;
|
||||
|
||||
|
||||
if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen)
|
||||
== ERROR_BUFFER_OVERFLOW) {
|
||||
FREE(pAddresses);
|
||||
pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen);
|
||||
}
|
||||
|
||||
DWORD dwRetVal = GetAdaptersAddresses(
|
||||
AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen
|
||||
);
|
||||
|
||||
if(dwRetVal != NO_ERROR) {
|
||||
LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed");
|
||||
FREE(pAddresses);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
pCurrAddresses = pAddresses;
|
||||
while(pCurrAddresses) {
|
||||
PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress;
|
||||
|
||||
pUnicast = pCurrAddresses->FirstUnicastAddress;
|
||||
if(pUnicast == nullptr) {
|
||||
LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv4 address, this is not supported");
|
||||
}
|
||||
for(int i = 0; pUnicast != nullptr; ++i) {
|
||||
LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr;
|
||||
sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr;
|
||||
if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) {
|
||||
auto result = pAddresses->Mtu;
|
||||
FREE(pAddresses);
|
||||
return result;
|
||||
}
|
||||
pUnicast = pUnicast->Next;
|
||||
}
|
||||
pCurrAddresses = pCurrAddresses->Next;
|
||||
}
|
||||
|
||||
LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv4 addresses found");
|
||||
FREE(pAddresses);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback)
|
||||
{
|
||||
ULONG outBufLen = 0;
|
||||
PIP_ADAPTER_ADDRESSES pAddresses = nullptr;
|
||||
PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr;
|
||||
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr;
|
||||
|
||||
if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen)
|
||||
== ERROR_BUFFER_OVERFLOW) {
|
||||
FREE(pAddresses);
|
||||
pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen);
|
||||
}
|
||||
|
||||
DWORD dwRetVal = GetAdaptersAddresses(
|
||||
AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen
|
||||
);
|
||||
|
||||
if(dwRetVal != NO_ERROR) {
|
||||
LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed");
|
||||
FREE(pAddresses);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
bool found_address = false;
|
||||
pCurrAddresses = pAddresses;
|
||||
while(pCurrAddresses) {
|
||||
PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress;
|
||||
pUnicast = pCurrAddresses->FirstUnicastAddress;
|
||||
if(pUnicast == nullptr) {
|
||||
LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv6 address, this is not supported");
|
||||
}
|
||||
for(int i = 0; pUnicast != nullptr; ++i) {
|
||||
LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr;
|
||||
sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr;
|
||||
|
||||
for (int j = 0; j != 8; ++j) {
|
||||
if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) {
|
||||
break;
|
||||
} else {
|
||||
found_address = true;
|
||||
}
|
||||
} if (found_address) {
|
||||
auto result = pAddresses->Mtu;
|
||||
FREE(pAddresses);
|
||||
pAddresses = nullptr;
|
||||
return result;
|
||||
}
|
||||
pUnicast = pUnicast->Next;
|
||||
}
|
||||
|
||||
pCurrAddresses = pCurrAddresses->Next;
|
||||
}
|
||||
|
||||
LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv6 addresses found");
|
||||
FREE(pAddresses);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback)
|
||||
{
|
||||
#ifdef UNICODE
|
||||
string localAddress_temporary = localAddress.to_string();
|
||||
wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end());
|
||||
#else
|
||||
std::string localAddressUniversal = localAddress.to_string();
|
||||
#endif
|
||||
|
||||
if(localAddress.is_v4()) {
|
||||
sockaddr_in inputAddress;
|
||||
inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr));
|
||||
return GetMTUWindowsIpv4(inputAddress, fallback);
|
||||
} else if(localAddress.is_v6()) {
|
||||
sockaddr_in6 inputAddress;
|
||||
inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr));
|
||||
return GetMTUWindowsIpv6(inputAddress, fallback);
|
||||
} else {
|
||||
LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported");
|
||||
return fallback;
|
||||
}
|
||||
|
||||
}
|
||||
#else // assume unix
|
||||
int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback)
|
||||
{
|
||||
ifaddrs* ifaddr, *ifa = nullptr;
|
||||
if(getifaddrs(&ifaddr) == -1)
|
||||
{
|
||||
LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno));
|
||||
return fallback;
|
||||
}
|
||||
|
||||
int family = 0;
|
||||
// look for interface matching local address
|
||||
for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
|
||||
{
|
||||
if(!ifa->ifa_addr)
|
||||
continue;
|
||||
|
||||
family = ifa->ifa_addr->sa_family;
|
||||
if(family == AF_INET && localAddress.is_v4())
|
||||
{
|
||||
sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr;
|
||||
if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4))
|
||||
break; // address matches
|
||||
}
|
||||
else if(family == AF_INET6 && localAddress.is_v6())
|
||||
{
|
||||
sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr;
|
||||
if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16))
|
||||
break; // address matches
|
||||
}
|
||||
}
|
||||
int mtu = fallback;
|
||||
if(ifa && family)
|
||||
{ // interface found?
|
||||
int fd = socket(family, SOCK_DGRAM, 0);
|
||||
if(fd > 0)
|
||||
{
|
||||
ifreq ifr;
|
||||
strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query
|
||||
if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0)
|
||||
mtu = ifr.ifr_mtu; // MTU
|
||||
else
|
||||
LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno));
|
||||
close(fd);
|
||||
}
|
||||
else
|
||||
LogPrint(eLogError, "NetIface: Failed to create datagram socket");
|
||||
}
|
||||
else
|
||||
LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found");
|
||||
freeifaddrs(ifaddr);
|
||||
|
||||
return mtu;
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
int GetMTU(const boost::asio::ip::address& localAddress)
|
||||
{
|
||||
const int fallback = 576; // fallback MTU
|
||||
|
||||
#ifdef WIN32
|
||||
return GetMTUWindows(localAddress, fallback);
|
||||
#else
|
||||
return GetMTUUnix(localAddress, fallback);
|
||||
#endif
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6)
|
||||
{
|
||||
#ifdef WIN32
|
||||
LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32");
|
||||
return boost::asio::ip::address::from_string("127.0.0.1");
|
||||
#else
|
||||
int af = (ipv6 ? AF_INET6 : AF_INET);
|
||||
ifaddrs * addrs = nullptr;
|
||||
if(getifaddrs(&addrs) == 0)
|
||||
{
|
||||
// got ifaddrs
|
||||
ifaddrs * cur = addrs;
|
||||
while(cur)
|
||||
{
|
||||
std::string cur_ifname(cur->ifa_name);
|
||||
if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af)
|
||||
{
|
||||
// match
|
||||
char * addr = new char[INET6_ADDRSTRLEN];
|
||||
bzero(addr, INET6_ADDRSTRLEN);
|
||||
if(af == AF_INET)
|
||||
inet_ntop(af, &((sockaddr_in *)cur->ifa_addr)->sin_addr, addr, INET6_ADDRSTRLEN);
|
||||
else
|
||||
inet_ntop(af, &((sockaddr_in6 *)cur->ifa_addr)->sin6_addr, addr, INET6_ADDRSTRLEN);
|
||||
freeifaddrs(addrs);
|
||||
std::string cur_ifaddr(addr);
|
||||
delete[] addr;
|
||||
return boost::asio::ip::address::from_string(cur_ifaddr);
|
||||
}
|
||||
cur = cur->ifa_next;
|
||||
}
|
||||
}
|
||||
if(addrs) freeifaddrs(addrs);
|
||||
std::string fallback;
|
||||
if(ipv6) {
|
||||
fallback = "::";
|
||||
LogPrint(eLogWarning, "NetIface: cannot find ipv6 address for interface ", ifname);
|
||||
} else {
|
||||
fallback = "127.0.0.1";
|
||||
LogPrint(eLogWarning, "NetIface: cannot find ipv4 address for interface ", ifname);
|
||||
}
|
||||
return boost::asio::ip::address::from_string(fallback);
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // util
|
||||
} // i2p
|
127
libi2pd/util.h
Normal file
127
libi2pd/util.h
Normal file
|
@ -0,0 +1,127 @@
|
|||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <boost/lexical_cast.hpp>
|
||||
namespace std
|
||||
{
|
||||
template <typename T>
|
||||
std::string to_string(T value)
|
||||
{
|
||||
return boost::lexical_cast<std::string>(value);
|
||||
}
|
||||
|
||||
inline int stoi(const std::string& str)
|
||||
{
|
||||
return boost::lexical_cast<int>(str);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace i2p
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
|
||||
template<class T>
|
||||
class MemoryPool
|
||||
{
|
||||
public:
|
||||
|
||||
MemoryPool (): m_Head (nullptr) {};
|
||||
~MemoryPool ()
|
||||
{
|
||||
while (m_Head)
|
||||
{
|
||||
auto tmp = m_Head;
|
||||
m_Head = static_cast<T*>(*(void * *)m_Head); // next
|
||||
delete tmp;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
T * Acquire (TArgs&&... args)
|
||||
{
|
||||
if (!m_Head) return new T(args...);
|
||||
else
|
||||
{
|
||||
auto tmp = m_Head;
|
||||
m_Head = static_cast<T*>(*(void * *)m_Head); // next
|
||||
return new (tmp)T(args...);
|
||||
}
|
||||
}
|
||||
|
||||
void Release (T * t)
|
||||
{
|
||||
if (!t) return;
|
||||
t->~T ();
|
||||
*(void * *)t = m_Head; // next
|
||||
m_Head = t;
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
std::unique_ptr<T, std::function<void(T*)> > AcquireUnique (TArgs&&... args)
|
||||
{
|
||||
return std::unique_ptr<T, std::function<void(T*)> >(Acquire (args...),
|
||||
std::bind (&MemoryPool<T>::Release, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
std::shared_ptr<T> AcquireShared (TArgs&&... args)
|
||||
{
|
||||
return std::shared_ptr<T>(Acquire (args...), std::bind (&MemoryPool<T>::Release, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
T * m_Head;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class MemoryPoolMt: public MemoryPool<T>
|
||||
{
|
||||
public:
|
||||
|
||||
MemoryPoolMt () {};
|
||||
template<typename... TArgs>
|
||||
T * AcquireMt (TArgs&&... args)
|
||||
{
|
||||
if (!this->m_Head) return new T(args...);
|
||||
std::lock_guard<std::mutex> l(m_Mutex);
|
||||
return this->Acquire (args...);
|
||||
}
|
||||
|
||||
void ReleaseMt (T * t)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(m_Mutex);
|
||||
this->Release (t);
|
||||
}
|
||||
|
||||
template<template<typename, typename...>class C, typename... R>
|
||||
void ReleaseMt(const C<T *, R...>& c)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(m_Mutex);
|
||||
for (auto& it: c)
|
||||
this->Release (it);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::mutex m_Mutex;
|
||||
};
|
||||
|
||||
namespace net
|
||||
{
|
||||
int GetMTU (const boost::asio::ip::address& localAddress);
|
||||
const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
28
libi2pd/version.h
Normal file
28
libi2pd/version.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef _VERSION_H_
|
||||
#define _VERSION_H_
|
||||
|
||||
#define CODENAME "Purple"
|
||||
|
||||
#define STRINGIZE(x) #x
|
||||
#define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c)
|
||||
|
||||
#define I2PD_VERSION_MAJOR 2
|
||||
#define I2PD_VERSION_MINOR 13
|
||||
#define I2PD_VERSION_MICRO 0
|
||||
#define I2PD_VERSION_PATCH 0
|
||||
#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO)
|
||||
#define VERSION I2PD_VERSION
|
||||
|
||||
#ifdef MESHNET
|
||||
#define I2PD_NET_ID 3
|
||||
#else
|
||||
#define I2PD_NET_ID 2
|
||||
#endif
|
||||
|
||||
#define I2P_VERSION_MAJOR 0
|
||||
#define I2P_VERSION_MINOR 9
|
||||
#define I2P_VERSION_MICRO 29
|
||||
#define I2P_VERSION_PATCH 0
|
||||
#define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue